refactor(#276): extract DashboardSession — phase 3b#282
Merged
Conversation
src/ui/dashboard/dashboard-session.ts is the route-scoped owner of the
dashboard execution runtime: the 6-way tile pool, wave generations
(reserved at wave CREATION, not worker start — the race-closing
invariant), per-slot AbortControllers, runFavoriteSource's
gate/stream/classify core, filter waves + curated-field merge +
persistence ports, runAffected's cascade, and retryFilter.
Constructible without App (issue rule 1); input is an explicit
DashboardRuntimeInput built by the shell from the favorites list, so a
stored DashboardDocument can replace that source later without touching
the session. Every DOM write stays in the shell behind injected hooks;
slots keep their route's own DOM nodes for now (session-state/shell-DOM
split deferred to Phase 5, documented). Tile supersede remains
client-abort only — never a server KILL (intentional, preserved).
renderDashboard(app) keeps its signature as the integration entry —
tests/unit/dashboard.test.ts passed UNMODIFIED, the strongest
behavior-preservation signal this extraction has.
destroy() aborts all in-flight tile/KPI/filter work, tears down chart
instances, disposes the filter bar, and flips a destroyed flag that
turns runAll/runAffected/retryFilter into no-ops (re-checked after the
token-preflight await too) — so an orphaned filter-bar debounce timer
firing post-teardown can never issue a request or trigger a sign-out.
buildFilterBar now returns { el, dispose() }; the dashboard shell and
the detached Data view both dispose the previous bar on re-render,
closing today's orphan-timer gap. Review round also added a
stale-generation guard on streamed progress: a superseded request's
last buffered chunk can no longer write into the newer wave's live
loading label (the pre-refactor closure-captured label was immune;
the guard restores that).
Gate: 106 files / 3090 tests green (dashboard suite untouched), build
green, check:arch green (ui/dashboard rules active: no ui/workbench,
no editor imports). e2e chromium+webkit fully green; firefox 40/40
serialized (parallel-run flake is the documented environment issue).
New session spec: 36 tests; dashboard-session.ts 100/94.9/100/100,
dashboard.ts 100/97.9/97.4/100, filter-bar.ts 100/100/100/100.
Part of #276 (phase 3b — completes Phase 3 with #281; roadmap #68
updated).
Claude-Session: ad39bf19-1690-43c7-abb4-f0084fca00a2
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Part of #276 — Phase 3b, completing Phase 3 with #281 (3a). PRs so far: #278 (0–1), #279 (2), #281 (3a).
DashboardSession(src/ui/dashboard/dashboard-session.ts, 36-test spec; 100/94.9/100/100) owns the dashboard execution runtime: the 6-way tile pool, wave generations reserved at wave creation (the race-closing invariant the DOM suite asserts to the exact request count), per-slotAbortControllers,runFavoriteSource's gate/stream/classify core, filter waves + curated-field merge (persistence via injected ports),runAffected's cascade, andretryFilter. Constructible withoutApp; the input is an explicitDashboardRuntimeInputbuilt by the shell from the favorites list — a storedDashboardDocumentcan replace that source later without touching the session (issue §4 requirement). Every DOM write stays in the shell behind hooks; slots keep their route's own DOM for now (session-state/shell-DOM split deferred to Phase 5 — deliberate, documented in the module header). Tile supersede remains client-abort only, never a serverKILL(intentional, preserved).Strongest signal:
renderDashboard(app)keeps its signature as the integration entry andtests/unit/dashboard.test.tspassed unmodified — every streaming/race/filter-wave/recents behavior pin holds against the extracted runtime.Teardown (issue acceptance: "tests prove cleanup"):
destroy()aborts all in-flight tile/KPI/filter work, tears down chart instances, disposes the filter bar, and flips adestroyedflag makingrunAll/runAffected/retryFilterno-ops (re-checked after the token-preflightawait) — an orphaned filter-bar debounce timer firing post-teardown can never issue a request or trigger a sign-out.buildFilterBarnow returns{ el, dispose() }; both the dashboard shell and the detached Data view dispose the previous bar on re-render, closing a real pre-existing orphan-timer gap. LikeWorkbenchSession,destroy()has no production caller until Phase 5's route shells (the dashboard is its own tab today) — documented.Review round (3 angles + e2e): the port-fidelity angle caught a genuine regression — with progress routed through the live
slot.loadLabel, a superseded request's last buffered chunk could corrupt the newer wave's label (the old closure-captured label was immune). Fixed with a stale-generation guard before the progress hook + a unit test modelling the buffered-chunk-after-abort window. Also fixed: destroy-during-preflight window, two stale doc comments. Cross-file + conventions angles otherwise clean.Verification: full gate green (106 files / 3,090 tests + tsc + check:arch,
ui/dashboardrules active), build green, e2e chromium+webkit fully green, firefox 40/40 serialized (parallel-run timeouts are this machine's documented flake).Acceptance criteria this completes (with #281): Workbench and Dashboard have separate route-scoped sessions and cancellation state ✅; both expose
destroy()with proven cleanup ✅; route sessions do not import one another (enforced) ✅; dashboard runtime input is an explicit interface, not a favorites-list assumption ✅.Checklist
npm testpasses (the per-file coverage gate is non-negotiable)npm run buildsucceeds (single-filedist/sql.html)src/core/, network insrc/net/(injected fetch), DOM insrc/ui/CHANGELOG.md([Unreleased]) updated if behavior or the deployed surface changed🤖 Generated with Claude Code