refactor(panel): add @luke/panel with the bridge and platform doors

Merged
DA
11d ago
+302-182

Summary

Step 16 of the web plan ("šŸ“± Web support", Refactors to land first) moves the desktop panel out of apps/desktop/src/renderer into a package, so apps/web can mount the same panel later. This is the first of about 13 PRs. The move goes bottom-up, one dependency band at a time, because tasks/, desktop/, settings/ and the renderer root import each other in both directions, and a package may not import an app.

This PR adds packages/panel (@luke/panel) and moves its first band as-is. Only the import specifiers change:

  • the bridge door app-bridge.ts and the platform door app-platform.ts
  • the act door act.ts
  • the 7 hooks that reach only those doors: use-sign-in, desktop/use-run-choices, settings/writes, tasks/use-inbox-pages, tasks/use-open-link, tasks/use-taskless-inbox-items, tasks/use-workspace-members

Git records all 10 files as renames. The 116 desktop files that imported them now import @luke/panel/<path>. Biome re-sorted those imports, which is why each import line moves. The package's exports names exactly the subpaths the desktop still imports, and later bands prune that list as their importers move.

Beyond renames and import rewrites (please look at these specifically):

  • packages/panel skeleton: package.json, tsconfig.json (copied from @luke/ui's), src/sources.css (@source "./", the @luke/ui/theme.css pattern). The desktop's tailwind.css imports sources.css, so the utilities a panel module spells keep being generated. No vitest.config.ts yet, because no test moves in this band; the first band that moves one adds the config and the root projects row.
  • packages/AGENTS.md (approved by the developer): the DOM may live in packages/ui and packages/panel; Electron in neither.
  • scripts/repository-checks.sh:
    • The logic-lives-in-packages shell list drops the two door rows (renderer/app-bridge.ts, renderer/app-platform.ts), as their comment promised for step 16. It adds renderer/desktop-platform.ts, the desktop's answer to the platform door: with the doors gone it imports only packages, and it is shell by role.
    • A new panel_shell_reaches refusal keeps packages/panel/src off window.luke, every desktop alias (#shared/ #renderer/ #main/ #preload/ #testing/) and electron/electron-updater/@sentry/electron.
    • The renderer's path-keyed checks also scan packages/panel/src: renderer_escapes, drawn_hosted_quota, node_reaching_effect and the mkdtemp rule.
  • apps/desktop/src/renderer/AGENTS.md: one path changes. "renderer/act.ts is the only caller" now reads "@luke/panel's act.ts".
  • apps/desktop/package.json gains @luke/panel: workspace:*, and pnpm-lock.yaml is regenerated by pnpm install.

The desktop's behavior is unchanged: no code inside a moved file changed beyond its relative specifiers gaining .js.

Evidence

  • ./scripts/repository-checks.sh: passed.

  • tsc for @luke/panel and @luke/desktop: passed.

  • lint, knip, discarded-effect (turbo, --concurrency=1): passed.

  • @luke/desktop vitest: 215 files, 2043 tests passed. The count is unchanged: no test was added or removed.

  • @luke/desktop build, bundle budget included: passed. panel.js plus its static chunks carries the same modules. Only their order within the shared chunks moves.

  • I booted the built app on Linux under xvfb-run -a with --disable-gpu --disable-dev-shm-usage --fixture smoke, driven through Playwright's _electron. Not run on a Mac.

    • The panel drew Home with the fixture's tasks.
    • The hidden voice window mounted VoiceHost (<div id="root"><audio autoplay hidden>…).
    • call.html drew the call card.
    • The only console errors were Sentry's sentry-ipc:// requests, which also appear on main.

    Panel (--fixture smoke) and the call window:

    Panel, Home on the smoke fixture

    Call window card

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged.
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added. (No behavior change.)
  • Gateway envelope goldens unchanged.
  • No new as type assertion outside the allowlisted files.
  • No Effect.runPromise/runSync/runFork outside the runtime edges.
  • No new raw async primitives.
  • No Effect, Stream, or Layer written as a statement and dropped.
  • Test count equals the pre-PR count (2043).
  • No hand-rolled file replaced.
  • Every CLAUDE.md/AGENTS.md sentence naming a changed part is edited.
  • PRIVACY.md unchanged.
  • Package package.json deps match what the sources import by bare specifier.
  • Barrel/door rule: no Node-reaching Effect module behind a barrel the renderer opens. The package is now covered by the node_reaching_effect check.

Notes

  • Next: the renderer runtime and use-app-state, plus their cascade. That PR also changes the path in the root AGENTS.md (approved).
  • Follow-up, not in this PR: the changelog-screenshots skill's hook patch for future releases names apps/desktop/src/renderer/act.ts. A release cut after this merges needs that path moved to packages/panel/src/act.ts. The per-release patches for past releases stay valid.

Made with Luke


Devin Review