Context
Split from #14930 (scope replacement, not addition): the parent leaf proved to be two PR-sized tiers — the design tier (the §06 authority section + the DockPreview FM/motion-contract polish, delivered by PR #14955, which now resolves the narrowed #14930) and this leaf: the implementation tier that builds the indicator experience the authority specifies. Operator PRIO-0 lineage (2026-07-10): the drag → visible-options → drop loop is the felt Qt-parity floor.
The Problem
The §06 authority decides the interaction model — the indicator-overlay menu as the primary drag affordance — but nothing renders it yet: DockPreviewProducer computes ONE affordance from pointer position, no component renders an option menu, and no showcase films a human drag.
The Architectural Reality
- Producer extension (
src/dashboard/DockPreviewProducer.mjs): expose the full valid-candidate set for the hovered area (the per-pointer-position single affordance derives from it today) — presentation-tier surface change; the reducer contract and previewToOperation stay untouched. Gated on PR #14933 (open), which owns the producer file in flight.
DockDropIndicators (new src/dashboard/ sibling, structural fast-path): renders the candidate set as the §06 menu — 5-position cross on the hovered area + container edge chips; hovering an indicator drives the existing DockPreview target-region highlight; drop commits through previewToOperation unchanged; Escape cancels (absorbs tree line C5, single-window). Real child components on the composition bar (the #14906 precedent), FM tokens, motion via --dock-transition-*.
- The cold-open camera beat: ~15s of human drag with the menu lit on the Demo-A surface, before the tour ("now watch an agent do it") — consumes the two pieces above.
The Fix
One PR per slice as they unblock: producer extension (post-#14933), the indicator component + its unit contract specs, the beat staging on the dockdemo surface. The §06 artifact section is the binding authority for every visual/interaction decision — deltas amend the artifact first.
Acceptance Criteria
Out of Scope
- The design authority + DockPreview polish (delivered: PR
#14955 → the narrowed #14930).
- Grouped-drag proxy (E-tranche) · cross-window drag scenes (
#14772) · the non-FLIP transition classes (#14949, Grace).
Decision Record impact
aligned-with ADR 0029 — presentation tier over the §2.1 seams; the §06 artifact section is the design authority of record.
Related: parent #13158 · split-from #14930 · authority PR #14955 · gates: PR #14933 (producer file) · siblings #14949 (Grace), #14929 (FLIP) · consumer #14591 animation half.
Contract Ledger — neo.harness.dockCandidates.v1 (backfilled per PR #14974 review cycle 1)
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
CANDIDATES_SCHEMA + isValidCandidateSet (src/dashboard/dockPreviewContract.mjs) |
§06 of the dock-choreography artifact (PR #14955) + ADR 0029 §2.3 purity rules |
Fail-closed COMPLETENESS gate: exactly 5 unique cross positions + (when root present) exactly 4 unique chips; every candidate wraps an individually valid dockPreview.v1 carrying the set's itemId, targeting the hovered zone (cross) / root (chips), with placement matching its visual position as a full direction/kind/AXIS tuple per the §06 grammar (crossPlacementMatchesPosition — split kinds must carry vertical from top/bottom, horizontal from left/right; axis hardening per #14983 / PR #14990) |
Any partial/duplicated/mismatched/lying menu → false → the indicator layer hides (never renders a menu misrepresenting the drop) |
JSDoc on the module (Anchor & Echo) |
Unit: producer emissions on BOTH grammar axes = positive controls + 12 negative probes incl. the review falsifiers (one-entry center→edge-left; the four perpendicular-axis split tuples) |
DockPreviewProducer.produceCandidates() |
Same grammar as produce() — ONE shared buildPreview assembly path |
Emits the full menu for the hovered zone; root chips omitted when no root supplied OR hovered zone IS the root |
No zone under pointer / no itemId → null |
JSDoc |
Unit: grammar parity (candidate ≡ pointer preview, deep-equal), fail-closed paths |
DockDropIndicators (consumer) |
Commit-free, pointer-transparent contract (class JSDoc) |
Renders/selects candidates geometrically; never converts or commits |
Invalid set / missing hostRect → cls-hidden |
JSDoc |
Unit: 9 component specs; e2e: the real-pointer §06 journey (DemoADragMenuNL.spec.mjs) |
Drop commit path (DemoAWorkspace.onDockCrossZoneDrop) |
previewToOperation UNCHANGED (ticket AC) |
Release-point re-hit-test (release truth, never cached hover); candidate must carry the gesture's itemId; else pointer-inference fallback; Escape-suppressed → nothing |
No candidate + no geometry → no commit |
JSDoc |
Unit: 3 drop-truth pins (incl. the (999,999) stale-selection falsifier); e2e: commit + Escape journeys |
Live latest-open sweep: latest-20 checked immediately before create; no equivalent (nearest #14949 = the non-FLIP motion classes, disjoint). A2A herd window: no competing claim on the indicator tier.
Origin Session ID: 748f9b8f-20cd-4360-9a29-c3084d059052
Retrieval Hint: "dock drop indicator menu candidate set cross edge chips cold open drag beat"
Context
Split from
#14930(scope replacement, not addition): the parent leaf proved to be two PR-sized tiers — the design tier (the §06 authority section + the DockPreview FM/motion-contract polish, delivered by PR#14955, which now resolves the narrowed#14930) and this leaf: the implementation tier that builds the indicator experience the authority specifies. Operator PRIO-0 lineage (2026-07-10): the drag → visible-options → drop loop is the felt Qt-parity floor.The Problem
The §06 authority decides the interaction model — the indicator-overlay menu as the primary drag affordance — but nothing renders it yet:
DockPreviewProducercomputes ONE affordance from pointer position, no component renders an option menu, and no showcase films a human drag.The Architectural Reality
src/dashboard/DockPreviewProducer.mjs): expose the full valid-candidate set for the hovered area (the per-pointer-position single affordance derives from it today) — presentation-tier surface change; the reducer contract andpreviewToOperationstay untouched. Gated on PR#14933(open), which owns the producer file in flight.DockDropIndicators(newsrc/dashboard/sibling, structural fast-path): renders the candidate set as the §06 menu — 5-position cross on the hovered area + container edge chips; hovering an indicator drives the existingDockPreviewtarget-region highlight; drop commits throughpreviewToOperationunchanged; Escape cancels (absorbs tree line C5, single-window). Real child components on the composition bar (the#14906precedent), FM tokens, motion via--dock-transition-*.The Fix
One PR per slice as they unblock: producer extension (post-
#14933), the indicator component + its unit contract specs, the beat staging on the dockdemo surface. The §06 artifact section is the binding authority for every visual/interaction decision — deltas amend the artifact first.Acceptance Criteria
DockDropIndicatorsrenders cross + edge chips from the candidate set; hover previews the exact target region (accept/reject policy-honest); Escape cancels mid-drag.--dock-transition-*motion; reduced-motion compliant.#14591animation half.Out of Scope
#14955→ the narrowed#14930).#14772) · the non-FLIP transition classes (#14949, Grace).Decision Record impact
aligned-with ADR 0029— presentation tier over the §2.1 seams; the §06 artifact section is the design authority of record.Related: parent
#13158· split-from#14930· authority PR#14955· gates: PR#14933(producer file) · siblings#14949(Grace),#14929(FLIP) · consumer#14591animation half.Contract Ledger —
neo.harness.dockCandidates.v1(backfilled per PR#14974review cycle 1)CANDIDATES_SCHEMA+isValidCandidateSet(src/dashboard/dockPreviewContract.mjs)#14955) + ADR 0029 §2.3 purity rulesdockPreview.v1carrying the set'sitemId, targeting the hovered zone (cross) / root (chips), with placement matching its visual position as a full direction/kind/AXIS tuple per the §06 grammar (crossPlacementMatchesPosition— split kinds must carryverticalfrom top/bottom,horizontalfrom left/right; axis hardening per#14983/ PR#14990)false→ the indicator layer hides (never renders a menu misrepresenting the drop)DockPreviewProducer.produceCandidates()produce()— ONE sharedbuildPreviewassembly pathnullDockDropIndicators(consumer)DemoADragMenuNL.spec.mjs)DemoAWorkspace.onDockCrossZoneDrop)previewToOperationUNCHANGED (ticket AC)itemId; else pointer-inference fallback; Escape-suppressed → nothingLive latest-open sweep: latest-20 checked immediately before create; no equivalent (nearest
#14949= the non-FLIP motion classes, disjoint). A2A herd window: no competing claim on the indicator tier.Origin Session ID: 748f9b8f-20cd-4360-9a29-c3084d059052 Retrieval Hint: "dock drop indicator menu candidate set cross edge chips cold open drag beat"