feat(workbench): route the shell through the PlatformPort, not the router (0406) - #642
Merged
Conversation
…uter Migrates all 29 workbench modules and 20 app-land consumers off direct @tanstack/react-router imports onto the PlatformPort (0406 phase 1). The shell now states intent — open this node, go home, this path — and the web host resolves it to a URL. Web behaviour is unchanged. What moved where: - navigation.ts collapses to intent emission; the node->route switch lives in the host's routeForTarget, where a missing entry is a compile error instead of a silent dead click. - Preview-tab arming (0284) moves into the host's navigate, extracted as armPreviewIntent so its semantics stay pinned by tests through the move. - navigateToNewDoc gains a port-shaped sibling, retiring the `as unknown as NavigateLike` casts the compiler could not see into. - The port grows search read/write (settings sections, /ai?q=) and a richer Link (drag sources, testids) — requirements the survey found, not guesses. - An ESLint no-restricted-imports override turns any future router import in the workbench into a red build; verified against a probe file. - test-platform.tsx replaces router module mocks in tests: components stopped importing the router, so tests stop mocking it and provide a port. Verified by driving the live app: doc creation, settings ?section round-trip (write and read), working-set opens, profile-menu settings, history back/forward — zero console errors. One palette quirk (Enter on "Go to tasks" under synthetic input) reproduced identically on the pre-migration baseline via stash, so it is pre-existing, not a regression. 992 web tests green, typecheck clean, lint 0 errors. Signed-off-by: xNet Test <test@xnet.dev>
Contributor
|
✓ Changelog fragment found — thanks! |
Contributor
🖼️ UI changes in this PRScreensInteractionsAuto-captured by CI · run. Informational — not a blocking check. |
Contributor
|
Preview removed for PR #642. |
… works again The editor-ux CI failure was real: the home surface's New → Page silently did nothing. routes/index.tsx still called the router-shaped navigateToNewDoc with the port's navigate behind `as unknown as NavigateLike` — the cast the compiler cannot see into, so the migration's typecheck sweep never surfaced it. At runtime the port received a router-shaped object with no `kind`, resolved no route, and dropped the click. Point routes/index.tsx at the workbench's port-shaped navigateToNewDoc, and delete the router-shaped original plus NavigateLike from lib/doc-creation — they had no callers left, and leaving them standing invites the same silent cast back. Verified: the exact CI spec set (editor-ux, editor-ux-mobile, safety-ui, desktop + mobile projects, --fail-on-flaky-tests) now passes locally against the same 5173 setup CI uses, plus left-nav and the 992-test dom suite. Signed-off-by: xNet Test <test@xnet.dev>
crs48
added a commit
that referenced
this pull request
Jul 28, 2026
…ch (0406) (#644) Continues exploration **0406** (12/33 after this). Follows #641 (the seam) and #642 (the migration onto it); this PR makes the core physically shareable. ## Why the core first, not the whole directory The workbench's chrome references **52 distinct app-local modules** (views, comms, hooks, search). A whole-directory move would churn web's highest-traffic area for days and conflate relocation with API design. The host-agnostic core, by contrast, has exactly four dependencies — `react`, `zustand`, `lucide-react`, `@xnetjs/plugins` — so it moves cleanly and delivers the thing desktop actually needs first: **the state layer**. `@xnetjs/workbench` now contains: the `PlatformPort`, the workbench store, tab/preview grammar, navigation intent, surfaces, command wiring, focus, route titles, sidebar sections, explorer sort, new-doc identity, and the test port. Private and source-consumed (`main → src/index.ts`, the `canvas-core` pattern) — no build ordering, no vite aliases. Old paths keep one-line shims (the 0280 `layout-tree.ts` pattern, this repo's own precedent). Both roads resolve to one module instance; consumers migrate opportunistically. ## What the move itself surfaced - `state.ts` declared its own `ChromePosture`, a twin of the canonical type in `@xnetjs/plugins` — 0277's copied-type drift in miniature, invisible until a barrel saw both declarations. Fixed at the source rather than papered over in the barrel. - `newDocId`/`CreatableDocType` graduate into the package; `lib/doc-creation` keeps routes + menu chrome and re-exports identity. ## Why this matters for the desktop The parity test's founding condition — *"the moment desktop adopts them it consumes the SAME module as web"* — now covers the entire state layer, not just `layout-tree`. Phase 3 (mounting the shell in Electron) can begin with desktop importing `useWorkbench` and the port directly. ## Verification - Live app: boot, working-set open, and New → Page (URL change + editor mount) all flow through the package modules, zero console errors. - 992 web + 89 package + 149 electron tests green (parity test included); repo-wide typecheck clean; lint 0 errors. - The router ban now also covers `packages/workbench/src`. `skip-changelog`: internal restructuring, no user-visible change. Private package → no changeset. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
crs48
added a commit
that referenced
this pull request
Jul 28, 2026
…650) ## Summary The chrome phase of exploration 0406 (one shell, two surfaces): the entire workbench shell chrome — frames, islands, menus, mobile shell, sidebar, explorer views, time machine, drafts, calm surface — moves from \`apps/web/src/workbench\` into \`@xnetjs/workbench\`, behind two injection points the host app fills at boot: - **View registry** (\`registerHostedViews\`): the shell renders tab content through \`hostedView()\`; web registers its complete view table in \`platform/hosted-views.tsx\` (a full \`Record<TabNodeType, …>\`, so exhaustiveness stays a compile error). ViewHost drops all 18 view imports. - **WorkbenchHost contract** (\`setWorkbenchHost\`): the app services the chrome consumes but cannot define — workspace data hooks (spaces, tags, storage, requests, node actions), comms presence, the Today-panel experiments API, and host-owned components (ShareDialog, GlobalSearch, WorkspaceCommands, SelfAvatar, coachmarks, error fallback…). Members are narrowed to what the chrome actually reads, so the desktop host will implement the minimum. Web binds its existing modules onto the contract in \`platform/workbench-host.tsx\` — no new behaviour, one file that knows both names. Pure modules moved outright (with shims at the old paths, 0280 pattern): format-bytes, settings-sections, saved-view-registry, tag-view-data, data-runtime, explorer-items, the source-stamp browser half, \`DOC_TYPE_META\` (the label/icon half of \`DOC_TYPE_ROUTES\`), and \`AGENT_LAYOUT_EVENT\`. What deliberately did **not** move: \`inspect/\` (dev-only point-and-change overlay, coupled to devkit), and every app component that binds the router or app data — those stay host-side behind the contract. Boot-order rule enforced throughout: **no \`workbenchHost()\` call at module scope** — the barrel evaluates before the app's boot registration runs, so module-scope registration (Workbench's builtin views, WorkspaceSwitcher's coachtips) became lazy-once on first render, and reading before registration throws loudly instead of rendering an empty shell. ## Verification - \`turbo run typecheck\` green across web, desktop, workbench (44 tasks) - 992 dom tests + 155 electron tests green; \`pnpm lint\` clean - Live-driven on the dev server: shell boots with zero console errors; New menu renders all DOC_TYPE_META entries; **New → Page navigates** (the #642 regression path); ShareDialog opens on a doc; Chats surface renders through the host wrapper; status bar (sync chips, storage bytes, What's New) all live - editor-ux spec set run locally: 7 passed; the one mobile failure reproduces identically against a baseline \`main\` server — pre-existing local-env issue, not this change Part of exploration 0406 (now 15/23 implementation). Next: mount \`<Workbench/>\` in Electron behind \`XNET_UNIFIED_SHELL\`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
crs48
added a commit
that referenced
this pull request
Jul 29, 2026
Every box in exploration 0406 is checked — 23/23 implementation, 10/10 validation — so the filename flips to `[x]`. The arc, for the record: #641/#642/#644 (port + core extraction), #648 (AI panel on desktop), #650/#651 (chrome behind injection points, first Electron mount), #652 (desktop surfaces in the shell), #653 (flag removed, bespoke shell deleted), #655 (cold-open back under the 0.52s baseline), #656 (approval-gated writes — the last blocked item). The desktop and web apps now render one shell from one module, verified live at every step. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.















Continues exploration 0406 (11/33 after this). Follows #641, which defined the seam; this PR makes the shell actually use it.
What this does
Migrates all 29 workbench modules and 20 app-land consumers off direct
@tanstack/react-routerimports onto thePlatformPort. The shell now states intent — "open this node", "go home", "this path" — and the web host resolves it to a URL. Web behaviour is unchanged; the point is that the same components can now mount on desktop over aShellState-backed port.Key moves:
navigation.tscollapses to intent emission; the node→route switch lives in the host'srouteForTarget, where a missing entry is a compile error instead of a silent dead click.armPreviewIntentso its semantics stay pinned by tests through the move.navigateToNewDocgains a port-shaped sibling, retiring theas unknown as NavigateLikecasts the compiler could not see into./settings?section=,/ai?q=) and a richerLink(drag sources, testids) — requirements the call-site survey found, not guesses.no-restricted-importsoverride turns any future router import in the workbench into a red build; verified against a probe file, not assumed.test-platform.tsxreplaces router module mocks: components stopped importing the router, so tests stop mocking it and provide a port.Verification
Beyond 992 green web tests: drove the live app through every migrated path — doc creation via the New menu, settings
?sectionround trip (write and read), working-set opens, profile-menu navigation, history back/forward. Zero console errors.One anomaly found (palette Enter on "Go to tasks" under synthetic input doesn't navigate) was stash-tested against the pre-migration baseline and reproduces identically — pre-existing, not a regression.
Changelog
skip-changelog: internal refactor, no user-visible change.🤖 Generated with Claude Code