LearnNewsExamplesServices
Frontmatter
id16145
titleWorkstation film cursor cleanup leaves orphan DOM
stateClosed
labels
bugaitestingregression
assigneesneo-gpt
createdAtJul 29, 2026, 11:22 PM
updatedAtJul 30, 2026, 1:33 AM
githubUrlhttps://github.com/neomjs/neo/issues/16145
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 30, 2026, 1:33 AM

Workstation film cursor cleanup leaves orphan DOM

Closed Backlog/active-chunk-10 bugaitestingregression
neo-gpt-emmy
neo-gpt-emmy commented on Jul 29, 2026, 11:22 PM

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

  1. Centralize Workstation film-cursor retirement behind one idempotent cleanup seam.
  2. Retire body-mounted cursor components with destroy(true) at every normal, cancellation, error, and cross-window migration boundary.
  3. Preserve the current one-cursor handoff during a source-to-target window migration: remove the old physical node before publishing the replacement.
  4. 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

  • One centralized Workstation cleanup seam retires a film cursor through destroy(true); no global Base.destroy() default changes.
  • During a film gesture, each participating window contains at most one physical .film-cursor.
  • After successful completion, cancellation, thrown/error cleanup, and every source-to-target cursor migration, the retired window contains zero .film-cursor DOM nodes and no live film-cursor component/VDOM residue.
  • A window migration removes the old body node before or atomically with creating the replacement; no multi-dot frame is admitted as intentional overlap.
  • Two consecutive representative film gestures in one application session end with zero cursor residue and do not accumulate dots.
  • A non-film negative control creates no cursor and preserves current gesture behavior.
  • The focused headed witness uses test/playwright/playwright.config.e2e.mjs; the default unqualified Playwright command is not used.

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).

tobiu referenced in commit 0d8e23e - "fix(workstation): retire film cursor physical nodes (#16145) (#16147) on Jul 30, 2026, 1:33 AM
tobiu closed this issue on Jul 30, 2026, 1:33 AM