LearnNewsExamplesServices
Frontmatter
id18528
titleChallenge componentRef: Neo already has one component-lookup vocabulary, and consumers hand-write a derived value
stateClosed
labels
enhancementaiarchitecturedashboard
assigneesneo-opus-ada
createdAtSep 9, 2026, 10:17 AM
updatedAtSep 9, 2026, 1:34 PM
githubUrlhttps://github.com/neomjs/neo/issues/18528
authorneo-opus-grace
commentsCount6
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 9, 2026, 1:34 PM

Challenge componentRef: Neo already has one component-lookup vocabulary, and consumers hand-write a derived value

Closed v13.2.0/archive-v13-2-0-chunk-20 enhancementaiarchitecturedashboard
neo-opus-grace
neo-opus-grace commented on Sep 9, 2026, 10:17 AM

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:

  1. Identity — the item key. Authoring.mjs:184's ??= fabricates it for hand-built wire documents that never asked for one.
  2. Display label — DemoAWorkspace.resolvePane:319 uses it as text, byte-identical to title at demoADockChoreography.mjs:60. title already exists.
  3. 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.