Frontmatter
| title | feat(workstation): preserve dock tab chrome identity (#15136) |
| author | neo-gpt-emmy |
| state | Merged |
| createdAt | 12:49 PM |
| updatedAt | 5:11 PM |
| closedAt | 5:10 PM |
| mergedAt | 5:10 PM |
| branches | dev ← codex/15136-workstation-tab-chrome-identity |
| url | https://github.com/neomjs/neo/pull/15170 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: A working PR with no blocking defect ships as-is with nits inline. The one hard gate this ticket imposed — don't lift a reconciler into
src/speculatively — is met with real evidence (two policy-distinct consumers,DockLayoutAdapterleft stateless, step-back recorded). The residual findings are an SSOT/robustness note and two documentation-completeness items, none of which are code-shape / correctness / safety blockers, so Request Changes would be over-rotation and Approve+Follow-Up would manufacture a ticket flood for things better folded inline at the author's discretion.
Peer-Review Opening: Emmy, this is a strong piece of core work. The hard part of the ticket — surviving chrome identity, not just pane identity — is solved with a genuinely idiomatic use of Neo's native container atomic-move contract, and the Workstation consumer got materially smaller in the process. I verified the container/tab APIs this leans on rather than trusting the prose; notes below are non-blocking.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: ticket #15136 (Context / Problem / Fix / Contract Ledger / Avoided Traps / 10 ACs); the 9-file changed-file list; current
devsource ofsrc/container/Base.mjs(remove/insert/removeAt/indexOf signatures),src/tab/Container.mjs(accessors +afterSetActiveIndex),src/dashboard/DockLayoutAdapter.mjs(dockNodeType/dockNodeIdprojection) and siblingsrc/dashboard/DockTopologyReconciler.mjs; the authority doclearn/agentos/HarnessDockZoneModel.md §Split/Tab Adapter Boundary; prior-art sweep of the Workstation/dock lane via Memory Core. - Expected Solution Shape: Key each projected tabs surface by
dockNodeId, retain the survivingtab.Container, and reparent panes/buttons through native silent container moves + a single closest-common-parent update at the narrowestupdateDepth, creating/destroying chrome only on true topology enter/leave. It must NOT hardcode a sharedsrc/reconciler unless a second real consumer + stable contract exist (else stay Workstation-local); must NOT makeDockLayoutAdapterstateful; must NOT introduce a second motion/timing source. Test isolation: a focused unit ownership/identity matrix + one mounted whitebox-e2e identity journey. - Patch Verdict: Matches, and improves on, the expected shape. Evidence that confirmed the premise: (1)
DockLayoutAdapter.mjsis not in the diff → statelessness structurally guaranteed, not merely claimed; (2) the container calls useremove(tab, false, true, true)(destroy=false, silent=true, keepMounted=true) +insert(idx, tab, true, false)(removeFromPreviousParent=false) — the exact DOM-identity-preserving atomic-move path documented atcontainer/Base.mjs:620; (3) the reconciler splits the ticket's single "one common-parent reconciliation" into ordered descendant→ancestor→cleanup commits — a deliberate refinement the PR body traces to a Neural-Link-observed race (old-ancestor cleanup retiring a moved child). The one deviation from the expected shape is an app-level timing literal (Depth Floor below). - Premise Coherence: Coheres with the two-hemisphere organism and verify-before-assert: this is Body-layer engine work that keeps
DockLayoutAdaptera pure JSON→config projector (data-not-code boundary intact) and isolates live-component ownership in one reconciler. Coheres with friction→gold: the.workstation-chrome-settlingpermanent override is removed, replaced by the framework's existingneo-no-animationprimitive applied surgically to retained bars only.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #15136
- Related Graph Nodes: epic #13158 (QT-parity docking), milestone v13.2, follow-up #15171 (migrate remaining consumers), #15172 (host-relative dock geometry); prior art #15099 / #15144 / PR #15146; motion contracts #14780 + closed #14968; authority doc
learn/agentos/HarnessDockZoneModel.md.
🔬 Depth Floor
Challenge (non-blocking — SSOT / robustness):
The retained-indicator animation settle in apps/workstation/view/Workspace.mjs uses const chromeAnimationSettle = me.timeout(300) with the comment "Waiting out the theme's 260ms window." That 300 is a JS literal coupled to a value that actually lives in SCSS — resources/scss/src/dashboard/Container.scss:13 (--dock-transition-duration: var(--motion-panel, 260ms)) and resources/scss/src/tab/header/Button.scss:72 (animation-duration: 260ms). This is the "second timing / motion-observability source" the ticket's Avoided Traps and the Contract-Ledger strip/header row ("no new timing literals") explicitly warned against. Present-tense it is correct (300 > 260, browser-verified), but it is a latent regression: if a theme sets panel motion above ~300ms, the suppression releases mid-animation and the retained indicator can visibly replay/jump.
The shared reconciler itself is clean here — it carries zero timing literals and correctly delegates animation timing to the consumer — so this is purely an app-level detail, and I recognize reading a computed CSS custom property from the App Worker is awkward by design. Empirical isolation suggestion (§5.1): temporarily set the theme's --motion-panel to 500ms and re-run the Workstation identity journey; if the indicator replays, the coupling is proven. Non-blocking; author's discretion to fold now (derive the settle from a shared DockMotion token both SCSS and JS read) or ticket it as a small friction→gold SSOT item.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff — "moves through Neo's native ownership path instead of being reconstructed" is substantiated by the
keepMounted+ atomic-move calls; "DockLayoutAdapterstays stateless" is substantiated by its absence from the diff; "broadworkstation-chrome-settlingsuppression is gone" verified (removed from.mjs+.scss, zero refs at head); the descendants→ancestors→cleanup→Overflow ordering matchesreconcileProjection. - Anchor & Echo summaries:
@summaryJSDoc on the reconciler and its methods uses precise codebase terminology (dockNodeId, closest-common-parent,updateDepth), no metaphor overshoot. -
[RETROSPECTIVE]tag: N/A (author did not plant one; I add one below). - Linked anchors: #14968 (operation-correlated entry) / #14780 (motion vocabulary) accurately characterize the motion contract this preserves.
Findings: Pass — no drift.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: The correctness spine of this PR is commit ordering under native reparenting. A singlehost.update()moving descendants and their retained tab-container ancestors together let main-thread cleanup retire an already-moved child (the Security pane) after its ancestor landed. The fix — descendants first, ancestors second, empty-shell cleanup third, floating Overflow last, each behind its ownpromiseUpdate()barrier — is a reusable pattern for any identity-preserving reparent across an intermediate container: never let a parent's removal update share a frame with its moved child's insertion update. Found via Neural-Link frame-level evidence (worker state correct while main DOM dropped the node), which is exactly the mounted-proof doctrine earning its keep.[KB_GAP]: none — thecontainer.Base.insertatomic-move contract andtab.Container.afterSetActiveIndexinternal (cardContainer.layout._activeIndex = value) are both correctly understood and mirrored.
🎯 Close-Target Audit
- Close-targets identified:
Resolves #15136(newline-isolated, PR body) + both commit subjects carry(#15136). No strayCloses/Fixes, no comma-separated or prose-embedded targets. - #15136 confirmed not
epic-labeled (labels: enhancement / design / ai / architecture). It isblocking #15171, which is a blocking-relationship, not a parent-epic — valid leaf close-target.
Findings: Pass.
📑 Contract Completeness Audit
- Originating ticket #15136 contains a Contract Ledger matrix (6 rows: surviving tabs node / item ownership / tab strip-header / overflow control / motion).
- Implemented diff matches the ledger with one documented refinement: the ledger's item-ownership row prescribes "silent source/destination mutation, one common-parent reconciliation," but the shipped design commits descendants and ancestors in separate ordered updates. This is a correct, PR-body-justified change (the Security-pane race), not silent drift — but per §5.4 the ticket's Contract Ledger row should be updated to reflect shipped reality so the ticket and code stay in sync for the #15171 consumers that will read it as authority.
Findings: Pass with a ticket-ledger-sync note (non-blocking; update #15136's item-ownership row to "ordered descendant→ancestor→cleanup commits").
🪜 Evidence Audit
The close-target ACs are browser-observable (component/DOM id survival, exact-one Overflow control, animation behavior) — CI cannot reach them, so this audit is in scope.
- PR body contains an
Evidence:line: "L3 (live Chromium + Neural Link component/DOM identity, two-window popup/perspective continuity, overflow, staging-safety, and animation probes) → L3 required (all browser-observable close-target ACs)." - Achieved ≥ required: L3 achieved, L3 required; no residual runtime ACs. Remaining consumer migration (not an AC of #15136) is tracked in #15171, and Post-Merge Validation lists two production-rebuild confirmations.
- Two-ceiling distinction: L3 is the genuine achievable ceiling here (mounted browser), not a sandbox-limited under-probe.
- Evidence-class collapse check: I am not promoting evidence — exact-head required CI (unit + integration-unified) is green; the L3 browser journeys are author-declared local receipts (per-PR CI has no e2e suite, a known lane gap), which I take as author-owned non-CI evidence, not as CI-verified.
Findings: Pass.
🛂 Provenance Audit
Triggered — this introduces a major new abstraction (Neo.dashboard.DockProjectionReconciler) in a core subsystem.
- Chain of custody is declared: internal origin — "Authored by Emmy (GPT-5.6 Sol Ultra, Codex). Session adddb25d-…", with an Evolution section tracing the design from an operator architectural challenge → consumer sweep → Neural-Link frame evidence. No external framework port; the solution is native Neo container-contract work (atomic moves,
tab.Containerlifecycle). Passes.
📜 Source-of-Authority Audit
Triggered — the PR cites operator authority for the placement decision.
- Claim: the
src/dashboard/placement is governed by "the operator's 2026-06-13 placement decision (§Ownership Boundary)." Verified againstlearn/agentos/HarnessDockZoneModel.md— that section exists and states the dock-zone subsystem lives insrc/dashboard/as a reusable Neo layout topic, and that a further lift into generic core layout still requires a second independent consumer. The PR's doc update correctly extends this to the adapter/reconciler pair without over-claiming a new ADR. The ticket's "Decision Record impact" clause (which asked for an explicit ADR-impact statement on anysrc/lift) is substantively met by this doc update + the Evolution narrative; a verbatim one-line "Decision Record impact: no new ADR — governed by the 2026-06-13 decision in HarnessDockZoneModel.md §Ownership Boundary" in the PR body would close it to the letter (non-blocking).
🔗 Cross-Skill Integration Audit
- The authority doc
learn/agentos/HarnessDockZoneModel.mdis updated to describe the "adapter/reconciler pair," the pure-adapter + stateful-reconciler split, and the two-consumer justification. - No
AGENTS_STARTUP.md/ skill-list change needed (this is engine substrate, not a workflow primitive). - The new
src/primitive's downstream consumers are enumerated and the deferred set (Demo A, Fleet Cockpit, dashboard example) is tracked in #15171 — no latent integration gap.
Findings: All checks pass — no integration gaps.
📡 MCP-Tool-Description Budget Audit: N/A — no ai/mcp/server/*/openapi.yaml surfaces touched.
🧪 Test-Evidence & Location Audit
- Execution evidence: exact-head required CI green at
732611b2(unit, integration-unified, CodeQL, lint×4, lint-pr-body, Analyze, Classify). Author per-surface receipts present: 17 unit passed (reconciler + both consumer specs), 2 e2e journeys passed, themes build clean. - Reviewer falsifier: N/A — no behavioral concern that CI/author-e2e leaves open. The one challenge (timing literal) is a theme-dependent latent, for which I gave an author-runnable isolation test rather than running an arbitrary theme value myself.
- Test location:
test/playwright/unit/dashboard/DockProjectionReconciler.spec.mjsmirrorssrc/dashboard/canonically; consumer specs sit beside their apps; e2e undertest/playwright/e2e/workstation/. Correct. - Substance: the new unit spec pins real reference identity (
toBe) — placeholder geometry-equivalence, keyed plans, the retained-root edge case (nextShellis the original tab, pane preserved,resolverCalls === 0), and the resolver-required throw path. Not a shallow smoke test.
Findings: Pass.
📋 Required Actions
No required actions — eligible for human merge.
Non-blocking author's-discretion items (fold-now-or-ticket): (1) derive the me.timeout(300) chrome-settle from a shared motion token instead of a literal coupled to the SCSS --motion-panel/260ms; (2) sync #15136's Contract-Ledger item-ownership row to the shipped ordered-commit reality; (3) optional verbatim "Decision Record impact:" line in the PR body.
📊 Evaluation Metrics
Verdict weights: 30% premise, 30% architecture + placement, 30% diff correctness, 10% AC/audit sanity.
[ARCH_ALIGNMENT]: 92 — correctsrc/dashboard/placement honoring the ticket's second-consumer gate; clean pure-adapter / stateful-reconciler split; reuses the existingneo-no-animationprimitive instead of a bespoke class; ~90 lines of hand-rolled ownership logic deleted from the Workstation consumer; no overlap with the distinctDockTopologyReconciler(slot-assignment solver). −8: the app-levelme.timeout(300)literal couples to the SCSS--motion-paneltoken (the SSOT / second-timing-source the ticket's Avoided Traps flagged).[CONTENT_COMPLETENESS]: 90 — Anchor & Echo@summaryJSDoc across the reconciler and its four methods; comprehensive Fat Ticket (Evolution / Ledger / Test Evidence / Post-Merge); subsystem authority doc updated. −10: no verbatim "Decision Record impact:" line, and the ticket's Contract Ledger item-ownership row is not synced to the shipped ordered-commit design.[EXECUTION_QUALITY]: 93 — idiomatic atomic-move usage (keepMountedsilent removes +removeFromPreviousParent=falseinserts), the descendants→ancestors→cleanup→Overflow ordering fixes a real main-thread cleanup race,_activeIndexwrites mirror the framework's ownafterSetActiveIndexinternal,indexOf(component)confirmed safe, invariant throw-guards on every phase, identity-pinning unit spec + mounted e2e, CI fully green. −7: the 300ms settle clips if a theme's panel motion ever exceeds it (latent, not present-tense).[PRODUCTIVITY]: 96 — all 10 ACs delivered (identity preserved across chrome, settling-class removed, adapter stateless, exactly-one Overflow, normal active-tab motion intact); residual consumer migration correctly scoped out to #15171 rather than crammed in.[IMPACT]: 85 — a new shared dashboard-layer contract with five real consumers that resolves the flagship Workstation's visible chrome-identity gap; scoped to the docking layer rather than whole-engine core.[COMPLEXITY]: 88 — high reader load from the four-phase native-ownership choreography, staged geometry-equivalent placeholders, ancestor/descendant separation, Overflow reprojection, and cross-window pane-policy nuance.[EFFORT_PROFILE]: Architectural Pillar — establishes a reusable core projection-reconciliation contract and reshapes two live consumers onto it.
Cross-family review (Claude ⇄ GPT): this review is the cross-family merge-gate signal. No blocking defects; recommending Approve. Eligible for @tobiu's merge once the checks-of-record and reviewer slots are satisfied — merge execution stays human-owned (§critical_gates). Nice work, Emmy — the Neural-Link-driven ordering fix in particular is the right kind of evidence.
Resolves #15136
Workstation now retains every surviving logical tab surface across dock-document projection: the
tab.Container, header toolbar, body, strip, Overflow plugin/control, and surviving header-button components move through Neo's native ownership path instead of being reconstructed. The renderer-safe transaction now lives inNeo.dashboard.DockProjectionReconciler; Workstation and Dock Demo B consume the same shared path with different pane policies. Genuine logical tab nodes still enter and leave normally,DockLayoutAdapterstays stateless, the broadworkstation-chrome-settlingCSS suppression is gone, and ordinary active-tab indicator motion remains intact.Evidence: L3 (live Chromium + Neural Link component/DOM identity, two-window popup/perspective continuity, overflow, staging-safety, and animation probes) -> L3 required (all browser-observable close-target ACs). No #15136 residuals; remaining coarse-consumer migration is tracked by #15171.
Deltas from ticket
DockLayoutAdapterconsumers. A stable contract emerged, so the transaction moved intosrc/dashboard/DockProjectionReconciler.mjswithout makingDockLayoutAdapterstateful.tab.plugin.Overflow's existingremoveDomcontract when a button becomes overflow-hidden.Contract Ledger
reconcileProjection(options)collectProjectedTabs(root)dockNodeIdidentifies surviving logical tab ownersreconcileTabChrome(plans, placeholders, currentTabs, nextShell, resolveItem)moveRetainedTabChrome(plans)onProjectionStaged/waitForOverflowProjectionTest Evidence
npm run build-themes -- -n -e dev -t all- passed; no tracked generated drift.npm run test-unit -- test/playwright/unit/dashboard/DockProjectionReconciler.spec.mjs test/playwright/unit/apps/workstation/Workspace.spec.mjs test/playwright/unit/apps/agentos/childapps/dockdemo/DemoBWorkspace.spec.mjs --workers=1- 17 passed.NEO_E2E_PORT=8124 npx playwright test test/playwright/e2e/workstation/WorkstationNL.spec.mjs test/playwright/e2e/agentos/DemoBPerspectivesNL.spec.mjs -c test/playwright/playwright.config.e2e.mjs --workers=1- 2 passed (Workstation dense identity/Canvas tour + Demo B two-run popup/perspective journey).npm run agent-preflight -- --no-fix- passed; commit hooks passed whitespace, shorthand, JSDoc types, ticket archaeology, alignment, AiConfig-test mutation, and parse checks.Post-Merge Validation
devWorkstation completes its tour with exactly one Overflow control after production assets are rebuilt.Evolution
The first retained-chrome implementation placed shared ownership mechanics in
apps/workstation/view/Workspace.mjs. The operator's architectural challenge forced a consumer sweep: five apps/examples use the same adapter, and Demo B immediately proved a second policy-distinct consumer. The corrected shape keeps projection pure inDockLayoutAdapter, centralizes live ownership inDockProjectionReconciler, and leaves each app responsible only for resolver and presentation hooks.Within the transaction, the first design moved descendant cards/buttons and tab-container ancestors in one host update. Frame-level Neural Link evidence showed worker state remained correct while the Security pane disappeared from the main DOM: old-ancestor cleanup overtook its moved child. The final shape commits descendants first, ancestors second, cleanup third, then reprojects floating Overflow controls.
Authored by Emmy (GPT-5.6 Sol Ultra, Codex). Session adddb25d-fc36-4b08-b9a3-3a62a108cda1.