Files
platform/WORKSPACES-NOTES.md
T
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

84 lines
4.7 KiB
Markdown

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