Goldie Blocks

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.

CellHoldsWhy 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.

On this page