fix(shell): dismiss the wizard when a library exists #234

Open
logan wants to merge 1 commits from fix/175-wizard-follows-the-library into main
Collaborator

The issue

first-run-wizard dismissed itself as a step in its own flow —
handleFinish set finished, closed the dialog and cleared active
so a library appearing by any other route left the modal up over an app
that was already set up, intercepting every pointer event (#175).

What changed

One subscription. AddLibrary emits LibraryAdded whoever calls it,
so the wizard now waits on the state it exists to wait for:

  • EventsOn(Events.LibraryAdded, …) in connectedCallback, cancelled
    in disconnectedCallback.
  • It is registered before the initial GetAllLibrariesWithTrackCounts,
    and the result of that read is discarded if the event landed while it
    was in flight — otherwise a stale empty list raises the wizard a
    moment after the library arrived.
  • Both routes out end in one dismiss(), which sets finished first
    because preventClose cancels the hide otherwise.

On the Direction's first question

#175 asks whether a user can reach this before it asks what to do, and
then says the dismissal should follow the library existing either
way
. That is what made it takeable without settling the question: the
answer moves the priority, not the diff. What the code says, for
whoever does settle it:

  • There are exactly two frontend callers of AddLibrary — this wizard
    and Settings' Add Library.
  • Measured on the real app while the wizard is up,
    document.elementFromPoint(550, 400) is FIRST-RUN-WIZARD; the
    wa-dialog is a native modal, which is what makes Settings
    unreachable.

So on desktop it reads as robustness rather than a lockout. Android's
storage-access flow is the case that needs a device, and nothing here
depends on it.

Verification

  • make ui-test — 104 files, 1135 tests, green.
    frontend/test/components/first-run-wizard.test.ts is new: shows on a
    fresh install; stays hidden when a library already exists; dismisses
    on LibraryAdded; does not raise itself when the event lands during
    the initial read; still dismisses through its own Get Started
    button (that last one pinning the handleFinishdismiss()
    refactor).
  • Planted, not read. With the component reverted the two
    new-behaviour cases fail (expected true to be false) and the three
    pinning existing behaviour pass.
  • The real app, on a fresh YJ_HOME (make dev-headless-fresh,
    port checked free first), driven with this issue's own reproduction —
    __yjEvents.call('library.Library.AddLibrary', [fixture library]).
    Before: dialogRendered: true, nativeDialogOpen: true,
    elementFromPoint → FIRST-RUN-WIZARD, all unchanged after the call.
    After: the dialog is gone and that point is HOME-VIEW. Two builds
    from this tree, one with the source change stashed.
  • make e2e (chromium, 246 passed) against
    make dev-headless SEED=default, as a regression check only: every
    e2e sandbox has a library by construction — global-setup.ts refuses
    to start without one — so no spec there can reach the wizard's active
    state. The fresh-sandbox probe above is what covers it. WebKit is
    CI's tier, not one this machine can run.
  • npx tsc --noEmit clean; check-css-literals and
    check-css-nesting clean.

Deliberately not done

  • No Go, no .sql, no .templ, no bound signature, so make lint,
    make test, make generate and make bindings were not run.
  • CLAUDE.md untouched. It says of this component only that it is
    eagerly mounted because it is the first paint, which is still true.
  • Only LibraryAdded is subscribed. LibraryRemoved taking the
    last library away does not raise the wizard mid-session; that is the
    existing behaviour and not what #175 reports.
  • No new issues filed — nothing was found that was not this one.

Closes #175

## The issue `first-run-wizard` dismissed itself as a step in its **own** flow — `handleFinish` set `finished`, closed the dialog and cleared `active` — so a library appearing by any other route left the modal up over an app that was already set up, intercepting every pointer event (#175). ## What changed One subscription. `AddLibrary` emits `LibraryAdded` whoever calls it, so the wizard now waits on the state it exists to wait for: - `EventsOn(Events.LibraryAdded, …)` in `connectedCallback`, cancelled in `disconnectedCallback`. - It is registered **before** the initial `GetAllLibrariesWithTrackCounts`, and the result of that read is discarded if the event landed while it was in flight — otherwise a stale empty list raises the wizard a moment after the library arrived. - Both routes out end in one `dismiss()`, which sets `finished` *first* because `preventClose` cancels the hide otherwise. ## On the Direction's first question #175 asks whether a user can reach this before it asks what to do, and then says the dismissal should follow the library existing **either way**. That is what made it takeable without settling the question: the answer moves the priority, not the diff. What the code says, for whoever does settle it: - There are exactly two frontend callers of `AddLibrary` — this wizard and Settings' *Add Library*. - Measured on the real app while the wizard is up, `document.elementFromPoint(550, 400)` is `FIRST-RUN-WIZARD`; the `wa-dialog` is a native modal, which is what makes Settings unreachable. So on desktop it reads as robustness rather than a lockout. Android's storage-access flow is the case that needs a device, and nothing here depends on it. ## Verification - **`make ui-test`** — 104 files, 1135 tests, green. `frontend/test/components/first-run-wizard.test.ts` is new: shows on a fresh install; stays hidden when a library already exists; dismisses on `LibraryAdded`; does not raise itself when the event lands during the initial read; still dismisses through its own *Get Started* button (that last one pinning the `handleFinish` → `dismiss()` refactor). - **Planted, not read.** With the component reverted the two new-behaviour cases fail (`expected true to be false`) and the three pinning existing behaviour pass. - **The real app, on a fresh `YJ_HOME`** (`make dev-headless-fresh`, port checked free first), driven with this issue's own reproduction — `__yjEvents.call('library.Library.AddLibrary', [fixture library])`. Before: `dialogRendered: true`, `nativeDialogOpen: true`, `elementFromPoint → FIRST-RUN-WIZARD`, all unchanged after the call. After: the dialog is gone and that point is `HOME-VIEW`. Two builds from this tree, one with the source change stashed. - **`make e2e`** (chromium, 246 passed) against `make dev-headless SEED=default`, as a regression check only: every e2e sandbox has a library by construction — `global-setup.ts` refuses to start without one — so no spec there can reach the wizard's active state. The fresh-sandbox probe above is what covers it. WebKit is CI's tier, not one this machine can run. - `npx tsc --noEmit` clean; `check-css-literals` and `check-css-nesting` clean. ## Deliberately not done - **No Go, no `.sql`, no `.templ`, no bound signature**, so `make lint`, `make test`, `make generate` and `make bindings` were not run. - **`CLAUDE.md` untouched.** It says of this component only that it is eagerly mounted because it is the first paint, which is still true. - **Only `LibraryAdded` is subscribed.** `LibraryRemoved` taking the last library away does not raise the wizard mid-session; that is the existing behaviour and not what #175 reports. - No new issues filed — nothing was found that was not this one. Closes #175
logan added 1 commit 2026-08-30 10:41:14 +00:00
fix(shell): dismiss the wizard when a library exists
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m58s
CI / e2e (pull_request) Successful in 10m25s
b9e60bdb0a
The first-run wizard dismissed itself as a step in its own flow, so a
library appearing by any other route left a full-screen modal up over
an app that was already set up — intercepting every pointer event,
which is also what keeps Settings out of reach while it is there.

AddLibrary emits LibraryAdded whoever calls it, so one subscription
makes the dismissal follow the state the wizard exists to wait for
rather than the button being pressed. It is registered before the
initial read, or a library arriving while that call is in flight is
answered with a stale empty list; both routes out now end in one
dismiss(), which sets `finished` before asking the dialog to close
because preventClose cancels the hide otherwise.

Closes #175
Author
Collaborator

CI green on b9e60bd — run 601, both required jobs.

  • check (job 29915): commit messages, lint, test, frontend typecheck,
    CSS literals, the component and store suite, bindings, skill-check —
    every step success.
  • e2e (job 29916): chromium and webkit both success. Worth naming
    because the WebKit step is the one that gets skipped when chromium
    fails, and it is the only signal for the renderer that actually
    ships.

Not merged, per the run's instructions.

**CI green on `b9e60bd`** — run 601, both required jobs. - `check` (job 29915): commit messages, lint, test, frontend typecheck, CSS literals, the component and store suite, bindings, skill-check — every step success. - `e2e` (job 29916): **chromium and webkit both success.** Worth naming because the WebKit step is the one that gets skipped when chromium fails, and it is the only signal for the renderer that actually ships. Not merged, per the run's instructions.
All checks were successful
CI / check (push) Skipped
Required
CI / e2e (push) Skipped
Required
CI / check (pull_request) Successful in 2m58s
Required
Details
CI / e2e (pull_request) Successful in 10m25s
Required
Details
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/175-wizard-follows-the-library:fix/175-wizard-follows-the-library
git checkout fix/175-wizard-follows-the-library
Sign in to join this conversation.