docs(plan): record what Phase 5 landed and what it found
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDCbcCZQepnpSQYJ6SxxZm
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
# 009 — Wails v3 migration
|
# 009 — Wails v3 migration
|
||||||
|
|
||||||
**Status:** Phases 0–4 complete. The Go side is entirely on v3 and the
|
**Status:** Phases 0–5 complete. The Go side is entirely on v3, the
|
||||||
frontend now imports real v3 bindings: `go build .` and
|
frontend imports real v3 bindings, and the component tier is green:
|
||||||
`pnpm build` both succeed, `make bindings-check` passes, and `src/`
|
`go build .` and `pnpm build` both succeed, `make bindings-check`
|
||||||
typechecks clean. **Phase 5 (the Vitest fake) is next, and until it and
|
passes, `tsc --noEmit` is clean, and the 757 Vitest tests pass across
|
||||||
Phase 6 land the branch is not mergeable** — `make ui-test` and
|
all 63 files. **Phase 6 (the e2e harness) is next, and until it lands
|
||||||
`make e2e` both drive a harness written against `window.go`, which v3
|
the branch is not mergeable** — `.playwright/init-events.js` and
|
||||||
|
`e2e/perf/measure.mjs` are both written against `window.go`, which v3
|
||||||
does not have.
|
does not have.
|
||||||
**Branch:** `wails-v3`, off `main` at `edb13a6`.
|
**Branch:** `wails-v3`, off `main` at `edb13a6`.
|
||||||
**Created:** 2026-08-13
|
**Created:** 2026-08-13
|
||||||
@@ -693,6 +694,93 @@ that needs changing is evidence the fake is wrong, not the test.
|
|||||||
**Est.** One session. This is where the official estimate stops
|
**Est.** One session. This is where the official estimate stops
|
||||||
applying.
|
applying.
|
||||||
|
|
||||||
|
### Phase 5 — what actually landed
|
||||||
|
|
||||||
|
**757 tests pass across all 63 files, and one test file changed.** The
|
||||||
|
plan's design survived and got smaller, because v3 has a seam v2 did
|
||||||
|
not.
|
||||||
|
|
||||||
|
**`setTransport()` is the whole fake.** v3 routes *every* runtime call
|
||||||
|
— bindings, event emits, window, dialogs, clipboard, screens — through
|
||||||
|
one IPC transport, and replacing it is public, documented API. So the
|
||||||
|
fake covers strictly more than v2's two globals did while being
|
||||||
|
shorter, and the tests still exercise the real generated bindings, the
|
||||||
|
real runtime and the real store code.
|
||||||
|
|
||||||
|
**The dispatcher is deleted rather than re-derived.** The plan said to
|
||||||
|
re-derive `Listener`/`notify()` against v3 instead of porting them; the
|
||||||
|
better answer is that neither is needed. `emit()` goes through
|
||||||
|
`window._wails.dispatchWailsEvent`, the exact entry point the backend's
|
||||||
|
push uses, so delivery, `maxCallbacks` expiry and the post-dispatch
|
||||||
|
filter are the runtime's own code; registration and unregistration are
|
||||||
|
`Events.OnMultiple` / `Off` / `OffAll`. What *is* mirrored is one line
|
||||||
|
of Go — how `EventManager.Emit` packs variadic data into an event's
|
||||||
|
single `data` field (none is null, one is the value, more is the
|
||||||
|
slice), which is invisible when wrong and shows up as a store reading
|
||||||
|
`undefined` off its payload.
|
||||||
|
|
||||||
|
One thing stayed non-public: the listener registry, for
|
||||||
|
`listenerNames()`. `listener.js` has no entry in the package's exports
|
||||||
|
map, so `vitest.config.mts` aliases it. It buys the one question the
|
||||||
|
public surface cannot answer — did importing a store subscribe it —
|
||||||
|
and if Wails moves the file the import throws at setup, which is loud.
|
||||||
|
|
||||||
|
**A binding carries an ID, not a name, and the map has to be
|
||||||
|
complete.** `$Call.ByID(2822423495)` is FNV-1a over
|
||||||
|
`yellowjacket/backend/home.Service.GetShelves`, so the fake computes the
|
||||||
|
same hash — deriving the FQN from the generated tree rather than
|
||||||
|
writing it down. The Go type's casing survives in exactly one place,
|
||||||
|
each package's `index.ts` (`export { Library }`); the filename cannot
|
||||||
|
tell you `frontendutil.ts` is `FrontendUtil`. Building the map lazily
|
||||||
|
as paths are mentioned does not work: 21 assertions read `calls()` with
|
||||||
|
no argument and compare the whole list of paths, including methods no
|
||||||
|
test stubs. An unmapped ID records as `#<id>`, which fails the
|
||||||
|
assertion naming it.
|
||||||
|
|
||||||
|
**Two things had to change that are not the fake**, and both are
|
||||||
|
findings rather than accommodations:
|
||||||
|
|
||||||
|
- **`fixture()` drains microtasks between two renders.** A v3 binding
|
||||||
|
settles several hops later than v2's — `Call()`, an async
|
||||||
|
`runtimeCallWithID`, the transport, a `CancellablePromise`, against
|
||||||
|
v2's one resolved promise — and the tests were already written as
|
||||||
|
though `fixture()` meant "mounted *and loaded*". Fixing it there
|
||||||
|
rather than in each test is what kept this to one test-file edit.
|
||||||
|
Microtasks and **not** `setTimeout`: the first attempt used a timer,
|
||||||
|
which hung `transport.test.ts` for 45 s because it installs fake ones.
|
||||||
|
- **`tracklist-store` keeps its defaults on an empty answer.**
|
||||||
|
`GetTrackListColumns` substitutes `tracklist.DefaultColumns` only when
|
||||||
|
the whole config section is missing — a section that exists with no
|
||||||
|
columns returns nothing, and a track list with no columns is not what
|
||||||
|
that means. Until v3 this was accidental: the binding was typed
|
||||||
|
`Column[]`, an absent answer arrived as `undefined`, and `.map` threw
|
||||||
|
into the `catch` that restores the defaults. Phase 4's `list()` turned
|
||||||
|
that into an honest empty list and the accident stopped working.
|
||||||
|
|
||||||
|
**The one test file edited was `harness.test.ts`, and it was asserting
|
||||||
|
something no longer true.** v2's `EventsEmit` notified in-page listeners
|
||||||
|
*before* Go, so a frontend emit was observable synchronously. v3's
|
||||||
|
`Events.Emit` does not touch the local registry at all: it calls the
|
||||||
|
backend, and `EventProcessor.Emit` sends the event back out to every
|
||||||
|
window, including the emitting one. The page still sees its own emit,
|
||||||
|
one round trip later. The test says that now, and the fake reproduces
|
||||||
|
it with a microtask. That is the "re-derive, don't port" risk paying
|
||||||
|
off — ported blindly, this would have looked like a store bug.
|
||||||
|
|
||||||
|
**`make ui-test` still cannot complete in one run on this machine, and
|
||||||
|
that is not this migration.** A single browser session dies partway
|
||||||
|
through the 58 files it queues, with "Cannot connect to the iframe"
|
||||||
|
after ~5 s. It reproduces **unchanged at `c9905fb`**, the commit before
|
||||||
|
Phase 4 — checked in a worktree, not assumed — so it is a resource
|
||||||
|
limit here (6 GB available, 9 GB already in swap), not a regression.
|
||||||
|
Run in batches of six it is 757 passed, 0 failed. If CI is green on the
|
||||||
|
single run, nothing needs doing; if it is not, that is a pre-existing
|
||||||
|
problem to file separately rather than something Phase 5 introduced.
|
||||||
|
|
||||||
|
**Not verified: `make ui-visual`.** Screenshot baselines only mean
|
||||||
|
anything on the machine that recorded them, and nothing here changes
|
||||||
|
what a component renders.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 6 — E2E harness and testctl
|
## Phase 6 — E2E harness and testctl
|
||||||
@@ -822,7 +910,8 @@ Listed so nobody smuggles them into the port and calls it a migration.
|
|||||||
| Binding generation analyses the wrong build config | ~~high~~ | **Retired.** Default tags *are* the shipped configuration since Phase 1; recorded in `scripts/bindings-check.sh` |
|
| Binding generation analyses the wrong build config | ~~high~~ | **Retired.** Default tags *are* the shipped configuration since Phase 1; recorded in `scripts/bindings-check.sh` |
|
||||||
| Beta churn mid-migration | high | Pin `beta.8`. `beta.3`→`beta.8` in one app's lifetime |
|
| Beta churn mid-migration | high | Pin `beta.8`. `beta.3`→`beta.8` in one app's lifetime |
|
||||||
| E2E rewrite silently weakens coverage | high | Acceptance = `make e2e` green, **zero spec edits** |
|
| E2E rewrite silently weakens coverage | high | Acceptance = `make e2e` green, **zero spec edits** |
|
||||||
| v3 event ordering differs from v2's | **high** | *Confirmed, worse than assumed.* v3's frontend `Events.Emit` does not notify in-page listeners at all; `harness.test.ts` asserts that it does |
|
| `make ui-test` cannot complete in one browser session here | low | *New.* Pre-existing — reproduces at `c9905fb`. A resource limit on this machine; batched runs are green. Watch CI |
|
||||||
|
| v3 event ordering differs from v2's | ~~high~~ | **Retired.** Confirmed and handled in Phase 5: v3's frontend `Events.Emit` round-trips through Go instead of notifying locally first. One test asserted the old behaviour and now asserts the new |
|
||||||
| `ServiceShutdown()` signature trap | medium | Silent no-call; grep after Phase 2c |
|
| `ServiceShutdown()` signature trap | medium | Silent no-call; grep after Phase 2c |
|
||||||
| Quit-during-writes veto breaks | medium | Data-safety path; test deliberately in 2d |
|
| Quit-during-writes veto breaks | medium | Data-safety path; test deliberately in 2d |
|
||||||
| Regression no tier covers | medium | `make perf` before/after on the same seed |
|
| Regression no tier covers | medium | `make perf` before/after on the same seed |
|
||||||
|
|||||||
Reference in New Issue
Block a user