LearnNewsExamplesServices
Frontmatter
id17864
titleRoot edge chips vanish for descriptor-shaped center zones
stateClosed
labels
bugai
assigneesneo-fable
createdAtAug 29, 2026, 11:15 PM
updatedAtAug 30, 2026, 12:06 AM
githubUrlhttps://github.com/neomjs/neo/issues/17864
authorneo-fable
commentsCount1
parentIssue13158
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 30, 2026, 12:05 AM

Root edge chips vanish for descriptor-shaped center zones

neo-fable
neo-fable commented on Aug 29, 2026, 11:15 PM

Context

Found during the #17863 journey-repair diagnosis (its engine-escalation clause routes the fix here). The WorkstationDragAffordancesNL park journey asserts the top edge chip's position against the app header — and the chip is not merely mispositioned, it is never offered: the parked candidate set arrives with root: undefined, so all four container edge chips stay -off. Reproduced with an in-spec diagnostic dump: candidateSet.root: undefined, zone: 'scale-tabs' while the cross indicators light normally.

The Problem

Neo.dashboard.dock.interaction.DragAffordances#ensureGeometry resolves the chips' root target as:

rootId = nodes[me.owner.dockModel.root]?.type === 'edge-zone'
    ? (nodes[me.owner.dockModel.root].zones?.center ?? me.owner.dockModel.root)
    : me.owner.dockModel.root;

In the current document schema an edge-zone's zones.center is a descriptor object ({nodeId: 'split-main'} in Workstation's committed document), not a bare node-id string — Document.getZoneNodeId exists precisely to unwrap both shapes, and the projection tier uses it everywhere. Here the raw descriptor flows into geometry.root.nodeId, and PreviewProducer#produceCandidates fail-closes on its type guard (typeof root.nodeId === 'string') → rootOut = null → DropIndicators receives no root.chips → every edge chip keeps neo-dashboard-dock-drop-indicator-off.

Blast radius: every edge-zone-rooted consumer (Workstation, the cockpit) silently loses the dock-to-container-edge affordance family during drags. Split-rooted documents (the dashboard dock example, Demo-A) take the : me.owner.dockModel.root branch with a bare string and are unaffected — which is why the defect hid: the affordance demos kept working while the flagship lost its chips.

The Architectural Reality

  • src/dashboard/dock/interaction/DragAffordances.mjs — the resolution site; imports PreviewProducer but not Document.
  • src/dashboard/dock/model/Document.mjs#getZoneNodeId — the canonical object-descriptor reader ({nodeId, …}); the v13.2 contract deliberately rejects the retired bare-string shorthand (fail-closed null, no hidden compatibility reader — its own JSDoc states this). (Corrected per PR #17867 review, 2026-08-29 — the original line claimed dual-shape support.)
  • src/dashboard/dock/interaction/PreviewProducer.mjs#produceCandidates — the fail-closed consumer whose guard converts the malformed id into silent chip absence. The guard is CORRECT; the producer's silence is the designed fail-closed posture.

The Fix

Unwrap through the canonical helper at the resolution site:

rootId = nodes[me.owner.dockModel.root]?.type === 'edge-zone'
    ? (Document.getZoneNodeId(nodes[me.owner.dockModel.root].zones?.center) ?? me.owner.dockModel.root)
    : me.owner.dockModel.root;

plus the Document import. Unit witness: a DragAffordances geometry test with an edge-zone document whose center is descriptor-shaped must produce a candidate set whose root.nodeId is the unwrapped string and whose chips carry all four edges — red against the current line by construction.

Acceptance Criteria

  • With an edge-zone root whose center zone is descriptor-shaped, the produced candidate set carries root.nodeId as the center node's id string and four edge chips.
  • Split-rooted documents behave identically to today (no shape regression; the existing split-root tests prove the unchanged branch). The retired bare-string shorthand stays rejected — fail-closed null falls back to the document root, and no legacy compatibility reader is added. (AC corrected per PR #17867 review, 2026-08-29.)
  • Unit-tier witness red against the unfixed line (mutation-checked).
  • The WorkstationDragAffordancesNL park journey's top-chip guard executes again (validated in the #17863 lane; its AC converts from deferred-evidence once this merges).

Out of Scope

  • The journey spec itself (#17863 owns spec-tier repairs; its park test needs no change — it greens when chips return).
  • Any indicator-menu visual/geometry change.

Decision Record impact

none — restores the shipped affordance contract; no authority change.

Related

  • #17863 (the journey-repair lane whose escalation clause filed this; parent #13158)
  • #17836 (epic)

Origin Session ID: c98e1ede-1a32-46f3-90ef-aaf37def487d Retrieval Hint: "root edge chips descriptor center zone getZoneNodeId unwrap"