From f0bcc8beddc5b978d851c2a96c6597d14eeaa12d Mon Sep 17 00:00:00 2001 From: midagedev Date: Sun, 30 Aug 2026 01:42:01 +0900 Subject: [PATCH 1/2] desktop: ship wailsapp/wails#6006 from a fork until it merges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wails' 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. There is a second mode in the same branch: the COM call can return S_OK without setting the out pointer, which makes `defer req.Release()` a nil-pointer release (wails#1103's field crash trace). The fix is ten lines and has been open upstream since 2026-08-19, unblocked — its one review comment was addressed and resolved the same day. It is waiting on a maintainer. gadak does not have to (user decision 2026-08-30). So: github.com/midagedev/wails, branch gadak/v3.0.0-beta.12 — the upstream tag with those two commits cherry-picked, nothing else — tagged v3.0.0-beta.12-gadak.1, and pinned here by a replace whose comment says which PR, why it is reachable in gadak, and to delete it when it merges. A replace with no expiry is how a fork becomes permanent by accident. Two measured facts are in docs/runbooks/upstream-pr.md, because this will happen again: the fork's go.mod module line stays as upstream's (Go resolves the package path from the original module path, so no import rewriting — the folklore answer would break every internal import), and the branch must be TAGGED or `go mod tidy` derives a pseudo-version from the nearest tag reachable in the fork, which read `v3.0.0-beta.9.0.2026…` for a branch cut from beta.12. Verified: go mod tidy, go build ./... (darwin), GOOS=windows go build ./..., go vet, gofmt — and the patched source is the one in the module cache. The linux cross-build fails from macOS for the same GTK/cgo reason it does on stock beta.12 (checked against the unmodified go.mod); the Linux desktop job is why this is a PR. Co-Authored-By: Claude Opus 5 --- desktop/go.mod | 20 +++++++++++++++ desktop/go.sum | 4 +-- docs/runbooks/upstream-pr.md | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) 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/docs/runbooks/upstream-pr.md b/docs/runbooks/upstream-pr.md index 659500db..7f735b4d 100644 --- a/docs/runbooks/upstream-pr.md +++ b/docs/runbooks/upstream-pr.md @@ -94,6 +94,55 @@ 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. + +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. From 88594aa34c0215ab856e17c2ef6975a328a8c6cd Mon Sep 17 00:00:00 2001 From: midagedev Date: Sun, 30 Aug 2026 01:53:14 +0900 Subject: [PATCH 2/2] desktop: teach the wails pin gate about a fork replace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestWailsModuleVersionMatchesGoMod compared debug.ReadBuildInfo()'s wails version against go.mod's require line. With the #6006 fork replace those legitimately differ: wailsModuleVersion() prefers Replace.Version by design — it reports what actually linked — while the require line keeps naming the upstream base the fork is cut from. So the gate went red on the commit that added the replace. Narrowed rather than relaxed. 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 fails with the reason spelled out. FAIL-first, both halves measured: the original failure is PR #78's "Desktop tests" job, and pointing the require line at beta.11 with the replace untouched reproduces the new branch. TestPinnedWailsVersionIsNamed is untouched — main.go, README.md and build-windows.ps1 still have to name the upstream base, which is the version whose behaviour those comments describe. Co-Authored-By: Claude Opus 5 --- desktop/platforms_test.go | 39 ++++++++++++++++++++++++++++++++++++ docs/runbooks/upstream-pr.md | 9 +++++++++ 2 files changed, 48 insertions(+) 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 7f735b4d..bf3c5d0d 100644 --- a/docs/runbooks/upstream-pr.md +++ b/docs/runbooks/upstream-pr.md @@ -135,6 +135,15 @@ Two things that are not obvious and cost a round if guessed: 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