Skip to content

Observatory Notes

The bench: separate tools from product recipes

The bench: separate tools from product recipes ​

September 29 disposition: select a portable resource core, generic collection rendering, semantic slots, and a shared visual/text toolbar model. The implementation plan supersedes the alternatives and classic-shell preservation requirements below; these remain the original review.

P2 — observed dependency mismatch; refactoring deferred.

The workbench README calls its folder reusable application-shell behavior; the guide says design-system components do not know about tasks, Arms, Brain, or routing. The four-layer story is a useful direction, but it is not an enforced dependency boundary today.

What the code actually owns ​

CodeActual responsibilityReuse assessment
resource-sheet-model.tsPure filtering, sorting, column projection, stable row identity, move neighborsStrongest candidate for reuse; needs only structural data contracts
AdaptiveCardCollection.tsxOrdered generic items, CSS layout, animation, injected card renderingMostly a generic collection; its name and presentation types tie it to cards unnecessarily
ResourceSheet.tsx and resource-sheet-tabulator.tsReact/Tabulator lifetime, editing, undo, row details, vendor mappingUseful adapter; public columns still expose Tabulator Validator and Coleo status entities
TaskSheet, BugSheet, DiscoverySheet, ArmCollectionRowDomain schemas, statuses, mutations, navigation or presentationProduct recipes, despite living beside generic infrastructure
CollectionViewToolbar and SheetWorkspaceToolbarVisual controls plus profile-backed template lookupApplication compositions inside the visual foundation
types.tsBoth generic preferences and Coleo ResourceKind, ArmRun, channel unionsA shared file, not a portable domain model

Evidence: pure sheet model, collection, column contract, task recipe, collection toolbar, sheet toolbar, status palette, and contracts.

The toolbar dependency is especially concrete: visual components call useToolbarTemplate, which requires the workbench's profile context. Meanwhile the template provider imports parsing through the design-system module. A new application cannot take that toolbar as a visual primitive without taking profile wiring too. This is an ownership inversion, not evidence of a runtime module-cycle failure.

ResourceSheetColumn<T> is generic over rows, but its validator is a Tabulator type and statusEntity selects task/bug styling. Calling the whole contract vendor-neutral overstates it. The pure projection model really is neutral; the renderer-facing column contract is deliberately adapted to a vendor.

Direction to choose ​

Recommended: keep a small reusable core and explicit Coleo compositions. An eventual dependency direction should read:

text
Coleo feature → workbench composition → visual primitives / renderer adapter
                       ↓
                pure projection model

Start with the toolbar: a visual toolbar receives a template and widgets; an application wrapper reads the profile. Keep the shared parser in its existing non-React module, src/workbench/toolbar-templates.ts, and import it directly. The server and browser already sharing that allowlisted schema is a good choice. Do not replace it with an executable plugin registry.

Treat domain sheets as recipes and eventually colocate them with the feature that owns their statuses and edits. Do not move dozens of files just to satisfy a diagram. Name the intended boundary now; move a recipe when changing it.

Alternative: accept a Coleo-specific workbench. Revise the documentation to say that only selected models and primitives are reusable. This is reasonable if another application remains hypothetical, but the design-system ownership claim must then explicitly acknowledge its connected components.

Functional programming that earns its keep ​

Keep projection as rows + columns + preferences → projected rows. Existing helpers return new arrays without mutating domain records, and stable IDs keep sorting separate from resource identity. Preserve those properties when adding filters. Local mutation of a newly allocated Map or array is fine; purity is about observable effects, not banning every assignment.

Avoid mirrors of derived state and event-counter props. For example, TaskSheet observes draftFilterToggleRequest in an effect, changes filters, then reports draftsOnly back upward. A future feature controller can own the user action and apply a pure toggleDraftFilter transformation once. Keep the previous-status restoration rule; replacing it with a simplistic boolean would lose existing behavior. React's effect guidance supports deriving display values during render and handling user actions where they occur; external table synchronization still belongs in effects.

Do not turn every type in types.ts into a generic framework requirement. WorkbenchEvent, MetricSample, and PlanDocumentRef have no consumers found outside their declarations in the inspected frontend. They communicate intent, but they are not evidence that the app executes through a unified model.

Proposed follow-up commits, only after selection ​

CommitBounded changeAcceptance
refactor(web): separate toolbar rendering from profile lookupKeep connected wrappers at the workbench boundary; pass templates into visual componentsRender primitives without profile providers; existing toolbar overrides and both shells behave identically
refactor(web): make draft filtering an explicit view actionExtract the filter transition and call it from the owning featureNon-status filters survive; prior statuses restore; no effect-counter protocol
docs(workbench): label product recipes and portable contractsUpdate ownership guidance after the chosen boundary existsThe documented import direction matches actual imports

For a pivot to a support desk, reuse the collection model, surface primitives, toolbar renderer, and stable panel identity. Supply ticket fields, ticket commands, and API queries as new recipes. Keep Brain, Arm, plan reconciliation, and task lifecycle policy in Coleo. A second real consumer should determine which remaining interfaces deserve extraction.