Zum Hauptinhalt springen

Desk workspace (dock layout, focus architecture, mobile projection)

Generated from pattern-library/registry.json — do not edit by hand; change the registry and run bun run patterns:build.

Category: Desk Workspace · Tier: deep · Maturity: proven (verified 2026-08-20 @ 1a130d67) · Risk: medium — persists workspace layout locally and via the file registry; no cross-user surface

A full-page multi-panel workspace on a binary split-tree dock: resizable panes, drag-and-drop tabs, a panel-type registry, typed cross-panel pub/sub (DeskBus), one persisted desktop tree with total focus derivation, and a mobile chrome that is a projection over the same state — never a second layout.

When to use: Use for IDE-like authoring surfaces where several content types (editor, explorer, spreadsheet, AI bot) must coexist, communicate, and stay AI-drivable — and where the same workspace must work on phones without forking its state model.

Docs

  • docs/blueprint/desk/README.md — The hub: focus architecture, desk effect contract, mobile chrome, Explorer, DeskBus channels (GitHub · GitLab)
  • docs/blueprint/desk/spreadsheet.md — How a new file-backed panel type joins the workspace (GitHub · GitLab)

Code

  • src/lib/components/desk/ — The whole composite: DockLayout, leaves, tab bars, mobile chrome, state modules (GitHub · GitLab)
  • src/lib/components/desk/dock.state.svelte.ts — Split-tree state: total focus derivation (focusedLeafId → first non-empty leaf fallback), focusSeq counter (GitHub · GitLab)
  • src/lib/components/desk/dock.operations.ts — Pure tree math (split/remove/move) — kept side-effect-free and unit-tested as such (GitHub · GitLab)
  • src/lib/components/desk/panel-actions.ts — focusPanel() — the single focus writer every activation path goes through (tabs, drawer rows, AI effects, deep links) (GitHub · GitLab)
  • src/lib/components/desk/desk-bus.svelte.ts — Typed pub/sub channels between panels (context-scoped factory, not module state) (GitHub · GitLab)
  • src/lib/server/desk/ — File registry domain module + DockLayoutState valibot schemas (GitHub · GitLab)

Tests

  • src/lib/components/desk/dock.state.svelte.test.ts — Tree-op purity (GitHub · GitLab)
  • src/lib/server/desk/schemas.parity.test.ts — Gates DockLayoutState ↔ valibot schema key parity (GitHub · GitLab)

Proof

  • /desk (app route, no showcase)
  • /showcases/ui/dock — The same DockLayout embedded in a Card with mobileChrome="bar" — proves the composite is not /desk-specific

Invariants

  • The persisted DockLayoutState is always the desktop tree; mobile is a projection over it — mobile paths may focus, open, or close panels, never split or resize.
  • Exactly one focused panel per dock instance on every surface. Menus, AI page context, and the mobile visible panel all derive from it; every activation path writes through focusPanel().
  • Every DockLayoutState field must exist in DockLayoutStateSchema in the same commit — valibot strips unknown keys silently, so a missed field diverges the DB lane without an error.
  • No module-level $state for per-dock-instance concerns (panel menus, mobile surfaces) — context-scope them so two dock instances on one page cannot cross-talk. Singleton-ness follows the resource: per-tab resources like the visual viewport may be module singletons.
  • Desktop panel kebab and mobile commands sheet render the same composePanelMenus() array — a difference between them is a bug.
  • A desk effect that surfaces a panel leaves it visible without further interaction — overlay auto-close (focusSeq) is part of the effect, not a courtesy.

Emulation notes

  • Build the tree math as pure functions over a plain node type first (dock.operations.ts) and test it in isolation; wire reactivity in a separate .svelte.ts state class.
  • Vitest's node environment compiles Svelte in SERVER mode: factory-scoped $derived evaluates once and freezes. Any derived state you want to unit-test must be a plain function reading $state sources.
  • Make focus total, not optional: the stored focusedLeafId if it still resolves, else the first non-empty leaf. Consumers never handle a 'nothing focused' state while panels exist.
  • Bump a monotonic focusSeq on every focus write, repeats included — overlays close by watching it, which makes 'AI focused the already-visible panel' still dismiss the drawer.
  • Keep closed panels in the panels registry (only the tree entry is removed) — that is what makes close-undo a cheap re-attach instead of a state reconstruction.

Depends on


Machine-readable record: desk-workspace in pattern-library/registry.json.

← Back to Pattern Library

Geht dieses Pattern noch besser? Sag uns, wie.

Feedback geben