Context
Workspace supplies its own previewFor/clearPreview/hitTest wiring to Participation, plus resolveCrossWindowPreviewSurface, ensureCrossWindowPreviewGeometry, renderCrossWindowPreview, renderMainCrossWindowPreview and a geometry map. Participation now owns resolveAffordances, default preview/clear paths and complete renderer/indicator composition when a real workspace is supplied. Full PopupWorkspace owners also exist.
This is a leaf of the dedicated Workstation consumer-refactoring epic. The shared outcome is a ≤1,000-total-line composition entrypoint with working ordinary docking and optional scripted demos.
The Problem
The named responsibility currently lives on apps/workstation/view/Workspace.mjs, coupling its state, caller surface and teardown to the root. This leaf removes that responsibility and updates its actual callers in the same PR; it does not create a forwarding method for every moved member.
The Architectural Reality
Placement pre-flight: first prefer deletion/adoption through existing src/dashboard/dock/window/Participation and interaction owners; no new engine file is prescribed. App policy, if demonstrated, uses an ordinary registered app owner rather than a raw helper. The actual popup owner is already apps/workstation/view/PopupWorkspace.
Final placement: the saved-home policy is apps/workstation/window/Participation.mjs, mirroring the engine Participation namespace and extending that registered class. The earlier view/PopupWorkspace reference identifies the existing rendered popup owner; the non-view policy belongs in the app's window domain.
The Fix
Bind Participation to the actual main or popup Workspace and use its existing preview/clear/geometry ownership where the production behavior is equivalent. Trace each Workstation override before removal, including the stored-home/native-drag behavior and shared main renderer. Keep only a small, evidenced product-policy difference as an explicit policy surface. If an engine primitive cannot express a required behavior, file/link the owning engine gap and block the dependent consumer cut rather than recreating the pipeline in another app helper.
Contract Ledger Matrix
| Target surface |
Source of authority |
Proposed behavior |
Fallback |
Docs |
Evidence |
| Cross-window preview selection and rendering |
Workspace.createCrossWindowParticipation and preview methods; Participation.resolveAffordances/defaultClearPreview |
Existing engine owner supplies preview, indicator and geometry lifecycle |
Current fail-closed target refusal |
Retained policy JSDoc |
Main/popup preview-to-drop symmetry and cancellation |
| Overlay ownership |
Main dragAffordances and full PopupWorkspace |
Reuse or own each renderer/controller exactly once |
Destroying participation releases only what it owns |
Creator/borrower contract |
Overlay identity and cleanup assertions |
Acceptance Criteria
Out of Scope
New arbitration, Group ownership, operation semantics, broad Participation redesign, and unrelated engine bug fixes.
Avoided Traps
No giant helper or copied post-setup factory descriptors; no shadow Group/document registry; no feature removal or weakened assertions to meet the size bar. Preserve explicit creator/borrower cleanup. Existing defects require baseline attribution rather than a false green.
Decision Record impact
Aligned with ADR 0029; preserves existing engine operation, Group, projection and native-window contracts. Any required engine-contract change must be separately authorized and linked before this leaf merges.
unowned-rationale: available for a peer to self-select after intake and dependency checks; the epic steward tracks the outcome without pre-assigning a batch.
Origin Session ID: 153d2e0a-ebae-4087-bcd5-3c92d213132c
Parent: #18456
Related: #18304 · #18303
Creation sweep: latest 20 open issues and all-state recent A2A rechecked immediately before creation; no duplicate of this leaf. Problem-specific MC recall checked; current source and the parent contract govern where recall is irrelevant. Own assignments were checked for this same Workstation epic; existing film/guide tickets retain their separate scope.
Source disposition for pickup
Revalidated at dev 6648921e48581cea97ab01d6f8509c0217aac218: the inspected Workspace, PopupWorkspace and Participation blobs match the source used below. This is a source map, not runtime equivalence evidence.
| Existing app surface |
Existing engine owner / disposition to verify |
Main affordances: me.dragAffordances |
Participation.resolveAffordances already returns the supplied controller; preserve this borrowing and prove refresh/destruction does not duplicate or destroy it. |
Popup workspace: null and manual preview/geometry pipeline |
Full PopupWorkspace instances already exist as state.host; bind the actual workspace so Participation can compose its defaults. Reconcile the old state.preview / state.indicators users before deleting their producer. |
| Main indicator-feed callback and ordinary on-zone rendering |
Participation.defaultPreviewFor uses ensureGeometry → resolvePreview → renderPreview. Prove indicator readiness and exact target identity through this default path before cutting the copied feed. |
| Main off-zone stored-home fallback |
Workstation.renderMainCrossWindowPreview falls back to the stored-home tabs center after resolvePreview returns null. Participation.defaultPreviewFor has no such branch. Preserve or explicitly disposition this app policy; a successful ordinary hover is not proof of equivalence. |
| Popup fallback to tab-into at the whole landing surface's center |
Workstation.renderCrossWindowPreview deliberately centers its fallback pointer while hovered indicators retain alternatives. Compare this with the default pointer-driven resolver on a real full popup; do not silently change the native drop policy. |
| Geometry map and clear/rebind state |
Engine defaults clear their supplied/owned controller. The current app callback additionally deletes its geometry entry and may clear an uncommitted popup document. Trace those writes and callers before removing them; visual cleanup alone does not prove lifecycle equivalence. |
The policy exceptions above do not justify moving the complete preview pipeline into a new app helper. Keep a surviving product difference small and explicit, with the existing preview/commit symmetry witnesses as the acceptance authority.
Context
Workspace supplies its own previewFor/clearPreview/hitTest wiring to Participation, plus resolveCrossWindowPreviewSurface, ensureCrossWindowPreviewGeometry, renderCrossWindowPreview, renderMainCrossWindowPreview and a geometry map. Participation now owns resolveAffordances, default preview/clear paths and complete renderer/indicator composition when a real workspace is supplied. Full PopupWorkspace owners also exist.
This is a leaf of the dedicated Workstation consumer-refactoring epic. The shared outcome is a ≤1,000-total-line composition entrypoint with working ordinary docking and optional scripted demos.
The Problem
The named responsibility currently lives on
apps/workstation/view/Workspace.mjs, coupling its state, caller surface and teardown to the root. This leaf removes that responsibility and updates its actual callers in the same PR; it does not create a forwarding method for every moved member.The Architectural Reality
Placement pre-flight: first prefer deletion/adoption through existing src/dashboard/dock/window/Participation and interaction owners; no new engine file is prescribed. App policy, if demonstrated, uses an ordinary registered app owner rather than a raw helper. The actual popup owner is already apps/workstation/view/PopupWorkspace.
Final placement: the saved-home policy is
apps/workstation/window/Participation.mjs, mirroring the engine Participation namespace and extending that registered class. The earlierview/PopupWorkspacereference identifies the existing rendered popup owner; the non-view policy belongs in the app's window domain.The Fix
Bind Participation to the actual main or popup Workspace and use its existing preview/clear/geometry ownership where the production behavior is equivalent. Trace each Workstation override before removal, including the stored-home/native-drag behavior and shared main renderer. Keep only a small, evidenced product-policy difference as an explicit policy surface. If an engine primitive cannot express a required behavior, file/link the owning engine gap and block the dependent consumer cut rather than recreating the pipeline in another app helper.
Contract Ledger Matrix
Acceptance Criteria
Out of Scope
New arbitration, Group ownership, operation semantics, broad Participation redesign, and unrelated engine bug fixes.
Avoided Traps
No giant helper or copied post-setup factory descriptors; no shadow Group/document registry; no feature removal or weakened assertions to meet the size bar. Preserve explicit creator/borrower cleanup. Existing defects require baseline attribution rather than a false green.
Decision Record impact
Aligned with ADR 0029; preserves existing engine operation, Group, projection and native-window contracts. Any required engine-contract change must be separately authorized and linked before this leaf merges.
unowned-rationale: available for a peer to self-select after intake and dependency checks; the epic steward tracks the outcome without pre-assigning a batch.
Origin Session ID: 153d2e0a-ebae-4087-bcd5-3c92d213132c
Parent: #18456 Related: #18304 · #18303
Creation sweep: latest 20 open issues and all-state recent A2A rechecked immediately before creation; no duplicate of this leaf. Problem-specific MC recall checked; current source and the parent contract govern where recall is irrelevant. Own assignments were checked for this same Workstation epic; existing film/guide tickets retain their separate scope.
Source disposition for pickup
Revalidated at dev
6648921e48581cea97ab01d6f8509c0217aac218: the inspected Workspace, PopupWorkspace and Participation blobs match the source used below. This is a source map, not runtime equivalence evidence.affordances: me.dragAffordancesworkspace: nulland manual preview/geometry pipelinePopupWorkspaceinstances already exist asstate.host; bind the actual workspace so Participation can compose its defaults. Reconcile the oldstate.preview/state.indicatorsusers before deleting their producer.The policy exceptions above do not justify moving the complete preview pipeline into a new app helper. Keep a surviving product difference small and explicit, with the existing preview/commit symmetry witnesses as the acceptance authority.