Files
platform/WORKSPACES-NOTES.md
pastilhasandClaude Opus 5 571deac731 one react-resizable-panels, and honest metadata for the peers
Two dependency problems, and only one of them was real.

── Real: two majors of the same library ──

The root asked for `react-resizable-panels@^4.0.15` (4.6.5) while
`src/workspaces/components` asked for `^2.1.7` (2.1.9). The workspace code targets 2.x,
whose API differs — v4 has no `PanelGroup` export, no `order` prop and no `ref` on
`Panel` — so a file that resolved the root's copy failed to typecheck in a way that
read like a broken extraction.

Nothing imported the root's version. It was vestigial, and removing it collapses the
repository to one: every importer now resolves 2.1.9 and the lockfile has zero v4
entries.

── Not real: the second React ──

Declaring react/react-dom in the package created
`src/workspaces/workspaces/node_modules/react`, which looks exactly like the
duplicate-instance bug that silently breaks hooks. It is not one. Both locations resolve
to the SAME file in bun's store — the local entry is a symlink — which I established by
resolving the specifier from each place instead of reading directory names, having
already reported it as a hazard on the strength of an `ls`.

Declared as optional peers regardless: that is the right metadata for a consumer, and it
stops bun installing peers it does not need to.

Notes corrected to match, since the wrong version of that story was written down.

tsgo clean, package 114/114, full suite 950 pass / 7 fail unchanged, frontend builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 00:21:12 +00:00

4.7 KiB

Workspaces extraction — phase 1

Branch workspaces-extraction. The Workspace/Panel framework is now src/workspaces/workspaces, a package that knows nothing about Officer.

Where it got to

The package renders under a bare React tree. Its own test suite (114 tests) mounts WorkspaceView with no provider of any kind — no react-query, no router, no Officer hooks. That is the proof: a host needs to supply a registry and a layout, and nothing else.

Two halves. core/ is the tree and every operation on it — pure, dependency-free, no React. react/ is one renderer. Phase 2 is a second renderer over the same core.

Officer is unchanged. All 24 WorkspaceView sites and 8 WorkspaceLayout sites still call exactly what they called before. officerdev/.../Workspace/index.ts is now an adapter that supplies the three things that are Officer's: the app registry, fullscreen persistence in sessionStorage, and the handshake that hides the nav header for a fullscreen panel.

Persistence was already injected. WorkspaceView always received { key, value, setValue, isLoaded } as a prop and called setValue with an updater. The headline requirement of phase 1 was mostly already true — the coupling was elsewhere.

~175 lines of dead code deleted: DragOverlay, LayoutEditor, WorkspaceHeader, referenced by nothing.

What actually had to move

Coupling Resolution
useAppRegistry registry is now a required prop
useSessionState (fullscreen) controlled/uncontrolled pair; Officer passes its sessionStorage state
panel-fullscreen / useGlobal the package reports via callback; Officer acts
use-panel-memory / useDashboardState stayed in Officer — it is host storage, not layout
@/components/{Card,ErrorBoundary,ui/*} vendored into react/internal/
hooks/useIsMobile vendored (27 lines)
helpers/cn, @/lib/utils vendored (2 lines)

Two traps, both worth knowing before touching dependencies

A second React — which turned out not to be one. Declaring react/react-dom created src/workspaces/workspaces/node_modules/react, and a directory of that name looks exactly like the duplicate-instance bug that breaks hooks. It was not: both locations RESOLVE to the same file in bun's store (.bun/react@19.2.4/…/react/index.js), because the local entry is a symlink. Checked by resolving the specifier from both places rather than by reading directory names.

They are declared as peerDependencies with peerDependenciesMeta.optional anyway — correct metadata for a consumer, and it keeps bun from installing peers it does not need to.

Two versions of react-resizable-panels — real, and now fixed. The root asked for ^4.0.15 (4.6.5) while src/workspaces/components asked for ^2.1.7 (2.1.9), and this code targets 2.x, whose API differs (v4 has no PanelGroup export, no order prop, no ref on Panel). Inheriting the root's resolution produced a screenful of type errors that read like a broken extraction.

Nothing imported the root's copy — it was vestigial. Removed, so the repository now carries one version and the lockfile has zero v4 entries.

Open

1. Panel-to-panel communication. usePanelChannel is a two-line wrapper over useGlobal, so it is react-query underneath. Six channels exist; three are genuine refresh signals (files:refresh-signal, preview:refresh, background-job:changed) and two are SELECTION (chat:selected-session, chat:active-session), which docs/navigation-audit.md says belongs in the URL. Deferred by decision: the mechanics will live in the package, configured from outside, probably as callbacks. The real requirement may be smaller than it looks.

2. The package still speaks Officer's theme. PanelSlot uses duck-teal classes and --panel-* CSS variables. Harmless inside Officer, wrong for a standalone module: a consumer would need those variables defined. Options are to accept a class-name map, ship a minimal stylesheet, or document the variables as part of the contract.

3. cwd/root are in the context. They are Officer concepts (a workspace points at a directory) that the package currently carries. Not harmful, not general.

4. Phase 2. A non-React renderer over core. What will not port is the resize interaction — drag handles, pointer maths, constraints — supplied today by react-resizable-panels. Either find a vanilla equivalent or write one; the tree logic comes across untouched, which was the point of the split.

Verifying

bun test src/workspaces/workspaces/ — 114 tests, no host. Full suite unchanged at 950 pass / 7 fail. Screenshots: 9 of 16 byte-identical to before the extraction, the rest being live data (chat sessions, system metrics) and the rotating wallpaper.