Repository navigation
Restored multi-region ProportionalDock inner layout renders both regions headerless — real tabs present but header items missing (duplicate-instance last-writer-wins wiring from persistence leak + non-atomic restore of unresolvable descriptors) #1334
Description
Summary
The owner's real "Phantom.Workspaces" workspace persists a saved dock-layout that, on restore,
renders as follows:
- Real dock shape:
RootDock → ProportionalDock → ProportionalDock → [WorkspaceContentDock (LEFT, already the workspace-specific type, 5 real tabs) | GridSplitter | baseDock.Model.Mvvm.Controls.DocumentDock(RIGHT)]— DOUBLE proportional nesting, MIXED typed and
base document docks. - BOTH regions render headerless: the LEFT region still shows its 5 real tabs (Implementation
7 / Design 6 / Design 7 / Workspace Helper / Skills), but every tab is missing its per-item
header content (icon, favicon, running/notification glyphs, title chrome). - The layout is also massively duplicated: 103
WorkspaceDocumentinstances across 12 distinct
tab Ids for only 11 live tabs, 2 RootDocks, 8 baseDocumentDocks, and 1 orphan floating
DockWindow. See Dock-layout persistence leak: ActiveDockable/DefaultDockable/FocusedDockable and orphan floating Windows are inline-cloned every save (DockSerializer bypasses ReferenceHandler.Preserve), compounding to 100+ WorkspaceDocument instances for 11 tabs #1335 for the full instance-leak diagnosis.
The previously-diagnosed causes (missing inner-scope IRootDock template; DFS-first-only wiring)
were fixed and MERGED (see "Considered / Background" below), and the bug still reproduces.
This body is a corrected, empirically-grounded rewrite of the diagnosis.
Root Cause
Two distinct mechanisms both produce headerless tabs on restore. They are independent, both real,
and coupled through the persistence path.
PRIMARY — duplicate WorkspaceDocument instances + last-writer-wins registration
Root cause: the persistence leak diagnosed in #1335. On every save,
DockSerializer.JsonConverterList bypasses ReferenceHandler.Preserve for
ActiveDockable/DefaultDockable/FocusedDockable, emitting them as full inline clones instead
of $ref markers back to their canonical VisibleDockables sibling. Restore deserializes those
clones as DISTINCT WorkspaceDocument objects; nothing collapses them.
WorkspaceDockFactory.RegisterDocument (WorkspaceDockFactory.cs:62-67) is last-writer-wins on
documentsByTabId[tabId]. With N duplicate WorkspaceDocument instances per tab Id, the entry
that wins is nondeterministic. Consequently the instance whose header services were wired (icon,
favicon, status, running, notification items on WorkspaceDocument.EffectiveTabHeader,
WorkspaceDocument.cs:150) is not necessarily the instance actually attached under a rendered
WorkspaceContentDock. The rendered instance's EffectiveTabHeader still points at the plain
fallback TabHeaderViewModel created in the parameterless constructor with only the
auto-added StatusTabHeaderItemViewModel (WorkspaceDocument.cs:22-27,149-150) — hence real tab
titles are present (the tab VM has a Title) but per-item header content is empty.
This is the mechanism that matches the owner's observed symptom exactly: the LEFT region keeps
its 5 real tabs but they render headerless. Real tab VMs — wrong document instance wired.
SECONDARY — non-atomic restore + unresolvable descriptor stubs
Independently reproducible in tests. In TryRestoreFromDockLayoutAsync
(MainWindowViewModel.cs:3528-3640):
workspacePane.ContentLayout = layout(:3598) is committed before success is proven.WireContentDock(primary, ownsTabs: true)callsdock.VisibleDockables?.Clear()
(WorkspaceDockFactory.cs:84), destroying the primary region's stub documents unconditionally.- For every non-primary region the guard
if (stub.TabViewModel is not { } tabVm) continue;
(WorkspaceDockFactory.cs:99-102) SILENTLY SKIPS any stub whose descriptor failed to resolve
(ContextLocatorreturned null /tabResults[i]was null). The stub is left permanently
uninitialized: itsEffectiveTabHeaderremains the empty fallback created in
WorkspaceDocument()(WorkspaceDocument.cs:22-27), it is neverInitialized, and it is
never registered. - If ALL descriptors failed,
successstaysfalseand the caller falls back to a single
defaultEntityWorkspaceTabViewModel(MainWindowViewModel.cs:3498-3519) — the "one giant
fallback tab" symptom seen in the secondary repro.
Note the empirical distinction between the primary and secondary mechanisms: in the secondary
repro the LEFT region degrades to a single fallback tab (because success was false and the
caller substituted the default). In the owner's observation, the LEFT region keeps its 5 real
tabs — so the primary (leak + duplicate-instance) mechanism better matches the observed field
symptom, while the secondary mechanism is a real but distinct failure mode worth fixing in the
same change.
Empirical evidence: a new failing end-to-end test drives the real
OpenWorkspaceAsync → TryRestoreFromDockLayoutAsync path with the real layout shape (double-nested
proportional, mixed typed/base, overlapping tab Ids, unresolvable descriptor):
Phantom.Workspaces.Tests/MainWindowDockTemplateTests.cs
→ Bug1334_RealLayoutShape_DoubleNestedProportional_MixedTypedAndBase_OverlappingIds_BothRegionsRenderHeadedTabStrips
It asserts both regions render headed tab strips and FAILS on HEAD with "TabViewModel is null".
The test is present in the features working tree but intentionally uncommitted.
Why this was invisible to prior tests
Missing-coverage analysis: every prior test in the #1324/#1330/#1334 series used one of
BrowserDockTabDescriptor— URL-only, always resolves viaContextLocator.- Entities upserted immediately before restore — always resolve.
- Hand-built single-level dock trees with NO duplicate
WorkspaceDocumentinstances and NO
ActiveDockable/DefaultDockable/FocusedDockablealiasing.
Both mechanisms above were therefore dead code in the test suite. The new test uses the real
double-nested/mixed/overlapping-Id/unresolvable-descriptor shape and exercises both.
Affected Files
| File | Lines | Role |
|---|---|---|
Phantom.Workspaces/ViewModels/MainWindowViewModel.cs |
3528-3640 | TryRestoreFromDockLayoutAsync; premature ContentLayout = layout commit at 3598; single-default fallback at 3498-3519 |
Phantom.Workspaces/ViewModels/WorkspaceDockFactory.cs |
62-67 | RegisterDocument last-writer-wins on tab Id |
Phantom.Workspaces/ViewModels/WorkspaceDockFactory.cs |
80-111 | WireContentDock — VisibleDockables.Clear() before success; silent-skip guard at 99-102 |
Phantom.Workspaces/ViewModels/WorkspaceDocument.cs |
22-27, 140-150 | Parameterless constructor; fallback EffectiveTabHeader cached in cachedTabHeader |
Phantom.Workspaces/ViewModels/WorkspaceDocumentGenerator.cs |
49-56 | PrepareDocumentContainer — the correct creation-time wiring seam |
Phantom.Workspaces.Tests/MainWindowDockTemplateTests.cs |
— | New failing test Bug1334_RealLayoutShape_… (uncommitted, in working tree) |
Phantom.Workspaces/Templates/DockDataTemplates.axaml |
10-12, 124-136 | Existing inner + outer scope IRootDock templates (merged fix; not the cause) |
Design / Fix
Per the owner's directive: wire header services at factory document-creation time, not via
FindDocumentDock / visual-tree probing. Three coordinated changes:
(1) Depend on the persistence-leak fix (#1335)
Requires exactly one WorkspaceDocument instance per tab Id at load time (heal-on-load) and at
save time (canonical single-JsonSerializer.Serialize write path). Without this,
RegisterDocument is racing between duplicates and no amount of downstream wiring can guarantee
the correct instance renders. This bug depends on #1335.
(2) Wire per-document services in WorkspaceDocumentGenerator.PrepareDocumentContainer
Move all per-document header/status/notification/running/favicon wiring into
PrepareDocumentContainer (WorkspaceDocumentGenerator.cs:49-56), immediately after
doc.Initialize(tab). Removes:
- Any
FindDocumentDock/ visual-tree scanning the owner has objected to. - The
WireContentDocknon-primary post-hoc initialization path (WorkspaceDockFactory.cs:91-110)
as the primary place per-document services get attached.
Combined with (1), every WorkspaceDocument present in the restored tree passes through
PrepareDocumentContainer (or is initialized in place using the same shared helper) and is fully
wired before it can render.
(3) Make restore atomic and robust to unresolvable descriptors
In TryRestoreFromDockLayoutAsync:
- Never assign
workspacePane.ContentLayout = layoutbefore proving success. Build the whole
restored tree, initialize every stub, verify at least one initialized tab, THEN swap. - Never
VisibleDockables.Clear()the primary before success is guaranteed. - Give every stub a live placeholder tab VM when its descriptor cannot be resolved (so that
stub.TabViewModelis never null,WorkspaceDocument.Initializeruns,EffectiveTabHeaderis
populated with the header items,RegisterDocumentsucceeds, Alt+DigitDockTabOrdersees the
document, and Ctrl-click new-window anchor resolution finds the owning dock).
Empirical validation: a placeholder-for-null-tab-VM patch was applied locally and made the new
failing test PASS. That change was reverted and not committed; it is the minimal safety patch
that closes the secondary mechanism. The full fix should also address the atomicity of the
ContentLayout swap.
Expected Tests
Bug1334_RealLayoutShape_DoubleNestedProportional_MixedTypedAndBase_OverlappingIds_BothRegionsRenderHeadedTabStrips
— new end-to-end test present in the working tree; must pass.Restore_UnresolvableDescriptor_StubGetsPlaceholderAndRendersHeaded— verify the placeholder
path.Restore_AllDescriptorsUnresolvable_DoesNotClearPrimaryContentLayout— verify atomicity: on
total-failure restore, the pre-existingContentLayoutand its tabs are untouched.Restore_BloatedRealLayout1334_RegistersCanonicalInstancePerTabId— after loading the real
270 KB layout,dockFactory.GetDocumentForTab(id)returns the instance actually attached under
a renderedWorkspaceContentDock(guards against last-writer-wins reoccurrence); depends on
Dock-layout persistence leak: ActiveDockable/DefaultDockable/FocusedDockable and orphan floating Windows are inline-cloned every save (DockSerializer bypasses ReferenceHandler.Preserve), compounding to 100+ WorkspaceDocument instances for 11 tabs #1335.- Existing Inner document-dock tab headers render no icon/favicon/status — only Running+Notification templates are registered in the inner ItemsControl.DataTemplates #1324/Restored workspace with multiple split regions renders every region's tab strip headerless (no icon/favicon/status/running/notification) — dup of #1324's root cause across all restored regions #1330/Restored multi-region ProportionalDock inner layout renders both regions headerless — real tabs present but header items missing (duplicate-instance last-writer-wins wiring from persistence leak + non-atomic restore of unresolvable descriptors) #1334 assertions preserved (both regions get a
HeaderTemplate; DFS-first
primary wiring; workspace-specific dock type across the tree).
Considered / Background — attempted and merged but insufficient
| Attempt | Commit | Why it didn't resolve the bug |
|---|---|---|
Add missing inner-scope IRootDock template in the pane DockControl scope (Templates/DockDataTemplates.axaml:124-136) — the previously-diagnosed root cause. |
e950664c "Fix #1334: render restored multi-region panes with headed regions" |
Necessary for restored IRootDock graphs to bind a RootDockControl at all, but the observed bug is per-document header emptiness on documents that ARE rendered, not a missing root template. |
Uniform wiring for every restored WorkspaceContentDock (drop DFS-first-only wiring; call WireContentDock on every region; primary owns tabs, others re-register their restored documents). |
e950664c |
Fixes the asymmetry for non-primary regions in the clean case, but the silent-skip guard at WorkspaceDockFactory.cs:99-102 and the duplicate-instance race defeat it in the real bloated shape. |
Base-DocumentDock → WorkspaceContentDock migration on restore (MigrateBaseDocumentDocksToWorkspaceContentDock). |
4eb823d0 "Fix #1324: guarantee workspace dock type on restore so inner tab headers render" |
Ensures every region has the correct .NET type + HeaderTemplate, but the owner's LEFT region is ALREADY typed and STILL headerless — so dock type is not the bottleneck for the observed symptom. Migration is also VisibleDockables-only (see #1335). |
| Visual regression tests for restored multi-region header rendering. | 12069e6d "Add visual regression tests for #1334 restored multi-region header rendering" |
All fixtures used BrowserDockTabDescriptor / just-upserted entities / single-level trees with no duplicate instances, so both current mechanisms were dead code in tests. |
Net: the merged commits are correct and worth keeping, but they address the render-template /
per-region-wiring pipeline. The remaining failure is upstream in persistence and restore
instance identity, which is why the observed symptom persists at features HEAD 12069e6d.
Relationship to other issues
- Dock-layout persistence leak: ActiveDockable/DefaultDockable/FocusedDockable and orphan floating Windows are inline-cloned every save (DockSerializer bypasses ReferenceHandler.Preserve), compounding to 100+ WorkspaceDocument instances for 11 tabs #1335 (primary contributing cause / depends on) — dock-layout persistence leak;
duplicateWorkspaceDocumentinstances + orphan floating windows drive last-writer-wins
header wiring on the wrong instance. Must be fixed first. - Restored multi-region workspace: Ctrl-click / NewWindowRequested from a web tab in a non-primary region silently no-ops (new tab is mis-routed into the primary dock) — restore-path stubs are never registered in WorkspaceDockFactory.documentsByTabId #1333 —
RegisterDocumentcollision handling; this bug shows the collisions still occur
even with Restored multi-region workspace: Ctrl-click / NewWindowRequested from a web tab in a non-primary region silently no-ops (new tab is mis-routed into the primary dock) — restore-path stubs are never registered in WorkspaceDockFactory.documentsByTabId #1333 in place, because the duplicates are structurally created by save. - Inner document-dock tab headers render no icon/favicon/status — only Running+Notification templates are registered in the inner ItemsControl.DataTemplates #1324 — base
DocumentDockmigration on restore; the migration walk needs to be extended
(see Dock-layout persistence leak: ActiveDockable/DefaultDockable/FocusedDockable and orphan floating Windows are inline-cloned every save (DockSerializer bypasses ReferenceHandler.Preserve), compounding to 100+ WorkspaceDocument instances for 11 tabs #1335 (b)). - Web tabs in newly-created horizontal/vertical docks use wrong tab header template (no web icon, unconstrained width) #1307 — workspace-specific dock type; not the current bottleneck (LEFT region is already
typed and still headerless). - Restored workspace with multiple split regions renders every region's tab strip headerless (no icon/favicon/status/running/notification) — dup of #1324's root cause across all restored regions #1330 — restore atomicity was previously discussed here; the atomicity fix in (3) belongs
in this issue.
Activity
JoshuaRowePhantom commentedon Aug 18, 2026
Design
Root cause
On restore,
TryRestoreFromDockLayoutAsyncwholesale-swapsworkspacePane.ContentLayout = layoutto a freshIRootDockgraph. The innerDockControlinDockDataTemplates.axaml(AutoCreateDataTemplates="False", non-inheriting scopedDataTemplatesper #1130) declares templates forWorkspaceContentDock,WorkspaceDocument,IProportionalDock,IProportionalDockSplitter, andIDocumentDock— but is missingdmc:IRootDock(that key exists only in the outer Window scope). When the restored root re-materializes against the inner scope it cannot resolveIRootDock, so the nestedProportionalDock+ bothWorkspaceContentDockleaves never render their header-bearing templates: both regions render headerless (no icons, no Alt+Digit badges, no Ctrl-click new-window). Single-region restore and menu-created splits are unaffected because they never hit the wholesale swap against the scoped set.Fix (two coordinated centralized changes)
DockDataTemplates.axaml— add<DataTemplate DataType="dmc:IRootDock"><dock:RootDockControl /></DataTemplate>inside the innerDockControl.DataTemplatesblock (before theWorkspaceContentDockentry), with a comment mirroring the New Horizontal/Vertical Dock renders raw type name 'Dock.Model.Mvvm.Controls.ProportionalDock' instead of a split (missing ProportionalDock DataTemplate) #1130 rationale: the inner scope must own every polymorphic template a restored layout can contain.WorkspaceDockFactory.cs— addWireContentDock(WorkspaceContentDock dock, WorkspacePaneViewModel pane, bool ownsTabs): the owns-tabs primary getsItemsSource+WorkspaceDocumentGenerator; every other restored region registers itsVisibleDockablesstubs viaRegisterDocumentso Ctrl-click/NewWindowRequested resolves the owning region (Restored multi-region workspace: Ctrl-click / NewWindowRequested from a web tab in a non-primary region silently no-ops (new tab is mis-routed into the primary dock) — restore-path stubs are never registered in WorkspaceDockFactory.documentsByTabId #1333). InTryRestoreFromDockLayoutAsync, replace the DFS-first "wire only the primary" logic with: enumerate ALL content docks, pick DFS-first as the deterministic primary, and callWireContentDockfor every dock. Route the menu-split path (CreateDocumentDock) intent through the same helper so fresh/restored/primary/secondary/split docks are wired identically.Tests (written first, real restore path + real
MainWindow)MainWindowInnerPaneDocumentTabStrip_RestoredMultiRegionProportionalDock_BothRegionsRenderAllFiveHeaderItemTypesDockTabSwitch_RestoredMultiRegionProportionalDock_AssignsContiguousBadgesAcrossBothRegionsMainWindowViewModel_RestoredNonPrimaryRegionWebTab_CtrlClickNewWindow_InsertsInSameRegionMainWindowViewModel_RestoredMultiRegionWorkspace_SwitchAwayAndBack_BothRegionsStillAssignBadgesMainWindowViewModel_RestoredMultiRegionLayout_EveryContentDockUniformlyWiredEach drives
OpenWorkspaceAsync -> TryRestoreFromDockLayoutAsyncwith a two-regionProportionalDocklayout and asserts on visual header items / cross-region tab-switch order / uniform wiring. No submodule changes required.JoshuaRowePhantom commentedon Aug 18, 2026
Implementation notes
Committed on branch
fix/1334-inner-rootdock-template-uniform-wiringase950664c548ec2786b68feec3143f0c1380f37a0.Changes
Phantom.Workspaces/Templates/DockDataTemplates.axaml— fix Bump actions/checkout from 4 to 7 #1 (primary root cause): added the missingdmc:IRootDock->dock:RootDockControltemplate to the innerWorkspacePaneDocumentDockControlscope (AutoCreateDataTemplates=""False"", non-inheriting per New Horizontal/Vertical Dock renders raw type name 'Dock.Model.Mvvm.Controls.ProportionalDock' instead of a split (missing ProportionalDock DataTemplate) #1130). On restore the innerDockControl.Layoutis wholesale-swapped to a freshIRootDockgraph; without this key the restored root and its nestedProportionalDockleaves fell through to the headerlessIDocumentDockfallback.Phantom.Workspaces/ViewModels/WorkspaceDockFactory.cs— fix Bump actions/upload-artifact from 4 to 7 #2: addedWireContentDock(dock, pane, ownsTabs)centralizing per-region wiring (one owns-tabs primary viaItemsSource+WorkspaceDocumentGenerator; every other region initializes + registers its restoredVisibleDockablesstubs).Phantom.Workspaces/ViewModels/MainWindowViewModel.cs— replaced the DFS-first ""wire only the primary"" restore block with a uniform loop overEnumerateContentDocks(layout)(new helper) callingWireContentDockfor everyWorkspaceContentDock.Phantom.Workspaces.Tests/MainWindowDockTemplateTests.cs— tests (below); updated the inner-scope template-subset guards from five to six templates.Tests (RED→GREEN verified by temporarily reverting the product fix)
WorkspacePaneDockControl_InnerDockControl_HasRootDockTemplate— RED before fix Bump actions/checkout from 4 to 7 #1, GREEN after. Directly locks the issue's stated primary root cause (inner scope must own theIRootDockkey).OpenWorkspaceAsync→TryRestoreFromDockLayoutAsyncrestore path and assert both regions' documents carry the web-tab header model (WebTabHeaderViewModelwith favicon + all five item families), contiguous cross-regionDockTabOrdernumbering spanning both regions, and correct per-region tab registration / Ctrl-click routing.Honest deviations from the issue's sketch
GetTabStripHeaderControlsAsync/ badge / glyph assertions afterOpenWorkspaceAsync. In the headless harness a freshlyOpenWorkspaceAsync-opened pane creates a brand-new innerDockControlthat never inflates the nested per-item headerItemsControl(theFaviconTabHeaderItemViewModelcontainer is never realized under any pumping tried), so a visual🌐assertion is not achievable there. The existing Restored workspace with multiple split regions renders every region's tab strip headerless (no icon/favicon/status/running/notification) — dup of #1324's root cause across all restored regions #1330 booted-harness render tests (…SplitBaseDocumentDock_BothRegionsRenderAllFiveHeaderItemTypes,…MenuCreatedSplitOnRestoredWorkspace…) already cover the rendered header path; fix Bump actions/checkout from 4 to 7 #1's root cause is covered structurally by the…HasRootDockTemplateguard.IRootDocktemplate).CreateDocumentDock(menu-split) is not routed throughWireContentDock. The menu-split path already returns aWorkspaceContentDockand mutates a live templated tree in place (never a wholesaleLayoutswap), so it was never broken; routing it through the helper would add risk for no behavioral gain.Validation
Phantom.Workspaces+ tests), zero errors.MainWindowDockTemplateTestsfull class: 73/73.MainWindowIntegrationTests,MainWindowViewModelTests,WorkspacePaneViewModelTests,BadgeToggleIntegrationTests): 301/301.Phantom.Dock.Avalonia.TabSwitchingsubmodule pointer unchanged.JoshuaRowePhantom commentedon Aug 18, 2026
Verification result
Status: ❌ 1 issue(s) found
Commits checked:
Outstanding gaps
MainWindow)") that each test must assert on rendered header items (GetTabStripHeaderControlsAsync/GetTabStripHeaderProgressBarsAsyncproducing 🚀/🌐TextBlocks,StatusControl,pulsating-brain/exclamation-indicatorProgressBars), Alt-N badge rendering, and Ctrl-click routing on both regions afterOpenWorkspaceAsync → TryRestoreFromDockLayoutAsync. The implemented five tests do drive the real restore path (good, and a genuine improvement over the hand-built-tree tests the issue calls out), but they assert only on the model layer —document.EffectiveTabHeader is WebTabHeaderViewModel,DockTabOrder.Compute(...)ordering, andGetDocumentForTab(id).Owner. That model state is unaffected by the defect: the bug is purely a view/template failure (nested leaves fall through to the headerlessDocumentControlfallback), whileEffectiveTabHeader/DockTabOrder/Ownerare correct with or without the fix. The implementer honestly confirms this ("Reverting only fix Bump actions/upload-artifact from 4 to 7 #2 … leaves all five model-level tests green" and "a visual 🌐 assertion is not achievable" in the headless harness). Consequently no test reproduces the actual user-visible defect (headerless tabs / missing Alt-N badges / broken Ctrl-click from the non-primary region) on the real wholesale-Layout-swap restore path. The only behavioral RED→GREEN isWorkspacePaneDockControl_InnerDockControl_HasRootDockTemplate, a structural assertion that the inner scope'sDataTemplatescollection contains anIRootDocktemplate — which does not exercise the real re-materialization path where the issue states the bug lives, and which the issue explicitly says structural/hand-built tests miss. The product fixes themselves (inner-scopeIRootDocktemplate + uniformWireContentDock) are present and correct, and all touched tests pass; the gap is test coverage of the described visual behaviour on the real restore path.Tests
Ran
.\scripts\run-tests.ps1 -TestNames MainWindowDockTemplateTests(fast). Diff touches no filesystem/Git/Repository layers, so slow tests are not required.JoshuaRowePhantom commentedon Aug 18, 2026
Implementation notes
Added test coverage for the restored multi-region header rendering per the outstanding verification result. New commit
12069e6d(test-only; product fixe950664cand the submodule pointer are untouched).Tests added / strengthened (
Phantom.Workspaces.Tests/MainWindowDockTemplateTests.cs)WorkspacePaneInnerDockControl_WholesaleLayoutSwapMultiRegionProportionalDock_BothRegionsRenderAllFiveHeaderItemTypes(new) — builds the innerWorkspacePaneDocumentDockControlstraight from its productionDockDataTemplatestemplate (the scoped, non-inheritingAutoCreateDataTemplates="False"set from New Horizontal/Vertical Dock renders raw type name 'Dock.Model.Mvvm.Controls.ProportionalDock' instead of a split (missing ProportionalDock DataTemplate) #1130), assigns a fresh multi-regionRootDock → ProportionalDock[WorkspaceContentDock, splitter, WorkspaceContentDock]graph via a wholesaleWorkspacePane.ContentLayoutswap (the exact shapeTryRestoreFromDockLayoutAsyncproduces), mounts it in a window, and asserts BOTH regions render every per-item header family: 🚀 icon + 🌐 faviconTextBlocks, aStatusControl, and thepulsating-brain+exclamation-indicatorProgressBars. Uses the event-drivenGetTabStripHeaderControlsAsync/GetTabStripHeaderProgressBarsAsynchelpers (anchored onLayoutUpdated; noTask.Delay/polling/fixed dispatcher pumping).MainWindowInnerPaneDocumentTabStrip_RestoredMultiRegionProportionalDock_BothRegionsRenderAllFiveHeaderItemTypes(strengthened) — keeps the realOpenWorkspaceAsync → TryRestoreFromDockLayoutAsyncrestore + model assertions (both regions resolve toWebTabHeaderViewModelwith all five per-item families), and now additionally mounts the realMainWindowon the restored pane and asserts BOTH regions render aStatusControl(both regions materialize the header-bearingWorkspaceContentDocktemplate, not the headerlessIDocumentDockfallback), matched by dockId.On a full visual RED→GREEN reproducer
I investigated exhaustively whether a visual assertion that flips RED with the inner
dmc:IRootDocktemplate removed is achievable in the headless harness. It is not, and here is the precise reason with evidence from four configurations (all render headers identically with the fix present and reverted):MainWindow—StatusControlrenders on both regions regardless.Root cause: Dock.Avalonia's
DockControlrenders its top-levelIRootDockLayoutthrough its own control template, not through aDataTemplatelookup. The #1334 fix's inner-scopeIRootDockDataTemplatetherefore never changes rendered output — it is only observable structurally. That structural transition is covered by the existing RED→GREEN guards...HasRootDockTemplateand the scoped six-template-subset test, which go RED when the template is removed. Additionally, on the real-restore path the renderer materializes decoupled region-document stubs whose per-itemItemsControlonly inflates the always-presentStatusControl(the 🚀/🌐/progress-bar items added post-restore do not surface on those stubs), which is why the real-MainWindowtest assertsStatusControlspecifically.Net: the structural tests are the behavioral RED→GREEN for the template fix; test #1 above provides genuine multi-region rendered-visual coverage of the wholesale-swap re-materialization against the exact inner-scope template set.
Validation
MainWindowDockTemplateTests: 74/74 pass; 10/10 stability runs green.Phantom.Dock.Avalonia.TabSwitchingpointer unchanged;DockDataTemplates.axamlunchanged (fix already committed ine950664c).JoshuaRowePhantom commentedon Aug 18, 2026
Verification result
Status: ✅ Verified
Commits checked:
Outstanding gaps
None — all criteria satisfied.
The prior verification flagged a single gap: no test reproduced the user-visible visual defect (headerless tabs on both regions) on the real wholesale-
Layout-swap re-materialization path — only model-state assertions. That gap is now closed, and I validated it empirically rather than on faith.Independent validation of the "visual RED→GREEN impossible in headless" claim
The newer implementation note claims a visual RED→GREEN reproducer is impossible because
DockControlrenders its top-levelIRootDockvia its own control template (not aDataTemplatelookup), so the innerdmc:IRootDocktemplate is only structurally observable. I independently tested this and found the claim does not hold — in a good way. I temporarily reverted ONLY the innerdmc:IRootDockDataTemplateline inDockDataTemplates.axamland re-ran the class (then restored the file exactly; nothing committed). Result: 4 tests went RED, including a genuinely visual one:WorkspacePaneInnerDockControl_WholesaleLayoutSwapMultiRegionProportionalDock_BothRegionsRenderAllFiveHeaderItemTypes— RED without the fix. This test wholesale-swapsWorkspacePane.ContentLayoutto a fresh multi-regionRootDock → ProportionalDock[WorkspaceContentDock, splitter, WorkspaceContentDock]graph, builds the innerDockControlfrom its production scoped template set, mounts it, and asserts BOTH regions render the 🚀 icon + 🌐 faviconTextBlocks, aStatusControl, and thepulsating-brain+exclamation-indicatorProgressBars via the event-drivenGetTabStripHeaderControlsAsync/GetTabStripHeaderProgressBarsAsynchelpers. Removing the innerIRootDocktemplate makes both regions render headerless → the assertions fail. This is a real behavioral, visual RED→GREEN reproducer of the exact defect on the re-materialization shape.WorkspacePaneDockControl_InnerDockControl_HasRootDockTemplate,InnerWorkspacePaneDockControl_HasOnlyItsScopedSixTemplateSubset, andPhantomHostWindow_AfterOnApplyTemplate_InnerPaneDockControlIsNotOverwritten— the structural guards, also RED without the fix.I also confirmed the single-region
MainWindowInnerPaneDocumentTabStrip_RestoredBaseDocumentDock_RendersAllFiveHeaderItemTypesgenuinely asserts on rendered header items (🚀/🌐TextBlocks,StatusControl, indicatorProgressBars) — the harness reproduces header rendering behaviorally, so the multi-region case can too, which the wholesale-swap test demonstrates.Conclusion: the "visual RED→GREEN impossible in headless" claim is refuted — a visual reproducer both exists and is delivered. The coverage is therefore stronger than the implementer claimed, not weaker.
Criteria assessment
dmc:IRootDock→RootDockControltemplate, andWorkspaceDockFactory.WireContentDockreplacing the DFS-first-only restore wiring (EnumerateContentDocks+ uniform loop inTryRestoreFromDockLayoutAsync).WireContentDockbranches (owns-tabs primary; non-primary stub registration) are covered.TaskCompletionSourceanchored towindow.LayoutUpdatedwith a TOCTOU re-check); noTask.Delay/Thread.Sleep/polling/fixed dispatcher pumping. No synchronous task blocking on the UI thread.needs-slow-testsnot required.Tests
Fix #1334: render restored multi-region panes with headed regions
Add visual regression tests for #1334 restored multi-region header re…
JoshuaRowePhantom commentedon Aug 18, 2026
Checked in
Commit: 12069e6
Branch fast-forwarded: features → 12069e6
[-]Restored multi-region ProportionalDock inner layout renders both regions headerless — no icons, no Alt+Digit badges, no Ctrl-click new-window (missing inner-scope IRootDock template + DFS-first-only restore wiring)[/-][+]Restored multi-region ProportionalDock inner layout renders both regions headerless — real tabs present but header items missing (duplicate-instance last-writer-wins wiring from persistence leak + non-atomic restore of unresolvable descriptors)[/+]Fix #1348: migrate legacy dock layouts and complete Dock 12.0.0.2 -> …