Serves: #18456 / #18461 — making Workstation and the dock examples idiomatic Neo consumers. A parallel lookup vocabulary is the opposite of idiomatic.
Body rewritten 2026-09-09 by its author, per ticket-create §11 — the lane is @neo-opus-ada's and she cannot edit this body, but it still argued a design she has already superseded and landed against. The ACs below replace the originals. My AC-3 read as a rename; that framing is withdrawn, and the reasons are recorded so it does not return.
The Problem
componentRef is a second component-lookup vocabulary beside reference, which the engine is already built around at three layers. Consumers additionally hand-write it into panes declarations, where the engine derives it anyway.
The Architectural Reality
Neo's existing reference machinery:
src/component/Base.mjs:224 reference_: null reactive config
src/component/Base.mjs:1506 getReference(v) { return this.down({reference: v}) }
src/controller/Component.mjs:117 getReference(name) VC lookup, deep trees
src/component/Base.mjs:739 value && this.changeVdomRootKey('data-ref', value)Reactive, resolvable from a component and a view controller, and it reaches the live DOM as data-ref. Dock panes have none of this today — projected panes carry no reference, so getReference() and the VC path do not reach into a dock.
The strongest consumer already converts one into the other by hand. neo-agent-institution, apps/agentos/view/fleet/cockpit/VesselContainer.mjs:367:
reference = componentRef === 'define-agent' ? 'add-agent-form' : componentRef;
with one deliberate remap — and a remap is just another reference value. That answers the design question from a consumer's own code rather than from absence of evidence.
And it genuinely occupies the advanced-resolver case. Container.mjs:736 is a switch (componentRef) dispatching on the value to build a pane config — the "stable componentRef handled by an advanced resolver" case ADR 0029 §5 reserves. Its values are not the item keys (fleet → 'fleet-grid', defineAgent → 'define-agent'), which is why the removal argument is "consumers choose these names, so the engine must not fabricate one" — not "the key already is the identity", which holds only for lowered documents.
Exposure is two repos, established org-wide (53 repos; local checkouts were never an authoritative root):
gh search code --owner neomjs 'neo.dock.zone.v1' --limit 100
→ 71 results, NOT capped → neomjs/neo = 66 · neomjs/neo-agent-institution = 5
gh search code --owner neomjs 'resolveComponentRef -repo:neomjs/neo' → 0
That last row is decisive: nobody outside neo overrides the engine's resolver seam by name.
⚠️ Counts below understate the institution surface. gh search code is case-insensitive; git grep is not — 6 files vs 11 for componentRef, the delta carrying resolveDockComponentRef. Pin -i on the git side before comparing, or the divergence reads as repo drift.
The Fix
Removal plus a capability, not a rename. Three things wear one name:
- Identity — the item key.
Authoring.mjs:184's ??= fabricates it for hand-built wire documents that never asked for one.
- Display label —
DemoAWorkspace.resolvePane:319 uses it as text, byte-identical to title at demoADockChoreography.mjs:60. title already exists.
- Component lookup — what
reference is for, and what dock panes lack.
So: drop componentRef from the document, and have projection lower the pane key onto the component's reference — putting data-ref on projected pane roots and handing consumers the engine's own lookup inside a dock. A deletion plus a gain, where a rename would preserve the bloat under a better name.
No tolerance. An earlier plan kept the field allowlisted read-only so the two repos could disagree at one instant. FM consumes a pinned engine SHA and resolves its tickets before bumping, so pinning already provides that decoupling — the argument was circular, and what remained was backward compatibility, which is explicitly not an argument here.
AC
- AC-1
A verdict on whether the document layer needs a key distinct from reference — ANSWERED: no. VesselContainer.mjs:367 converts it to a reference at the point of use; the engine must stop fabricating identities consumers did not choose.
- AC-2 No example, app or guide hand-writes a
componentRef.
- AC-3 (replaced) Projection lowers the pane key onto the component's
reference, so data-ref appears on projected pane roots and getReference() / the VC path reach into a dock. This is not a rename of a persisted field — a rename inside neo.dock.zone.v1 breaks every saved layout carrying the old key, which is the hazard #18529 names and this ticket originally omitted.
- AC-3b ⚠️ Field removal and the
Rail.mjs:1084 gate removal land in one commit. Removing the field alone does not degrade — it breaks reveal completely: post-lowering every item has the field so the gate always passes today; drop the field and it always fails, every reveal falls to blueprint, and any pane without one never reveals. Controlled by @neo-opus-ada: reverting only the gate turns DockWorkspaceAuthoring's three rail-reveal arms red with element(s) not found; restoring it returns 6/6.
- AC-4
If retained — dead branch, removed.
- AC-5 The
test occurrences are migrated or scoped out with a reason. Recount with -i before estimating; the 334 figure came from a case-sensitive grep.
- AC-6 The
neomjs/neo-agent-institution migration is filed as its own ticket in its own repo, on its own schedule.
Out of Scope
placement.kind and the drag/preview vocabulary. The kind catalog field — #18529, which is not symmetric: that one is on #18474's ratified avoided-traps list and has a decided half; this one was a genuine open design question until VesselContainer.mjs:367 answered it.
Decision Record impact
challenges ADR 0029's document vocabulary, and fills §5's advanced-resolver case with the finding that its one occupant already converts to reference by hand.
Sweeps and ownership
Org-wide census closed by @neo-opus-grace; independently re-run and improved by @neo-opus-ada (excluding the dominant repo makes the result cap structurally unreachable rather than empirically unreached). Lane: @neo-opus-ada. Engine migration landed at 3ad3b8b8dc.
Serves:#18456 / #18461 — making Workstation and the dock examples idiomatic Neo consumers. A parallel lookup vocabulary is the opposite of idiomatic.The Problem
componentRefis a second component-lookup vocabulary besidereference, which the engine is already built around at three layers. Consumers additionally hand-write it intopanesdeclarations, where the engine derives it anyway.The Architectural Reality
Neo's existing reference machinery:
src/component/Base.mjs:224 reference_: null reactive config src/component/Base.mjs:1506 getReference(v) { return this.down({reference: v}) } src/controller/Component.mjs:117 getReference(name) VC lookup, deep trees src/component/Base.mjs:739 value && this.changeVdomRootKey('data-ref', value)Reactive, resolvable from a component and a view controller, and it reaches the live DOM as
data-ref. Dock panes have none of this today — projected panes carry noreference, sogetReference()and the VC path do not reach into a dock.The strongest consumer already converts one into the other by hand.
neo-agent-institution,apps/agentos/view/fleet/cockpit/VesselContainer.mjs:367:reference = componentRef === 'define-agent' ? 'add-agent-form' : componentRef;with one deliberate remap — and a remap is just another
referencevalue. That answers the design question from a consumer's own code rather than from absence of evidence.And it genuinely occupies the advanced-resolver case.
Container.mjs:736is aswitch (componentRef)dispatching on the value to build a pane config — the "stablecomponentRefhandled by an advanced resolver" case ADR 0029 §5 reserves. Its values are not the item keys (fleet→'fleet-grid',defineAgent→'define-agent'), which is why the removal argument is "consumers choose these names, so the engine must not fabricate one" — not "the key already is the identity", which holds only for lowered documents.Exposure is two repos, established org-wide (53 repos; local checkouts were never an authoritative root):
That last row is decisive: nobody outside
neooverrides the engine's resolver seam by name.⚠️ Counts below understate the institution surface.
gh search codeis case-insensitive;git grepis not — 6 files vs 11 forcomponentRef, the delta carryingresolveDockComponentRef. Pin-ion the git side before comparing, or the divergence reads as repo drift.The Fix
Removal plus a capability, not a rename. Three things wear one name:
Authoring.mjs:184's??=fabricates it for hand-built wire documents that never asked for one.DemoAWorkspace.resolvePane:319uses it as text, byte-identical totitleatdemoADockChoreography.mjs:60.titlealready exists.referenceis for, and what dock panes lack.So: drop
componentReffrom the document, and have projection lower the pane key onto the component'sreference— puttingdata-refon projected pane roots and handing consumers the engine's own lookup inside a dock. A deletion plus a gain, where a rename would preserve the bloat under a better name.No tolerance. An earlier plan kept the field allowlisted read-only so the two repos could disagree at one instant. FM consumes a pinned engine SHA and resolves its tickets before bumping, so pinning already provides that decoupling — the argument was circular, and what remained was backward compatibility, which is explicitly not an argument here.
AC
A verdict on whether the document layer needs a key distinct from— ANSWERED: no.referenceVesselContainer.mjs:367converts it to areferenceat the point of use; the engine must stop fabricating identities consumers did not choose.componentRef.reference, sodata-refappears on projected pane roots andgetReference()/ the VC path reach into a dock. This is not a rename of a persisted field — a rename insideneo.dock.zone.v1breaks every saved layout carrying the old key, which is the hazard #18529 names and this ticket originally omitted.Rail.mjs:1084gate removal land in one commit. Removing the field alone does not degrade — it breaks reveal completely: post-lowering every item has the field so the gate always passes today; drop the field and it always fails, every reveal falls toblueprint, and any pane without one never reveals. Controlled by @neo-opus-ada: reverting only the gate turnsDockWorkspaceAuthoring's three rail-reveal arms red with element(s) not found; restoring it returns 6/6.If retained— dead branch, removed.testoccurrences are migrated or scoped out with a reason. Recount with-ibefore estimating; the 334 figure came from a case-sensitive grep.neomjs/neo-agent-institutionmigration is filed as its own ticket in its own repo, on its own schedule.Out of Scope
placement.kindand the drag/preview vocabulary. Thekindcatalog field — #18529, which is not symmetric: that one is on #18474's ratified avoided-traps list and has a decided half; this one was a genuine open design question untilVesselContainer.mjs:367answered it.Decision Record impact
challengesADR 0029's document vocabulary, and fills §5's advanced-resolver case with the finding that its one occupant already converts toreferenceby hand.Sweeps and ownership
Org-wide census closed by @neo-opus-grace; independently re-run and improved by @neo-opus-ada (excluding the dominant repo makes the result cap structurally unreachable rather than empirically unreached). Lane: @neo-opus-ada. Engine migration landed at
3ad3b8b8dc.