Context
A headed Workstation film take retained three orange .film-cursor dots after the
gestures that created them had terminated. The corresponding component/VDOM query
returned no live film cursor. That observation is consistent with a destroyed
component whose directly mounted body node remains physical DOM; it is not evidence
of three intentionally active cursors.
Fresh source verification on origin/dev
a490e5680843a2458746afb2e8e5c03e80a57884 found the same lifecycle mismatch:
Workspace.createFilmCursorDot() creates a Neo.component.Base with
parentId: 'document.body';
- all six Workstation cleanup boundaries call
cursorDot?.destroy() without an
argument;
component.Base.destroy(updateParentVdom=false) removes a document.body node
only when updateParentVdom is true.
The film-cursor mechanism entered through #15933 / PR #15952 and was then consumed
across the completed cross-window journey in #15906 / PR #16035. Those tickets are
resolved history and must remain closed.
The Problem
The cursor component is retired from Neo's component and VDOM surfaces, but the
physical node mounted directly under document.body survives. Each later film
gesture can therefore add another visible orange dot even though semantic queries
correctly report no live cursor.
This makes the capture visually false and creates state that accumulates across
journey legs or repeated takes. It also defeats cleanup assertions that inspect only
component/VDOM truth.
This is not the generic wholesale-refresh race resolved by #14911: the film cursor
has no component parent VDOM to reconcile. Its owner deliberately mounts at
document.body, and the existing Base.destroy(true) contract already provides the
matching removal path.
The Architectural Reality
apps/workstation/view/Workspace.mjs#createFilmCursorDot() owns creation, movement,
window migration, and terminal cleanup of the film-only cursor.
src/component/Base.mjs#destroy(updateParentVdom, silent) owns component retirement.
For parentId === 'document.body', updateParentVdom=true emits the physical
removeNode delta.
- Cleanup occurs at normal completion plus source-to-target cursor migration seams.
A repair must cover every exit without changing the global default semantics of
Base.destroy().
- PR #16144 owns popup-theme first paint and contains no film-cursor delta. This
pre-existing cleanup defect must not expand that ready PR's evidence boundary.
The Fix
- Centralize Workstation film-cursor retirement behind one idempotent cleanup seam.
- Retire body-mounted cursor components with
destroy(true) at every normal,
cancellation, error, and cross-window migration boundary.
- Preserve the current one-cursor handoff during a source-to-target window migration:
remove the old physical node before publishing the replacement.
- Add focused lifecycle coverage for semantic and physical cleanup, plus a headed
repeated-gesture witness using the repository's explicit E2E config.
Contract Ledger
| Target surface |
Source of authority |
Required behavior |
Fallback |
Docs |
Evidence |
| Active film cursor |
Workspace.createFilmCursorDot() |
At most one physical .film-cursor exists during a film gesture, in the current owning window |
No cursor when film presentation is disabled |
Existing method JSDoc |
Focused lifecycle test + headed DOM count |
| Cursor retirement |
Workstation cleanup seam + Base.destroy(true) |
Component, VDOM, and physical body node all retire at every terminal |
Idempotent no-op when already absent |
Cleanup-seam JSDoc |
Success/cancel/error/migration matrix |
| Ordinary app path |
Existing film-mode gate |
Non-film runs create no cursor and observe no cleanup side effects |
Current behavior |
N/A |
Negative control |
Decision Record impact
None. This is a Workstation-local lifecycle correction using the existing component
destruction contract.
Acceptance Criteria
Out of Scope
- Popup first-paint/theme staging in PR #16144.
- The parked-source cross-window settlement repair under #16123 / PR #16140.
- Editing cursor residue out during video composition.
- Changing
component.Base.destroy() defaults framework-wide.
- Reopening resolved
#14911, #15906, or #15933.
Avoided Traps
- Removing
.film-cursor through an ad-hoc querySelector().remove() while leaving
component lifecycle truth split.
- Fixing only the final
finally block and missing cross-window cursor replacement.
- Treating a zero-result VDOM query as proof that physical DOM is gone.
- Folding an unrelated pre-existing film helper defect into PR #16144.
Related
Related: #15933, #15906, #14911, #16113.
PR lineage: #15952, #16035, #16144.
Live latest-open sweep: checked the latest 20 open issues and the recent A2A claim
window at 2026-07-29T21:21Z; no equivalent owner or in-flight claim existed.
Origin Session ID: 019fac4d-7844-7422-9486-7f73ccf308f5
Retrieval Hint: "Workstation film cursor destroy true document.body orange dots orphan DOM"
Authored by Emmy (GPT-5.6 Sol Ultra, Codex).
Context
A headed Workstation film take retained three orange
.film-cursordots after the gestures that created them had terminated. The corresponding component/VDOM query returned no live film cursor. That observation is consistent with a destroyed component whose directly mounted body node remains physical DOM; it is not evidence of three intentionally active cursors.Fresh source verification on
origin/deva490e5680843a2458746afb2e8e5c03e80a57884found the same lifecycle mismatch:Workspace.createFilmCursorDot()creates aNeo.component.BasewithparentId: 'document.body';cursorDot?.destroy()without an argument;component.Base.destroy(updateParentVdom=false)removes adocument.bodynode only whenupdateParentVdomis true.The film-cursor mechanism entered through
#15933/ PR #15952 and was then consumed across the completed cross-window journey in#15906/ PR #16035. Those tickets are resolved history and must remain closed.The Problem
The cursor component is retired from Neo's component and VDOM surfaces, but the physical node mounted directly under
document.bodysurvives. Each later film gesture can therefore add another visible orange dot even though semantic queries correctly report no live cursor.This makes the capture visually false and creates state that accumulates across journey legs or repeated takes. It also defeats cleanup assertions that inspect only component/VDOM truth.
This is not the generic wholesale-refresh race resolved by
#14911: the film cursor has no component parent VDOM to reconcile. Its owner deliberately mounts atdocument.body, and the existingBase.destroy(true)contract already provides the matching removal path.The Architectural Reality
apps/workstation/view/Workspace.mjs#createFilmCursorDot()owns creation, movement, window migration, and terminal cleanup of the film-only cursor.src/component/Base.mjs#destroy(updateParentVdom, silent)owns component retirement. ForparentId === 'document.body',updateParentVdom=trueemits the physicalremoveNodedelta.Base.destroy().The Fix
destroy(true)at every normal, cancellation, error, and cross-window migration boundary.Contract Ledger
Workspace.createFilmCursorDot().film-cursorexists during a film gesture, in the current owning windowBase.destroy(true)Decision Record impact
None. This is a Workstation-local lifecycle correction using the existing component destruction contract.
Acceptance Criteria
destroy(true); no globalBase.destroy()default changes..film-cursor..film-cursorDOM nodes and no live film-cursor component/VDOM residue.test/playwright/playwright.config.e2e.mjs; the default unqualified Playwright command is not used.Out of Scope
component.Base.destroy()defaults framework-wide.#14911,#15906, or#15933.Avoided Traps
.film-cursorthrough an ad-hocquerySelector().remove()while leaving component lifecycle truth split.finallyblock and missing cross-window cursor replacement.Related
Related: #15933, #15906, #14911, #16113.
PR lineage: #15952, #16035, #16144.
Live latest-open sweep: checked the latest 20 open issues and the recent A2A claim window at
2026-07-29T21:21Z; no equivalent owner or in-flight claim existed.Origin Session ID:
019fac4d-7844-7422-9486-7f73ccf308f5Retrieval Hint:
"Workstation film cursor destroy true document.body orange dots orphan DOM"Authored by Emmy (GPT-5.6 Sol Ultra, Codex).