The Three Selection Cells Are Not Duplicates
Why canvas selection is not collapsed to one owner — each cell holds a different thing, and emitSelection resolves rather than translates
Decision
Canvas selection keeps its three useState cells. They are not three
copies of one value, so "collapse selection to one owner with the others
reading it" is rejected.
| Cell | Holds | Why it cannot be one of the others |
|---|---|---|
internalSelection (the editor's own state; since removed) | The editor's uncontrolled fallback. | Its domain is FocusTarget, which includes addModule — a keyboard focus position that is not a selection at all. No selection type can express it. |
selection (src/pages/document-work/ui/document-work-screen.tsx) | The controlled CounselDraftingV3Selection — { type, moduleId, blockId }. | Pure identity. Carries no labels, so the Inspector cannot render from it. |
workSelection (src/pages/document-work/model/use-canvas-screen-state.tsx) | WorkSelectionState — identity plus resolved display facts: moduleLabel, moduleDescription, blockLabel, moduleHandle, blockFieldKeys. | The resolved facts do not exist in either cell above and cannot be derived without modules. |
emitSelection is a resolver, not a translator. Its ~47 lines take an
identity and look up the module record, the block record, the module's
authored handle, and the block's field keys from modules — a snapshot the
work screen holds and the editor does not. It is doing work, not restating a
shape in a second spelling. A translation layer would be waste; a resolver
that turns identity into display facts is the screen earning its place in
the chain.
This matters because the shape looks like duplication from a distance — three cells, two type names, a function in between — and an architecture survey that counts shapes rather than reading them will flag it again. It is recorded here so it does not have to be re-derived.
Context
The architecture deepening
candidates survey
bundled two moves into its candidate 5: group the six pass-through block
handlers, and "collapse selection to one owner". The first was settled and
became
BlockAuthoringActions. The
second was rejected on the evidence above.
The two moves also differ in kind, which is the second reason to separate them. Grouping the handlers is mechanical and behaviour-preserving: the props cross the two conduit layers with zero logic applied at either, so the change is verified by the typechecker and the existing story suite unchanged. Reshaping selection changes who performs a lookup — a real behaviour question. Letting it ride along on an otherwise-mechanical change would have put the risky half and the safe half in one commit.
Why not collapse to the work screen as sole owner
This was the survey's suggested owner, and it is the wrong one for a
specific reason: the editor's cell outlives selection. Keyboard navigation
walks a FocusTarget list where addModule sits alongside blocks and
modules, so the editor needs a focus cell whether or not a selection is
controlled from above. Removing it would mean either pushing addModule
into the selection vocabulary — polluting a domain concept with a UI affordance —
or giving the editor a second cell anyway, which is the state we are already in.
The real question, deferred
The genuine simplification here is not fewer cells but a different
division of labour: give the Inspector a BlockRef and let it resolve
labels itself from modules, rather than having the work screen pre-resolve
them into WorkSelectionState and pass the result up.
That is a question about who owns snapshot lookup, and it reaches further than selection — the same question governs how the Inspector, the work screen, and the editor each read the canvas snapshot. It is deferred to its own candidate in the deepening plan rather than answered here.
Deferring it is not a way of avoiding it. It is deferred because answering it well needs the snapshot-reading sites surveyed together, and answering it badly would trade one resolver for several scattered lookups.
A Selected Document Paints a Black Spine and No Rail
Why the outer Folio marks selection with a black spine and the quietest tint ladder in the palette, and why it never paints the containment rail a module does
Deterministic Write-Safety For Hosted App Flow Conditions
Why the hosted app gates writeModule with evaluateFlowCondition while local no-runtime skills remain agent-interpreted