diff --git a/desktop/go.mod b/desktop/go.mod index 4347a465..20d904f8 100644 --- a/desktop/go.mod +++ b/desktop/go.mod @@ -4,6 +4,26 @@ go 1.26.4 replace github.com/midagedev/gadak => ../ +// wails v3.0.0-beta.12 plus wailsapp/wails#6006, nothing else. The tag is +// the upstream tag with those two commits cherry-picked +// (github.com/midagedev/wails, branch gadak/v3.0.0-beta.12); `git diff +// v3.0.0-beta.12..v3.0.0-beta.12-gadak.1` is 10 lines in one file. +// +// Why: webview_window_windows.go registers a "*" WebResourceRequested +// filter for asset serving, so on Windows EVERY request the WebView makes +// runs through edge.Chromium's handler — and that handler called +// log.Fatal(err) when args.GetRequest() failed. One transient COM failure +// on one request killed gadak, skipping deferred cleanup, and log.Fatal +// does not even reach the error callback wails' own SetErrorCallback +// configures. The PR drops the request and logs instead. +// +// The PR is open and unblocked (its only review comment was addressed and +// resolved the same day) — it is waiting on a maintainer, and gadak is not. +// DELETE THIS REPLACE when it merges into a beta we take: the fork branch +// exists only to carry it, and every wails bump has to redo it (fetch the +// new tag, cherry-pick, tag as -gadak.N) until then. +replace github.com/wailsapp/wails/v3 => github.com/midagedev/wails/v3 v3.0.0-beta.12-gadak.1 + require ( github.com/midagedev/gadak v0.0.0 github.com/wailsapp/wails/v3 v3.0.0-beta.12 diff --git a/desktop/go.sum b/desktop/go.sum index 40c33e00..cea11221 100644 --- a/desktop/go.sum +++ b/desktop/go.sum @@ -38,6 +38,8 @@ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byF github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/midagedev/issuetap v0.0.0-20260828070557-ccb366aea250 h1:SXqP1UxBFdBUHTThplIgw5LCr5IcvFwWbeDP+ku36Xk= github.com/midagedev/issuetap v0.0.0-20260828070557-ccb366aea250/go.mod h1:VI3sqskL2eqVH88g5jS9hAHTWPF8SJocH69p2/xh5Z0= +github.com/midagedev/wails/v3 v3.0.0-beta.12-gadak.1 h1:SCcBMBdM3MVtoPv9FaVJZMUd1Hl9sGEzy7Cx5Q5kRXY= +github.com/midagedev/wails/v3 v3.0.0-beta.12-gadak.1/go.mod h1:zKZYhB3WjrN5LhJWbnOAVMN0Xf8qTozbw2nf5micKl4= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -48,8 +50,6 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/wailsapp/wails/v3 v3.0.0-beta.12 h1:Vema2kFgJvkwPovQ8GMBBrkZNm5cKVR4bYzSCMgU+PU= -github.com/wailsapp/wails/v3 v3.0.0-beta.12/go.mod h1:zKZYhB3WjrN5LhJWbnOAVMN0Xf8qTozbw2nf5micKl4= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= diff --git a/desktop/platforms_test.go b/desktop/platforms_test.go index 8996e2d1..be1116dd 100644 --- a/desktop/platforms_test.go +++ b/desktop/platforms_test.go @@ -99,8 +99,47 @@ func TestPinnedWailsVersionIsNamed(t *testing.T) { } } +// replacedWailsVersion is the version go.mod redirects the wails module to, +// or "" when there is no replace. Distinct from pinnedWailsVersion: the +// require line names the upstream base even while a fork carries the build. +func replacedWailsVersion(t *testing.T) string { + t.Helper() + body, err := os.ReadFile("go.mod") + if err != nil { + t.Fatal(err) + } + const prefix = "replace github.com/wailsapp/wails/v3 " + for _, line := range strings.Split(string(body), "\n") { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, prefix) { + continue + } + fields := strings.Fields(line) + if len(fields) >= 5 { + return fields[4] + } + t.Fatalf("go.mod wails replace is not a module@version form: %q", line) + } + return "" +} + func TestWailsModuleVersionMatchesGoMod(t *testing.T) { want := pinnedWailsVersion(t) + // 2026-08-30 (GDK-1024 fork policy, docs/runbooks/upstream-pr.md): a + // `replace` onto a fork makes the built binary's version legitimately + // differ from the require line, and wailsModuleVersion() prefers + // Replace.Version by design — it reports what actually linked. So the + // assertion narrows rather than relaxes: the fork must be a patch ON + // the pin (prefix), and the binary must be the fork, not the base. + // FAIL-first: this test went red on the commit that added the replace, + // "wailsModuleVersion() = \"v3.0.0-beta.12-gadak.1\", go.mod has + // \"v3.0.0-beta.12\"" — the run is on PR #78, job "Desktop tests". + if fork := replacedWailsVersion(t); fork != "" { + if !strings.HasPrefix(fork, want) { + t.Fatalf("go.mod replaces wails with %q, which is not a patch on the pinned %q — a fork cut from a different upstream base is a silent version bump", fork, want) + } + want = fork + } if got := wailsModuleVersion(); got != want { t.Fatalf("wailsModuleVersion() = %q, go.mod has %q", got, want) } diff --git a/docs/runbooks/upstream-pr.md b/docs/runbooks/upstream-pr.md index 659500db..bf3c5d0d 100644 --- a/docs/runbooks/upstream-pr.md +++ b/docs/runbooks/upstream-pr.md @@ -94,6 +94,64 @@ unconsidered. - Do not open follow-up issues in the target's tracker on a bot's suggestion; that is the maintainers' call. +## Shipping a PR the upstream has not merged + +A good PR can sit for weeks on a maintainer who is busy, and gadak does not +have to wait behind it (user decision 2026-08-30: *"업스트림 머지 안된건 +포크해서라도 나가자"*). Ship it from a fork branch, pinned, with an +expiry note. + +Do this only when the defect is **reachable in gadak** — say how, in the +`replace` comment. wails#6006's reachability is one line of upstream source +(`webview_window_windows.go` registers a `"*"` filter, so every Windows +request runs the handler that called `log.Fatal`). A PR that is merely good +is an upstream contribution, not a dependency change. + +Go, measured 2026-08-30 on `desktop/` (wails v3): + +```bash +# 1. A distribution branch: the tag we are on, plus the PR's commits only. +git -C ~/repo/ branch -f gadak/ +git -C ~/repo/ checkout gadak/ +git -C ~/repo/ cherry-pick ... # the PR's commits +git -C ~/repo/ tag -gadak.1 +git -C ~/repo/ push fork gadak/ -gadak.1 + +# 2. Pin it. +go mod edit -replace =@-gadak.1 +go mod tidy && go build ./... && GOOS=windows go build ./... +``` + +Two things that are not obvious and cost a round if guessed: + +- **The fork's `go.mod` module line stays as upstream's.** Go resolves the + package path from the *original* module path and only checks that the + replacement's declared path matches the *replacement requirement* — so a + fork works with no import rewriting at all. (Rewriting the module path is + the folklore answer; it would break every internal import.) +- **Tag the fork branch.** Without a tag, `go mod tidy` derives a + pseudo-version from the nearest tag reachable *in the fork*, which was + `v3.0.0-beta.9.0.2026…` for a branch cut from beta.12 — a `go.mod` line + that reads like a downgrade. `-gadak.N` reads like what it + is and sorts as a prerelease. + +- **A pin gate that reads the build has to be taught about the replace.** + `debug.ReadBuildInfo()` reports the *replacement's* version, so + `desktop.TestWailsModuleVersionMatchesGoMod` went red on the very commit + that added the fork. The fix is not to relax it: the gate now reads the + `replace` line too and asserts the fork version is *prefixed by* the + required upstream version — so `v3.0.0-beta.12-gadak.1` passes while a + fork silently cut from a different base still fails. Name the fork tag + `-gadak.N` and that holds for free. + +Then, in the repo: the `replace` carries a comment saying which PR, why the +defect is reachable, and **"delete this when it merges"**. A `replace` with +no expiry is how a fork becomes permanent by accident. Every upstream bump +redoes the branch (new tag → cherry-pick → `-gadak.N+1`) until the merge. + +A `desktop/` change needs a PR — the Windows and Linux desktop jobs are the +ones local gates cannot run (CLAUDE.md). + ## Lead-only boundary Fork, push, `gh pr create`, replies, and any tracker write are lead actions.