Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions .wiki/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,19 @@ main.js (bootstrap + concrete adapters)
ui/ → net/state/core net/ → core core/ → nothing
```

`src/main.js` is the composition root. `createApp(env)` receives browser and
service dependencies. Render modules receive the returned `app` controller and
must not import `app.js`, which prevents cycles.
`src/main.js` bootstraps the app; `createApp(env)` in `src/ui/app.js` is the
composition root, receiving browser and service dependencies and returning the
`app` controller every render module addresses. Render modules must not import
`app.js`, which prevents cycles. `createApp` builds `app` via one typed object
literal with no `as App` cast — a member missing from construction is a `tsc`
error, not a runtime hole (#588). Four responsibilities that used to live
entirely inside `createApp` are now their own modules the composition root
wires up: workspace persistence/cross-tab sync
(`src/application/workspace-session.js`), `/sql` routing and main-surface
navigation (`src/application/surface-navigation.js`), the Workbench variable
strip (`src/ui/workbench/variable-strip.js`), and the save/conflict cluster
(`src/ui/workbench/save-controller.js`) — `src/application/*` may never import
`src/ui/`, mechanically enforced by `build/check-boundaries.mjs`.

## Side-effect seams

Expand Down
7 changes: 6 additions & 1 deletion .wiki/Source-Map.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ Back to [[Home]]. Related: [[Architecture]], [[Product-and-Features]].
| Path | Role |
|---|---|
| `src/main.js` | browser bootstrap and concrete adapter injection |
| `src/ui/app.js` | controller, actions, orchestration, render entry |
| `src/ui/app.js` | controller, actions, orchestration, render entry (composition root; shrunk by #588 — see below) |
| `src/application/workspace-session.js` | workspace write queue, cross-tab BroadcastChannel sync, refresh scheduling, `beforeunload` guard (#588) |
| `src/application/surface-navigation.js` | `/sql` routing, main-surface (Query↔Dashboard) navigation (#588) |
| `src/ui/workbench/variable-strip.js` | Workbench variable strip render + run-button sync (#588) |
| `src/ui/workbench/save-controller.js` | saved-query save/conflict/reload cluster (#588) |
| `src/ui/keyboard-owner.js` | shared keyboard-owner acquire/release channel (#588) |
| `src/state.js` | signals-backed state model and persistence operations |
| `src/net/ch-client.js` | ClickHouse HTTP execution and schema calls |
| `src/net/oauth.js` | OAuth flow/token exchange |
Expand Down
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,44 @@ auto-generated per-PR notes; this file is the curated, human-readable history.
of a wrong number. No behavior change for well-formed persisted data; no
change to `editorPct`/`sideSplitPct`/`cellDrawerPx`/`docPanePx` numeric
clamping, `clamp` itself, or `decodeStoredSavedQueries`.
- **Decomposed the `createApp` composition root along four extraction seams,
plus typed staged construction** (#588, phase 4 of the #593 refactor
umbrella). `src/ui/app.ts` (`createApp`) shrank from ~3,246 to ~2,226 lines
and the flat `App` interface from 112 to 105 required members, via five
sequential, gate-green extractions: (1) `renderVarStrip`/`setRunBtn` →
`src/ui/workbench/variable-strip.ts` (a new sibling controller, not
`variable-bar.ts` — that module's `VariableBarApp` port is deliberately
adapter-facing with neutral names, and the Workbench's own state doesn't
fit it); (2) `anchoredPopover` promoted into `src/ui/popover.ts` beside the
existing modal `openAnchoredDialog`, the save cluster
(`updateSaveBtn`/`saveActiveQuery`/`openConflictChooser`/…) into
`src/ui/workbench/save-controller.ts`, and the three copy-pasted
`keyboardOwnerChannel` implementations (`file-menu.ts`/
`library-assign-menu.ts`/`dashboard.ts`) hoisted into one
`src/ui/keyboard-owner.ts`; (3) workspace persistence, cross-tab
BroadcastChannel sync, refresh scheduling, and the `beforeunload` guard
into `src/application/workspace-session.ts` (queueing/tokens/broadcast/
listeners only — `applyCommittedWorkspace` stays in `app.ts` since it does
real UI orchestration, not "zero DOM" as originally scoped); (4) routing
and main-surface navigation into `src/application/surface-navigation.ts`,
with `SurfaceCommandPort`/`DashboardFocusOutcome`/`WorkspaceRouteStatus`
relocated to `src/application/main-surface.ts` so the new
`src/application/*` modules never import `src/ui/` (mechanically checked
by `check:arch`, including type-only imports); (5) the `appBase:
Partial<App>` + `as App` cast replaced by one late-bound object literal —
a forgotten member assignment is now a `tsc` compile error instead of a
runtime hole (caught one for real: `App.editingLibrary` had never been
initialized and was silently reading `undefined`). All four extractions
are pure refactors — one pre-existing defect is deliberately **not**
fixed: `anchoredPopover`'s stale `close()` can clobber a newer popover
sharing the same `dom` ref slot (documented in `popover.ts` and pinned by
a characterization test; tracked separately, not part of this phase).
`openSavePopover`, `handleSqlPopState`, `focusDashboardMember`,
`syncSqlRoute`, `rewriteWorkspaceRoute`, `sourceTabId`, `documentVisible`,
`getLastCommittedToken`, `serializeWrite`, `flushWorkspaceWrites`, and
`refreshWorkspaceFromStore` are gone from the flat `App` bag (repointed to
`app.nav.*`/`app.workspaceSession.*` at every production consumer); `App`
gains `nav`/`workspaceSession`.

### Changed
- **The project wiki moved in-repo, as tracked `.wiki/`.** The maintainer/agent
Expand Down
46 changes: 46 additions & 0 deletions src/application/main-surface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,52 @@ export type DashboardFocusTarget =
* authorization boundary (ADR-0003). */
export type DashboardSurfaceMode = 'view' | 'edit';

/** `App['workspaceRouteStatus']`'s canonical declaration (#588 phase 4 §3-T #3
* — moved here from `src/ui/shortcuts.ts`'s inline union so wave 3's
* `workspace-session.ts` and wave 4's `surface-navigation.ts` both have one
* real import instead of independently-copied inline unions). `app.types.ts`
* and `shortcuts.ts` both import this now; `workspace-session.ts` (wave 3)
* rewires its local placeholder copy onto this import in the same change. */
export type WorkspaceRouteStatus = 'loading' | 'ready' | 'not-found' | 'error';

/** Local copy (#588 phase 4 §3-T #2 — moved here from `src/ui/shortcuts.ts`),
* not an import of the canonical `dashboard-viewer-session.ts` one: this
* module may not import `src/dashboard/**` (the reverse would be the wrong
* dependency direction), and `shortcuts.ts`'s own pre-move copy made the same
* trade — filed as a later unification candidate (#588 phase 4 plan §9-5). */
type DashboardStyle = 'grid' | 'full' | 'report' | 'columns-2' | 'columns-3';

/**
* What an IN-PLACE member navigation could do (#426). Three outcomes, because
* two of them are not failures:
* - `ok` — delivered against the live surface; no rebuild happened.
* - `pending` — not deliverable in place *right now* (the opening wave has not
* settled, so a curated filter's control is about to be replaced;
* or this port has been superseded). The caller falls back to the
* normal render transition, which delivers focus at the
* deterministic point the node exists. NOT a diagnostic.
* - `missing` — the member is genuinely not on this Dashboard any more. The
* caller reports it non-destructively and changes nothing.
*
* (#588 phase 4 §3-T #2 — moved here from `src/ui/shortcuts.ts`, which now
* re-exports it so existing importers keep compiling unchanged.)
*/
export type DashboardFocusOutcome = 'ok' | 'pending' | 'missing';

/** (#588 phase 4 §3-T #2 — moved here from `src/ui/shortcuts.ts`, alongside
* `DashboardFocusOutcome`; see that type's own doc comment.) */
export interface SurfaceCommandPort {
surface: 'dashboard';
generation: number;
refresh(): void;
setDashboardStyle(style: DashboardStyle): void;
/** #426 — scroll/focus/highlight one already-rendered tile or curated filter
* WITHOUT rebuilding or re-running the Dashboard. Repeated same-Dashboard
* member navigation is a normal tree operation, so it must not cost a render
* or a history entry. */
focusMember(member: DashboardFocusTarget): DashboardFocusOutcome;
}

/**
* #426 splits what #425 carried as one `focus` field into two independent facts,
* because the Dashboard tree needs to distinguish them:
Expand Down
Loading