# 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.