document how the workspace/panel framework works
This commit is contained in:
@@ -4,6 +4,9 @@ Living list. Add items as they are found, tick them as they land, and write the
|
||||
item rather than deleting it — the reason a thing was done is worth more later than a clean list.
|
||||
Move anything fully settled to §7.
|
||||
|
||||
**How the framework actually works is documented separately, in `workspace-panels.md`** — read that
|
||||
first if you are new to it. This file is only the defect list and the work queue.
|
||||
|
||||
Findings and full reasoning: `COMMS/workspace-panel-framework-analysis-2026-08-07.md`. Every `file:line`
|
||||
below was opened; DB claims were run against live `officer_dev`. Paths are relative to
|
||||
`platform/src/workspaces/officerdev/src/` unless they start with `servers/`, `databases/` or `sidecars/`.
|
||||
@@ -57,6 +60,19 @@ Both are two-line fixes, and without them you cannot tell whether any later fix
|
||||
branches do not. A null falls to the UPDATE branch and sets a `NOT NULL` column
|
||||
(`databases/officer_db/src/queries/dashboards.ts:70`) → 23502.
|
||||
|
||||
- [ ] **`HostTerminalWrapper` never strips the prefix, so it mints phantom dashboards.** *(found
|
||||
2026-08-08, latent — `dashboards` is still 0 rows)*
|
||||
`apps/Terminal/HostTerminalWrapper.tsx:12` is `` `ws-host-terminals-${dashboardId}` `` with **no
|
||||
regex**, while its sibling `TerminalWrapper.tsx:13-14` correctly matches `^ws-layout-(.+)$` first.
|
||||
So the key becomes `ws-host-terminals-ws-layout-<id>` or `ws-host-terminals-screens/terminal`, the
|
||||
dispatcher's `^ws-host-terminals-(.+)$` branch captures that whole string as an id, and
|
||||
`upsertDashboard` **inserts a row when the id is unknown** (`queries/dashboards.ts:75-86`,
|
||||
`name: data.name ?? id`). `getAllDashboardState` maps every `dashboards` row into `workspaces`, so
|
||||
the garbage id surfaces in the Dashboards list as a real dashboard.
|
||||
Two apps deriving a state key from the same string by two different rules is the actual defect;
|
||||
the id-shaped-string-as-a-bag problem behind it is §5.9.
|
||||
Latent only because `officerdev/terminal-host` is in no default layout and hidden from the picker.
|
||||
|
||||
- [ ] **Renaming a dashboard resurrects it as a zombie row.**
|
||||
`apps/Dashboards/DashboardPreview.tsx:316-318` PATCHes `ws-layout-old: null` *and*
|
||||
`ws-terminals-old: null` together. The first deletes the row; the second then calls
|
||||
@@ -116,12 +132,16 @@ these.
|
||||
`databases/CLAUDE.md`'s stance that the schema is the source of truth for *contents*, a CHECK on
|
||||
`jsonb_typeof(layout) = 'object'` is the cheap half.
|
||||
|
||||
- [ ] **A bare number is squatting in a framework namespace.** *(verified in the live DB)*
|
||||
`apps/Soulseek/shared.ts:12` builds `screens/soulseek-zoom/${panelId}` and passes it to
|
||||
- [x] **A bare number is squatting in a framework namespace.** *(verified in the live DB)*
|
||||
`apps/Soulseek/shared.ts:12` built `screens/soulseek-zoom/${panelId}` and passed it to
|
||||
`useDashboardState<number>`. The server routes `^screens/(.+)$` into `screens.layout`, so:
|
||||
`user_id 1 | soulseek-zoom/soulseek-view | jsonb_typeof = number`. That namespace belongs to
|
||||
layouts. Give per-panel scalar prefs their own key family (and a matching dispatcher branch, which
|
||||
the §2 `else` will otherwise start rejecting).
|
||||
layouts.
|
||||
**Resolved `ba664dc`** — Soulseek's private zoom became a framework feature (`LayoutPanel.zoom`),
|
||||
so the scalar now rides on the layout node inside `screens/soulseek-v2` and needs no key of its
|
||||
own. The squatting row is gone: `select name from screens` returns 15 rows, none `soulseek-zoom/*`.
|
||||
This is also the general answer for per-panel scalar prefs — put them on the node, not in a key,
|
||||
because `useDashboardState` seeds a row per key on mount.
|
||||
|
||||
---
|
||||
|
||||
@@ -307,6 +327,59 @@ All the same bug: an app guessing "am I being closed?" from an unmount, or payin
|
||||
(`frontend.tsx:15-21`) with no per-route scoping, and `reset` is called nowhere.
|
||||
Authority: `docs/navigation-audit.md`.
|
||||
|
||||
### 5.9 The context has grown an app-config section — *(found 2026-08-08)*
|
||||
|
||||
`WorkspaceContext` is 18 fields, of which the framework itself reads none of the first six. Apps never
|
||||
touch the framework half, so the abstraction holds in one direction; the leak is entirely outbound.
|
||||
|
||||
- [ ] **Delete `initialFilePath` and `defaultFileSort`.** Declared `WorkspaceContext.ts:14-15`, plumbed
|
||||
through `WorkspaceView.tsx:19-20,30`, read only by
|
||||
`apps/FileBrowser/FileBrowserApp/FileBrowserPanelWrapper.tsx:7,9,10` — and **set by zero callers**.
|
||||
`DefaultFileSort` (`{field: 'name'|'size'|'type'|'date'}`) is file-browser vocabulary living in the
|
||||
framework's type file, and it is re-exported from the barrel (`Workspace/index.ts:27`). Pure leak,
|
||||
no payoff, entirely deletable.
|
||||
- [ ] **Move `promptPrefix` onto the component, not the context.** `WorkspaceContext.ts:16` →
|
||||
`Chat/ChatPanelWrapper.tsx:78` → `useEmbeddableChat.ts:107`. Set by `EmailScreen.tsx:53` and
|
||||
`BrowserScreen.tsx:35`, each a screen-local ~40-word system prompt. The framework is a courier for
|
||||
a string only one app understands, and the `components` prop already exists for exactly this —
|
||||
Email can supply a pre-configured chat by panel id.
|
||||
- [ ] **`dashboardId` is a bag whose *format* three apps parse.** It is literally `workspace.key`
|
||||
(`WorkspaceView.tsx:163`). Consumers reverse-engineer meaning from its shape:
|
||||
`Chat/ChatPanelWrapper.tsx:51-56` does `dashboardId === 'email' || dashboardId === 'screens/email'`
|
||||
→ email context, and `!startsWith('screens/')` → dashboard context — **so renaming a screen key
|
||||
silently changes the agent's system context**. `TerminalWrapper.tsx:13-16` regexes it;
|
||||
`HostTerminalWrapper.tsx:12` doesn't (§2). Give the context the parsed facts
|
||||
(`{ kind: 'screen'|'dashboard', id }`) instead of the raw key, and the three parsers collapse.
|
||||
- [ ] **`WorkspaceLayout.tsx:36` silently omits `root`, `initialFilePath`, `defaultFileSort`** — apps
|
||||
inside a `DashboardPreview` fall through to the `createContext` defaults. Whatever survives the
|
||||
three items above should be constructed in one place, not twice by hand.
|
||||
|
||||
### 5.10 Channel hygiene — *(found 2026-08-08)*
|
||||
|
||||
Cheap to fix, and prerequisites for the 5.8 migration rather than alternatives to it.
|
||||
|
||||
- [ ] **Four channels are bare string literals with no constant.** `'files:refresh-signal'` (repeated in
|
||||
4 files), `'chat:selected-session'` (3 files — plus a module-private `CHANNEL` const in a 4th place
|
||||
that only one of them uses), `'chat:active-session'`, `'preview:refresh'`. A typo silently yields a
|
||||
fresh channel pinned to `initialData`; nothing errors. Export a constant per channel, next to its
|
||||
payload type.
|
||||
- [ ] **Payload types are per-call-site, not per-channel.** `usePanelChannel<T>` takes `T` from each
|
||||
caller, so a publisher and a subscriber can disagree and nothing checks.
|
||||
`'files:refresh-signal'` is `number` in all four places by convention only. A
|
||||
`defineChannel<T>(name)` helper returning a typed hook would fix both this and the item above.
|
||||
- [ ] **Two write idioms disagree on the same channel.** `files:refresh-signal` is bumped with
|
||||
`Date.now()` at the Chat sites and `setRefreshSignal((n) => n + 1)` at the FileViewer sites — and
|
||||
`useGlobal`'s functional form applies against the **render-time** snapshot (`useGlobal.ts:18`), so
|
||||
two increments in one render window collapse into one. Standardise on the nonce.
|
||||
(`useLyricsOpen.ts:19-23` already documents avoiding the functional form for this reason.)
|
||||
- [ ] **`system-settings:run-command` has no writer.** `run-command-channel.ts:6`; the only two writes
|
||||
(`SystemSettings.tsx:87,133`) are both *clears*, and the sibling ServerSettings sections never
|
||||
import it. The panel it drives — a terminal that opens with a command pre-loaded — appears
|
||||
unreachable. Wire it or delete it; add to §8 either way.
|
||||
- [ ] **`PanelComponentEntry.component` is typed with no props** (`types.ts:60-62`) but
|
||||
`PanelSlot.tsx:521` passes `panelId` at runtime. `components`-supplied panels get a prop they
|
||||
cannot see; registry apps get the honest `{ panelId: string }`. One-line type fix.
|
||||
|
||||
---
|
||||
|
||||
## 6. Decisions needed — not defects, don't guess
|
||||
|
||||
Reference in New Issue
Block a user