Skip to content

Observatory Notes

The panel host: keep the instruments mounted

The panel host: keep the instruments mounted ​

September 29 disposition: all coupling seams below are improvement targets. Panels have independent view/filter state; classic-only code may be removed. The implementation plan supersedes the alternatives and classic-parity requirements in this original review.

P2 β€” tight coupling inventory; all extraction deferred.

Golden Layout and Tabulator solve real product problems: rearrangeable live views and dense editable data. Keeping their imperative instances alive is a correctness requirement when a refresh could otherwise destroy an edit. A functional React architecture should surround those instances with predictable data transformations, not pretend the instances are pure values.

What is working ​

Both shells use APP_ROUTES. WorkspaceRoutePanel gives each panel its route context and rendering error boundary. split-root-stack preserves existing content when splitting. Sheet synchronization defers data/configuration reconciliation during editing. These choices match the docs and should survive any cleanup.

The Inbox detail loader fetches a record and its attention separately; opening a detail does not require loading the collection. Preserve full conversation context for mail threads; a flat event feed is not an interchangeable replacement for a conversation.

Coupling inventory: identify it without moving it ​

BoundaryEvidence and costEventual seamWhy defer
Host ↔ Coleo navigation policyGoldenWorkspace.routeAttentionChannels hardcodes task/bug/Arm routes in addition to APP_ROUTES; host changes when domain attention changesRoute contributions can declare attention policy alongside title/navigation metadataFirst decide channel versus per-panel attention semantics
Host ↔ persistence ↔ chromeGoldenWorkspace.tsx is 1,339 lines and owns docking, route state, profile restore/save, local fallback, and shell UIWorkspace serialization/persistence policy plus a focused Golden Layout adapterSplitting by line count risks remounting panels or changing save timing
Sheet ↔ history ↔ embedded rootsResourceSheet.tsx is 877 lines, with a mount effect covering table events, history replay, row details, observers, and cleanupPure history transitions and a bounded renderer lifecycle ownerPreserve editor, selection, expanded-root, and undo guarantees first
Feature ↔ all view modesTasksPage.tsx is 1,431 lines; BugsPage.tsx 911; MessagingPage.tsx 645Domain query/action controller plus explicit list/detail compositionsSome shared state is intentional; avoid hooks that merely relocate a giant component
Viewer ↔ transport ↔ caches ↔ telemetryArmViewerPage.tsx is 3,329 lines and owns stream presentation plus browser-local history/preferencesSeparate event-log model, telemetry queries, and viewer presentationThis review sampled the integration boundary, not every Viewer interaction
Registration ↔ loadingAPP_ROUTES imports components through the pages barrel at module loadKeep static metadata and use lazy component loaders where worthwhileNo bundle trace was run; this is an eager source dependency, not a measured startup regression
Public sheet contract ↔ vendor/domainResourceSheetColumn exposes Tabulator Validator and task/bug StatusSeriesEntityNarrow validation/presentation callbacks only when another consumer needs themA vendor adapter is allowed to know its vendor; a universal grid DSL is not justified
Profile selection ↔ persistence timinguseSavedView and useViewPreferences combine server snapshots, drafts, provider rerenders, and timersExplicit draft identity and save revisionsCorrectness work in the notebook chapter must precede reuse

Source anchors: host, sheet, Tasks, Bugs, Inbox, Viewer, vendor contract, saved views. Line counts refer to the reviewed snapshot, not a target size.

Two ownership decisions hidden by the current vocabulary ​

A view instance is not a saved view. Panels have independent routes, but useSavedView resolves a stable key within a profile, such as tasks-sheet. Two panels using that key share the persisted definition. The layout stores route state, not a general independent view-definition reference for every panel. The guide's β€œview instance stored in each panel” should distinguish panel identity, route parameters, saved-view identity, and transient interaction state. Decide whether two task panels should share filter edits or fork views. Do not silently make per-panel settings permanent as part of cleanup.

A fallback is not a second authority. The host saves a browser-local layout and later the API layout, then prefers the server layout on restore. That is consistent with a server authority but can discard a newer local arrangement after an API save failure. Also, the unguarded localStorage.setItem occurs before scheduling the API save; a storage exception can prevent that save path entirely. Define fallback/error behavior before moving persistence to a hook. Browser-local preferences are explicitly permitted in ADR-004; their mere presence is not a violation.

Direction to choose ​

Recommended: keep the host; extract policy when behavior is protected. Start with explicit route contributions and pure serialization/history transitions. Keep one owner for each imperative instance. Use effects for external lifecycle synchronization and keep cleanup close to acquisition. Do not split one instance across many hooks that coordinate through a bag of mutable refs: that can hide the coupling while increasing ordering assumptions.

Pure models should take time/identity as input when they need it, return a new state or command description, and leave network and DOM work to a small shell. Stable resource keys and equality checks should prevent unnecessary imperative updates. Adding useMemo everywhere does not establish those semantics.

Alternative: retain application-specific modules until another product requires extraction. Still describe their actual responsibilities and fix state loss. A large integration adapter is tolerable when its invariants are explicit and well tested. A 1,339-line host that also defines product policy should not be described as an already reusable platform.

Proposed follow-up commits, only after selection ​

CommitBounded changeAcceptance
fix(web): keep layout persistence working without browser storageIsolate local fallback failure from API save schedulingThrow from storage; API save still runs; failure is visible; restore precedence stays explicit
refactor(web): declare attention policy with route contributionsMove the existing route/channel mapping after attention semantics are chosenClassic and Golden navigation still agree; no route loses expected attention behavior
refactor(web): separate sheet history transitions from table effectsPure model for recording/truncating/replaying edit and move historyRefresh adds no history; rejected edits do not corrupt history; active editor and row expansion survive

Before any adapter extraction, run the relevant Tasks/Bugs/Inbox/detail, stability, and error-boundary Playwright suites through bun run test:e2e:web. Include split/resize, profile refresh, dark/light editing, undo/redo, and restored deep links. Keep renderer-library upgrades out of the refactor.

The reusable future product is a bench with replaceable recipes. It is not Coleo with every noun generalized and every side effect hidden behind a hook.