Context
The visual-repair pass on #15099 proved that Demo C already preserves the hard part of docking permanence: DemoCWorkspace.refreshDockWorkspace() stages the destination shell, moves cached live pane instances through Neo's native container ownership path, and reconciles once at the closest common parent. The pane components and DOM nodes survive.
The tab chrome does not. Every dock-document refresh creates a new EdgeZone shell, new tab.Container instances, new header toolbars, new strips, and new overflow plugins. Their construction animations replay as flicker. The draft PR contains a local .agentos-dockdemo-chrome-settling containment class which disables those replacement-shell transitions. It prevents the false animation, but it also proves the deeper identity gap and cannot restore normal active-tab motion.
A live latest-20-open sweep plus all-state GitHub search was checked at 2026-07-13T19:13Z; no equivalent ticket exists. Closed #14968 owns operation-correlated tab-insert animation, not surviving tab-container identity. Open #14780 owns the motion vocabulary. The current A2A all-state window contains no overlapping claim or intent.
The Problem
A surviving logical tabs node is currently rendered as a new component tree after every dock operation. Consequences:
- tab-header and strip identities reset even when the dock node still exists;
- construction/entry effects can replay as whole-strip flicker;
- the overflow plugin and its floating control are torn down and recreated;
- a blunt animation-disable class is needed to keep coarse projection visually honest;
- normal active-tab indicator motion cannot be distinguished cleanly from replacement-shell construction.
The panes have object permanence while their containing chrome does not. For a flagship docking demo, that split is visible.
Architectural Reality
apps/agentos/childapps/dockdemo/view/DemoCWorkspace.mjs#refreshDockWorkspace already demonstrates the native permanence transaction: both source and destination parents exist, pane removals/inserts are silent, and one host.update() with updateDepth = -1 emits DOM moves.
src/tab/Container.mjs#createItems creates one header toolbar, strip, and body container per tab-container instance. Replacing the tab container necessarily replaces all three.
src/dashboard/DockLayoutAdapter.mjs projects JSON-first dock nodes into configs; it is not currently an identity reconciler.
- Closed
#14968 intentionally prevents broad tab-enter animation on coarse re-projection. This follow-up must preserve that operation-correlation contract rather than reintroducing a selector-wide animation.
- Open #14780 explicitly allows an intentional instant path for coarse projection, but a hard cut caused only by component replacement is not a substitute for surviving component identity.
The Fix
Extend Demo C's identity-preserving projection transaction to surviving tab chrome.
- Key each projected tabs surface by the dock node id and retain its
tab.Container when that logical node survives.
- Reconcile item moves/additions/removals through native container operations:
- apply the source and destination ownership changes silently;
- update once at their closest common parent with the narrowest correct
updateDepth;
- let Neo emit DOM move operations for surviving header/button/body identities.
- Create or destroy a tab container only when the logical dock node itself enters or leaves the topology.
- Preserve the surviving strip, header toolbar, and overflow-plugin/control identities; reproject their data, not their selves.
- Remove the Demo-C
agentos-dockdemo-chrome-settling animation suppression once identity makes it unnecessary.
- Keep operation-specific motion under the existing DockMotionSignal/token contracts. Ordinary active-tab changes should use the normal tab-strip animation; coarse refresh must not replay construction effects.
Start at the Demo-C consumer. Lift a reusable reconciler into src/ only if the implementation proves a second real consumer and a stable contract; do not make DockLayoutAdapter stateful merely to avoid local code.
Contract Ledger
| Target surface |
Source of authority |
Proposed behavior |
Fallback / failure |
Docs |
Evidence |
| surviving tabs node |
dock node id + projected tab.Container.dockNodeId |
same component instance survives projection |
node removed from document = destroy exactly once |
reconciliation JSDoc |
component-id before/after matrix |
| item ownership |
Neo container remove/insert/update contract |
silent source/destination mutation, one common-parent reconciliation |
invalid parent/index fails named; never duplicate an item |
app method JSDoc |
unit ownership + DOM identity assertions |
| tab strip/header |
Neo.tab.Container child lifecycle |
surviving toolbar/strip/buttons keep identity |
genuinely new/removed item may create/destroy its button |
no new timing literals |
NL identity journey |
| overflow control |
Neo.tab.plugin.Overflow |
one surviving plugin/control per surviving heavy tabs node |
node removal retires the control; no transient duplicate |
owner comment |
exact-one-control + stable-id assertion |
| motion |
#14780 + closed #14968 |
normal active-tab motion survives; coarse projection does not replay entry motion |
reduced motion and intentional instant paths remain token-owned |
remove local suppression rationale |
animation/transition browser oracle |
Decision Record impact
none if implemented as a Demo-C reconciliation consumer using existing container and docking contracts. A shared src/ reconciler would require an architecture step-back and an explicit ADR-impact statement before implementation.
Acceptance Criteria
Out of Scope
Changing DockZoneModel document identity · reintroducing broad tab-enter CSS · rewriting FLIP geometry motion · cross-window transfer choreography · general keyed-VDOM diffing · changing tab.Container public APIs without a separate architecture decision.
Avoided Traps
- Do not equate cached pane identity with complete workspace permanence.
- Do not hide replacement with a permanent zero-duration override.
- Do not retain components whose logical dock node was actually removed.
- Do not make
DockLayoutAdapter stateful by default.
- Do not create a second timing or motion-observability source.
Related
Related: #15099 · #13158 · #14780 · #14968 · #14653
Origin Session ID: f95e01ff-ba36-409a-98af-573263fab247
Retrieval Hint: Demo C dock tab chrome identity coarse projection common parent updateDepth overflow flicker permanence
Context
The visual-repair pass on #15099 proved that Demo C already preserves the hard part of docking permanence:
DemoCWorkspace.refreshDockWorkspace()stages the destination shell, moves cached live pane instances through Neo's native container ownership path, and reconciles once at the closest common parent. The pane components and DOM nodes survive.The tab chrome does not. Every dock-document refresh creates a new
EdgeZoneshell, newtab.Containerinstances, new header toolbars, new strips, and new overflow plugins. Their construction animations replay as flicker. The draft PR contains a local.agentos-dockdemo-chrome-settlingcontainment class which disables those replacement-shell transitions. It prevents the false animation, but it also proves the deeper identity gap and cannot restore normal active-tab motion.A live latest-20-open sweep plus all-state GitHub search was checked at 2026-07-13T19:13Z; no equivalent ticket exists. Closed
#14968owns operation-correlated tab-insert animation, not surviving tab-container identity. Open#14780owns the motion vocabulary. The current A2A all-state window contains no overlapping claim or intent.The Problem
A surviving logical tabs node is currently rendered as a new component tree after every dock operation. Consequences:
The panes have object permanence while their containing chrome does not. For a flagship docking demo, that split is visible.
Architectural Reality
apps/agentos/childapps/dockdemo/view/DemoCWorkspace.mjs#refreshDockWorkspacealready demonstrates the native permanence transaction: both source and destination parents exist, pane removals/inserts are silent, and onehost.update()withupdateDepth = -1emits DOM moves.src/tab/Container.mjs#createItemscreates one header toolbar, strip, and body container per tab-container instance. Replacing the tab container necessarily replaces all three.src/dashboard/DockLayoutAdapter.mjsprojects JSON-first dock nodes into configs; it is not currently an identity reconciler.#14968intentionally prevents broad tab-enter animation on coarse re-projection. This follow-up must preserve that operation-correlation contract rather than reintroducing a selector-wide animation.The Fix
Extend Demo C's identity-preserving projection transaction to surviving tab chrome.
tab.Containerwhen that logical node survives.updateDepth;agentos-dockdemo-chrome-settlinganimation suppression once identity makes it unnecessary.Start at the Demo-C consumer. Lift a reusable reconciler into
src/only if the implementation proves a second real consumer and a stable contract; do not makeDockLayoutAdapterstateful merely to avoid local code.Contract Ledger
tab.Container.dockNodeIdNeo.tab.Containerchild lifecycleNeo.tab.plugin.Overflow#14968Decision Record impact
none if implemented as a Demo-C reconciliation consumer using existing container and docking contracts. A shared
src/reconciler would require an architecture step-back and an explicit ADR-impact statement before implementation.Acceptance Criteria
tab.Container, header-toolbar, strip, overflow plugin/control, and surviving tab-button component identities.updateDepth.agentos-dockdemo-chrome-settlingand its transition-duration suppression.Out of Scope
Changing
DockZoneModeldocument identity · reintroducing broad tab-enter CSS · rewriting FLIP geometry motion · cross-window transfer choreography · general keyed-VDOM diffing · changing tab.Container public APIs without a separate architecture decision.Avoided Traps
DockLayoutAdapterstateful by default.Related
Related: #15099 · #13158 · #14780 · #14968 · #14653
Origin Session ID: f95e01ff-ba36-409a-98af-573263fab247
Retrieval Hint:
Demo C dock tab chrome identity coarse projection common parent updateDepth overflow flicker permanence