Skip to content

refactor(web): feature state moves into its feature; outside code imports only root entries - #502

Merged
huyplb merged 1 commit into
mainfrom
refactor/web-feature-state
Oct 10, 2026
Merged

huyplb merged 1 commit into
mainfrom
refactor/web-feature-state

Conversation

@huyplb

@huyplb huyplb commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Step 4 of the feature-architecture schedule (docs/plans/2026-10-10-feature-architecture.md).

What moves

  • Compare becomes a frontend feature (features/compare/):
    • the sync store (useSyncStore, plus syncHelpers and syncTypes, renamed from kebab-case)
    • all of object-detail (used only by the Compare shell)
    • the connection chips, sameDatabase and the workspace
  • The SQL editor store and its slices move to features/sql-editor/state/.
  • monaco-setup.ts moves into sql-editor (lib/monacoSetup.ts, public as monaco.ts).
  • relativeTime and the generic Access form controls move to shared/.

Public API = root files

Outside a feature, code imports only the files at its root:

Entry Holds
index.ts Light enough for the first screen: state, helpers, and load… functions for lazy components
view.ts The workspace
toolbar.ts What the view adds to the top toolbar
ui.ts Heavier components others compose (schema-diff renderers; sql-editor tree, write dialog, file imports, clone-table SQL)
state.ts The SQL editor store, which Home and the palette must not load

Measured, not assumed: one barrel import from the eager toolbar took the first visit from 141.5 to 195.7 KB gzip. Rolldown does not drop unused re-exports here.

Toolbar from the registry

A view declares toolbar: { start, end, below }. The shell keeps the activity indicator and the command palette. The DOM and test IDs are unchanged (same ID set in the catalog). One difference: the connection modal now unmounts when you leave Compare, and it can only be opened from there.

Boundary test

architecture.test.ts now resolves relative paths too (../../features/x was invisible before). It enforces:

  • outside a feature, only its root files may be imported (vi.mock is exempt)
  • no static import cycle may cross a feature boundary (the frontend has zero cycles today)
  • shared/ never imports a feature

Verification

  • tsc --noEmit: 0 errors. ESLint and security lint: clean.
  • vitest run: 6071 passed (495 files).
  • npm run test-ids: the same 1717 IDs.
  • A/B for every new test: each new rule failed on a planted violation and passed when restored, and the toolbar order and registry wiring tests failed when broken.
  • Build: no INEFFECTIVE_DYNAMIC_IMPORT. First visit is 144.1 KB gzip (budget 170), up from 141.5 KB, across 21 files instead of 10. The modules are the same: Rolldown now splits more of what the entry shares with lazy chunks (uiStore, recentQueries, a few icons). Two probes (passing the toolbar as a prop, moving the editor loaders out of index.ts) did not change that.
  • Live e2e on the dev server:
    • smoke, lazy-loading, schema browse/history/revert, and the SQLite SQL editor, utilities, access, workspaces, workflow, admin and feedback suites: green
    • with a Postgres container: compare-flow 11/11, git-migrations 5/5, peek row form 2/2
    • not run: backup-restore-ui (needs SQL Server)

🤖 Generated with Claude Code


Note

Medium Risk
Large structural refactor across Compare, SQL editor, and shell wiring; behavior is heavily tested but mistakes in store/toolbar boundaries could break schema compare, migrations, or first-load bundles.

Overview
Refactors the web frontend around feature-owned state and a registry-driven toolbar, so the shell no longer embeds Compare/Snapshots controls directly.

Compare becomes a first-class feature: useSyncStore and helpers move out of app/store, former object-detail UI is folded in, and Compare/Sync workspace loads via features/compare/view. Compare and Snapshots register toolbar.ts entries (start / end / below); TopToolbar only renders shell chrome (activity + ⌘K) plus those slots. Compare toolbar behavior is unchanged in substance but lives in CompareToolbar.tsx.

Cross-feature imports are narrowed to feature root entries (index.ts, view.ts, toolbar.ts, ui.ts, state.ts) with lazy load… helpers instead of barrel re-exports that pulled Monaco into the first paint. SQL editor store and recent-queries mirror move under features/sql-editor/state; shared bits (relativeTime, access form controls) move to shared/. architecture.test.ts now resolves paths, blocks deep feature imports, and forbids static import cycles across features.

E2e test IDs are regrouped under a compare namespace matching the new component ownership.

Reviewed by Cursor Bugbot for commit 4523a79. Bugbot is set up for automated code reviews on this repo. Configure here.

…orts only root entries

Compare becomes a frontend feature. It takes the sync store (`app/store/useSyncStore`,
`sync-helpers` → `syncHelpers`, `sync-types` → `syncTypes`), all of `object-detail` (used
only by the Compare shell), the connection chips, `sameDatabase` and the workspace.
The SQL editor store and its slices move to `features/sql-editor/state/`.

A feature's public API is now the files at its root. `index.ts` stays light enough for
the first screen; heavier pieces get their own entry:
- `ui.ts`: schema-diff renderers; sql-editor components and clone-table SQL
- `state.ts`: the SQL editor store, which Home and the palette must not load
- `monaco.ts`: the Monaco setup, moved from the frontend root
- `view.ts` / `toolbar.ts`: shell entries

Lazy components are `load…` functions in `index.ts`: auth screens, connections dialogs,
migration history, the admin panel and the SQL editors.

The top toolbar is registry-driven. A view declares `toolbar: { start, end, below }`;
the shell keeps the activity indicator and the command palette. Compare's controls are
`CompareToolbar` in the feature, and Snapshots' history bar is `HistoryToolbar`. The DOM
and test IDs are unchanged.

`architecture.test.ts` now resolves relative specifiers too. It enforces:
- outside a feature, only its root files may be imported (`vi.mock` is exempt)
- no static import cycle may cross a feature boundary
- `shared/` never imports a feature (this caught `shared/api/bodyless-requests.test.ts`
  importing a migrations API; its migration cases moved to
  `features/migrations/api/migrationApi.test.ts`)

`relativeTime` and the generic Access form controls move to `shared/`. RISK_STYLE stays
with its one user.

Three naming exceptions are gone: `sync-types`, `sync-helpers` and `monaco-setup`.

First visit: 144.1 KB gzip, up from 141.5 KB, across 21 files instead of 10. The modules
are the same; Rolldown splits more of what the entry shares with lazy chunks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 1fb6e203-39c4-4e7a-88b2-c432a626f3fe)

@huyplb
huyplb merged commit 2ba1c42 into main Oct 10, 2026
13 checks passed
@huyplb
huyplb deleted the refactor/web-feature-state branch October 10, 2026 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant