⚠ Authority correction (2026-07-16, from PR #15237's Drop+Supersede review)
The original premise below is FALSE and struck: "the agentos DockPreview consumes --fm-* tokens the workstation theme family already loads — reuse, don't fork". Accepted extraction #15144 removed exactly that coupling: the workstation owns its classes and theme; it loads NO --fm-* tokens and imports NO AgentOS.* classes. Importing the AgentOS renderer (and copying the Demo-A controller into a second app) reverses an accepted boundary — PR #15237 was closed unmerged on this ground plus two exact-head async defects (a cancelled gesture's awaited drop could still commit; the producer was never destroyed).
Superseding scope (this ticket, re-scoped in place):
- Lift the shared authority into
src/dashboard — the preview renderer and the reusable gesture controller (geometry memoization, candidate/preview tiers, release-truth drop, cancel hygiene) become ONE app-neutral owner. Demo-A and the workstation both become thin consumers; the duplicated controller copy this ticket originally prescribed was itself the friction signal.
- Explicit gesture/document-generation ownership: promise-identity re-checks after EVERY await (drop included — the falsified defect), cancel/refresh/destroy teardown symmetry, and producer lifecycle (create/destroy pairing).
- Witness upgrades: hover-zone-A / release-zone-B with the EXACT reducer descriptor asserted (release truth must be discriminated from cached hover); the production measurement path exercised (no injected pre-resolved geometry as the only path); plus a durable current-head real-pointer workstation journey.
- #15144's boundary preserved: no
AgentOS.* imports, no --fm-* consumption in the workstation tree; the affordances style through the settled --agent-dock-preview-* domain aliases each app projects (the PR #14955 / PR #15208 boundary).
- AC-3 (the
previewLanguage re-skin on the flagship) closes only when the combination with #15206's variants is actually demonstrated — never by inference.
D#15204's merge-order step 2 ("settle the active G5/#15207 surfaces") binds to THIS shape.
Context
Found while building the G5 preview-language switch (#15206, D#15204): apps/workstation/view/Workspace.mjs composes the dock host with the projection child ONLY — no DockPreviewProducer, no DockPreview renderer, no DockDropIndicators overlay (grep-verified, zero hits). The dense workstation — the release's flagship surface — therefore renders no drag-preview affordances at all: no §06 indicator menu, no zone-preview fill during a drag. Demo-A (the dockdemo childapp) carries the full pattern; the flagship does not.
This is a product capability gap independent of which preview design language wins the D#15204/G5 selection — the workstation needs the affordance layers under ANY skin. Filed separately from #15206 per scope discipline (the switch leaf must not ratchet a new capability mid-lane); #15206's recording pair lands on Demo-A where the affordances exist today, and the workstation previewLanguage config (already shipped, live-verified) becomes visually consequential the moment this leaf lands.
The Problem
A viewer dragging a tab on the flagship workstation gets proxy motion and commit — but no drop-indicator menu, no target-zone preview: the exact affordances the §06 grammar shipped for Demo-A (#14974) and the wow-tier scenario (D#15204 North Star beats 3–4) presume everywhere. The 20-tab dense scene is precisely where drop-targeting guidance matters most.
The Architectural Reality
The Demo-A composition is the verbatim precedent (apps/agentos/childapps/dockdemo/view/DemoAWorkspace.mjs): DockPreviewProducer created at construct; DockPreview (the renderer) + DockDropIndicators added as persistent overlay siblings of the projection child (object permanence across re-projections — the surgical removeAt(0)/insert(0) refresh pattern); the projection threads onDockCrossZoneDragMove/onDockCrossZoneDrop (measure-once-per-gesture memoized geometry, candidate-set swap on zone change, indicator-first/pointer-inference-fallback tier order, previewToOperation commits). The workstation's refreshDockWorkspace currently replaces its single child — the surgical refresh is part of this leaf. Renderer + controller authority (per the correction above): BOTH lift into src/dashboard as the app-neutral shared owner; Demo-A and the workstation compose it as thin consumers, styled through the --agent-dock-preview-* domain aliases each app projects (no --fm-* consumption, no AgentOS.* imports in the workstation tree — #15144's boundary).
The Fix
One PR (the superseding scope above): lift the renderer + gesture controller into src/dashboard (DockPreview, DockDragAffordances); convert Demo-A to the thin-consumer composition; compose the same owner on the workstation Workspace as persistent overlay siblings with the surgical refresh; controller witnesses (release truth, move + drop generation guards, the production measurement path, producer create/destroy pairing) plus a durable real-pointer workstation journey.
Contract Ledger
| Target Surface |
Source of Authority |
Behavior |
Fallback / Edge Case |
Docs |
Evidence |
Neo.dashboard.DockDragAffordances (new shared class: clear / ensureGeometry / onDragMove / onDrop / onDragCancel / destroy; consumer duck-type owner = {dockModel, applyDockZoneOperation, onDockZoneDocumentChange}; direct refs host / indicators / preview) |
this ticket's superseding scope + the §06 tier order (ADR 0029 consumer grammar) |
once-per-gesture memoized geometry; per-frame candidate/preview tiers (indicator-first, pointer-inference fallback); release-truth drop through previewToOperation; promise-identity generation guards after EVERY await; producer create/destroy pairing |
null overlay refs = silent no-op per tier; degenerate frames (missing OR zero-area rects) uncache + self-heal next frame; cancelled/re-projected gestures commit nothing |
class JSDoc (contract documented on the class + members) |
test/playwright/unit/dashboard/DockDragAffordances.spec.mjs (release truth, both generation guards, production measurement path incl. the zero-area frame, producer pairing) + the flagship journey |
Neo.dashboard.DockPreview — moved renderer (formerly the agentos tree) + edgeBandSize / splitLineSize instance configs + affordanceGeometry(affordance, targetRect, options) |
#15144 boundary (app-neutral tier) + PR #15282 RA-2 |
per-instance sizing policy flows into positioned geometry; the pure static helper accepts explicit options |
helper defaults (24 / 6) serve bare static calls; band clamps to the target rect, oversized values degrade gracefully |
config + method JSDoc |
DockPreview.spec.mjs sizing witnesses (options + instance override + clamp) + the four-anchor alias census |
Neo.dashboard.DockTabSortZone — dockReleaseTolerance config, releaseVoidsReorder(data), dockSourceToolbarRect snapshot |
PR #15282 RA-1 (the path-reorder revert repair) |
a release beyond the source toolbar's REAL bounds + tolerance voids the tracked within-toolbar reorder so the cross-zone drop owns the outcome |
fail-open: missing snapshot or non-numeric release coords never void (base behavior preserved); cancels untouched; near releases keep the base reorder |
member JSDoc |
DockTabSortZone.spec.mjs release-boundary matrix (near/far × horizontal/vertical + tolerance override + guards) |
Neo.main.draggable.sensor.Base#trigger — detached-source dispatch fallback |
PR #15282 RA-3 (the flagship's measured 23-mousemove/0-drag:move starvation) |
a disconnected dispatch target routes the event to document; consumers read event.detail, never the dispatch target |
connected sources dispatch on the element exactly as before (bubbling unchanged) |
method JSDoc |
test/playwright/unit/main/draggable/sensor/Base.spec.mjs (connected / detached / mid-gesture branch-flip discrimination) |
Acceptance Criteria
Out of Scope
The design-language variants themselves (#15206) · multi-window/tear-out affordances (epic #15239 leaves #15244–#15248) · FM cockpit surfaces.
Related
Parent epic #15239 (re-parented from #13158 at epic graduation, 2026-07-16) · sibling #15206 (the switch + skins; its workstation-recording ambition unblocks here) · precedent #14974 (Demo-A §06 experience) · D#15204 G5 · the #15099→workstation arc.
Live latest-open sweep: latest 6 open at 2026-07-16T06:44Z; "workstation preview indicator overlay" all-states search: nearest #15171 (projection reconciler migration — disjoint). A2A: filed unassigned for self-select (the workstation is Emmy's built surface; the affordance pattern is Demo-A's — either family lifts it cleanly); no competing claim in the herd window.
Origin Session ID: c5d7cd6b-4e01-45fd-aa59-5ccbc0e5f091
Retrieval Hint: "workstation drag affordance overlay DockPreview DockDropIndicators persistent siblings"
⚠ Authority correction (2026-07-16, from PR #15237's Drop+Supersede review)
The original premise below is FALSE and struck:
"the agentos. Accepted extraction #15144 removed exactly that coupling: the workstation owns its classes and theme; it loads NODockPreviewconsumes--fm-*tokens the workstation theme family already loads — reuse, don't fork"--fm-*tokens and imports NOAgentOS.*classes. Importing the AgentOS renderer (and copying the Demo-A controller into a second app) reverses an accepted boundary — PR #15237 was closed unmerged on this ground plus two exact-head async defects (a cancelled gesture's awaited drop could still commit; the producer was never destroyed).Superseding scope (this ticket, re-scoped in place):
src/dashboard— the preview renderer and the reusable gesture controller (geometry memoization, candidate/preview tiers, release-truth drop, cancel hygiene) become ONE app-neutral owner. Demo-A and the workstation both become thin consumers; the duplicated controller copy this ticket originally prescribed was itself the friction signal.AgentOS.*imports, no--fm-*consumption in the workstation tree; the affordances style through the settled--agent-dock-preview-*domain aliases each app projects (the PR #14955 / PR #15208 boundary).previewLanguagere-skin on the flagship) closes only when the combination with #15206's variants is actually demonstrated — never by inference.D#15204's merge-order step 2 ("settle the active G5/#15207 surfaces") binds to THIS shape.
Context
Found while building the G5 preview-language switch (#15206, D#15204):
apps/workstation/view/Workspace.mjscomposes the dock host with the projection child ONLY — noDockPreviewProducer, noDockPreviewrenderer, noDockDropIndicatorsoverlay (grep-verified, zero hits). The dense workstation — the release's flagship surface — therefore renders no drag-preview affordances at all: no §06 indicator menu, no zone-preview fill during a drag. Demo-A (the dockdemo childapp) carries the full pattern; the flagship does not.This is a product capability gap independent of which preview design language wins the D#15204/G5 selection — the workstation needs the affordance layers under ANY skin. Filed separately from #15206 per scope discipline (the switch leaf must not ratchet a new capability mid-lane); #15206's recording pair lands on Demo-A where the affordances exist today, and the workstation
previewLanguageconfig (already shipped, live-verified) becomes visually consequential the moment this leaf lands.The Problem
A viewer dragging a tab on the flagship workstation gets proxy motion and commit — but no drop-indicator menu, no target-zone preview: the exact affordances the §06 grammar shipped for Demo-A (#14974) and the wow-tier scenario (D#15204 North Star beats 3–4) presume everywhere. The 20-tab dense scene is precisely where drop-targeting guidance matters most.
The Architectural Reality
The Demo-A composition is the verbatim precedent (
apps/agentos/childapps/dockdemo/view/DemoAWorkspace.mjs):DockPreviewProducercreated at construct;DockPreview(the renderer) +DockDropIndicatorsadded as persistent overlay siblings of the projection child (object permanence across re-projections — the surgicalremoveAt(0)/insert(0)refresh pattern); the projection threadsonDockCrossZoneDragMove/onDockCrossZoneDrop(measure-once-per-gesture memoized geometry, candidate-set swap on zone change, indicator-first/pointer-inference-fallback tier order,previewToOperationcommits). The workstation'srefreshDockWorkspacecurrently replaces its single child — the surgical refresh is part of this leaf. Renderer + controller authority (per the correction above): BOTH lift intosrc/dashboardas the app-neutral shared owner; Demo-A and the workstation compose it as thin consumers, styled through the--agent-dock-preview-*domain aliases each app projects (no--fm-*consumption, noAgentOS.*imports in the workstation tree —#15144's boundary).The Fix
One PR (the superseding scope above): lift the renderer + gesture controller into
src/dashboard(DockPreview,DockDragAffordances); convert Demo-A to the thin-consumer composition; compose the same owner on the workstation Workspace as persistent overlay siblings with the surgical refresh; controller witnesses (release truth, move + drop generation guards, the production measurement path, producer create/destroy pairing) plus a durable real-pointer workstation journey.Contract Ledger
Neo.dashboard.DockDragAffordances(new shared class:clear/ensureGeometry/onDragMove/onDrop/onDragCancel/destroy; consumer duck-typeowner = {dockModel, applyDockZoneOperation, onDockZoneDocumentChange}; direct refshost/indicators/preview)previewToOperation; promise-identity generation guards after EVERY await; producer create/destroy pairingtest/playwright/unit/dashboard/DockDragAffordances.spec.mjs(release truth, both generation guards, production measurement path incl. the zero-area frame, producer pairing) + the flagship journeyNeo.dashboard.DockPreview— moved renderer (formerly the agentos tree) +edgeBandSize/splitLineSizeinstance configs +affordanceGeometry(affordance, targetRect, options)DockPreview.spec.mjssizing witnesses (options + instance override + clamp) + the four-anchor alias censusNeo.dashboard.DockTabSortZone—dockReleaseToleranceconfig,releaseVoidsReorder(data),dockSourceToolbarRectsnapshotDockTabSortZone.spec.mjsrelease-boundary matrix (near/far × horizontal/vertical + tolerance override + guards)Neo.main.draggable.sensor.Base#trigger— detached-source dispatch fallbackdocument; consumers readevent.detail, never the dispatch targettest/playwright/unit/main/draggable/sensor/Base.spec.mjs(connected / detached / mid-gesture branch-flip discrimination)Acceptance Criteria
previewLanguage(shipped on the Workspace) visibly re-skins the affordances once #15206's variants apply — the side-by-side works on the flagship.--agent-dock-preview-*domain aliases each app projects + the shared motion vocabulary tokens only (no--fm-*in the workstation tree —#15144's boundary).Out of Scope
The design-language variants themselves (#15206) · multi-window/tear-out affordances (epic #15239 leaves #15244–#15248) · FM cockpit surfaces.
Related
Parent epic #15239 (re-parented from #13158 at epic graduation, 2026-07-16) · sibling #15206 (the switch + skins; its workstation-recording ambition unblocks here) · precedent #14974 (Demo-A §06 experience) · D#15204 G5 · the #15099→workstation arc.
Live latest-open sweep: latest 6 open at 2026-07-16T06:44Z; "workstation preview indicator overlay" all-states search: nearest #15171 (projection reconciler migration — disjoint). A2A: filed unassigned for self-select (the workstation is Emmy's built surface; the affordance pattern is Demo-A's — either family lifts it cleanly); no competing claim in the herd window.
Origin Session ID: c5d7cd6b-4e01-45fd-aa59-5ccbc0e5f091 Retrieval Hint: "workstation drag affordance overlay DockPreview DockDropIndicators persistent siblings"