diff --git a/docs/workspace-panel-todo.md b/docs/workspace-panel-todo.md index ff8cc4ae..0f47b5b6 100644 --- a/docs/workspace-panel-todo.md +++ b/docs/workspace-panel-todo.md @@ -703,27 +703,45 @@ parsed identity. The two hand-written copies of the inert half are one named con 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 +- [x] **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` takes `T` from each +- [x] **Payload types are per-call-site, not per-channel.** `usePanelChannel` 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(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 + **Both resolved `c973558`** — `defineChannel(name, initial)` in `hooks/usePanelChannel.ts` + returns the hook, and `officerdev/src/channels.ts` declares the four. Every one of the twenty + remaining `usePanelChannel` call sites in the repo now passes a shared constant; there are no bare + literals left. +- [x] **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 + **Resolved `c973558`**, but not on `Date.now()` — that has the same bug at millisecond scale, and + the four FileViewer bump sites sit close enough together (save, delete, extract, transcribe) to hit + it. `useFilesRefresh()` exposes a `bump` over a module-level counter that never reads React state, + so it is correct however many times it is called between renders. `bump` is also identity-stable + through a ref, because `useGlobal`'s setter is a fresh closure every render and this one goes into + dependency lists. +- [x] **`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 + **Deleted `c973558`.** It had a writer once: `7c0b11c` ("sudo commands through ephemeral + terminal") wired the AI harness installer to it. That install moved server-side to + `POST /server-settings/chat-providers/install` and the write went with it, leaving the channel, the + `SystemTerminalPanel`, and a whole second `splitLayout` that nothing could select. Gone — the path + it existed for no longer wants a terminal. +- [x] **`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. + **Resolved `c973558`** — `component`, `header` and `provider` all take `{ panelId: string }` now, + since `PanelSlot` renders all three with it. A no-prop component is still assignable, so no screen + changed. ---