Context
#18164 / PR #18165 made a torn-out pane come home to its own node instead of the first one, including when the node collapsed while the pane was out. That fix is doing its job. What it does not restore is the home's size: an edge-zone slot whose only pane was torn out is rebuilt at the projection's default width rather than the width it had.
Measured on a consumer dock host at 862edb31b3 (which carries #18165), a single-pane right edge zone, host-relative:
left from x 1281 width 318 height 919
came home x 1321 width 272 height 900
The side is correct — that is #18165 — so this is the narrower property left over. It is user-visible and it costs work: a user who widens a docked panel loses that width by popping the panel out and closing it.
Not a symptom of #18175. restoreTab reaching the dispatch table without a change class was a map-completeness red; changeClassFor already fell through to topology, so runtime behaviour was correct throughout. I checked that first precisely because restoreTab is the shared surface, and its body settles it — the two are independent.
Live latest-open sweep: checked the latest 20 open issues at 2026-09-03T09:03:51Z; no equivalent found.
The Problem
Document.captureNodeHome records what is needed to put a node back, and it records different amounts depending on the parent type:
- split parent —
orientation, size (the child's own fraction, defaulted to 0.5), and a sibling anchor.
- edge-zone parent —
{parentId, slot} and nothing else.
So a split home restores its geometry and an edge-zone home does not. The member's own JSDoc explains the asymmetry, but on a different axis than the one that bites:
An edge-zone parent needs no anchor: an edge-zone node survives losing every zone, so its id and the zone key remain resolvable.
That is true and it is about resolvability — the id and key survive, so no sibling anchor is needed. It has been generalised into "an edge-zone home needs nothing else recorded", which does not follow: the id surviving says nothing about the extent surviving.
Worth stating why this is an oversight rather than a contract, since a lone asymmetry can be either. Document.setZoneNodeId spreads the descriptor it finds and overwrites only nodeId:
edgeZoneNode.zones[edge] = {
...(Document.isJsonRecord(edgeZoneNode.zones[edge]) ? edgeZoneNode.zones[edge] : {}),
nodeId
}Preserving extent across a repoint is therefore the intended behaviour wherever the descriptor is still present — the helper is written for it, and Document documents it as "repoints one nested edge-zone descriptor while preserving its extent and policy". The gap is that on a sole-occupant tear-out there is no descriptor left to spread: the slot was cleared when the node went away, so the restore writes {nodeId} alone and the extent is simply absent.
The Architectural Reality
- An edge zone's size is a property of the descriptor, not of the node it points at:
Document.dockZoneDescriptorKeys = new Set(['nodeId', 'extent', 'resizable']), validated as a finite number in the open interval (0, 1).
Operations.restoreTab mints a node when the home is gone — doc.nodes[nodeId] = {activeItemId: itemId, items: [itemId], type: 'tabs'}, id-collision-safe via Document.genId — then calls Document.restoreNodeHome.
Document.restoreNodeHome re-points the slot through setZoneNodeId once it has established the slot is unoccupied or already this node's (the ownership check #18165 added).
resizeEdgeZone is what writes extent in the first place, so the value being lost is a committed user gesture, not a default someone forgot to configure.
The whole path is therefore correct except for one missing pair of fields in the record.
The Fix
Document.captureNodeHome: when the parent is an edge-zone, record the descriptor's extent and resizable alongside slot, the way the split branch already records size and orientation.
Document.restoreNodeHome: when writing the slot, re-apply the recorded extent / resizable — additively, so a descriptor that survived keeps what it has and only an emptied slot is rebuilt from the record. setZoneNodeId's spread already gives the right merge semantics; the recorded values supply what the spread cannot find.
Keep the validation boundary: an out-of-range or non-finite recorded extent must be dropped in favour of the default rather than written into the document, since Document's own validator rejects extent outside (0, 1) and a restore must not be able to author an invalid document.
Acceptance Criteria
Out of Scope
- The
#18165 ownership rule and the occupied-slot refusal. Untouched; this only adds fields to the record it already writes.
- Split-node sizes and the sibling-anchor logic.
resizeEdgeZone itself, and any change to how extent is validated or projected.
- Persisting extents across a reload — a separate concern from restoring one within a session.
Avoided Traps
- Treating the asymmetry as a bug without reading its justification. The JSDoc gives a real reason for the edge-zone branch being shorter; the defect is that the reason covers anchors and was extended to cover everything. Filing it as "the branch is incomplete" would have missed that the anchor omission is correct and only the geometry omission is not.
- Restoring the extent unconditionally. A vessel is long-lived, and the user can resize that edge while the pane is out. Overwriting a surviving descriptor's
extent from a stale record would discard a newer gesture — the same class of mistake as overwriting an occupied slot, which is what #18165 had to guard.
- Reading the returned pane's pixel width as the assertion. The width also moves when the projection legitimately re-lays-out; the document's
extent is the value under test, which is why AC-7 is model-level and the pixel figures stay in Context as the symptom that found it.
- Blaming
#18175. restoreTab is the shared surface and the red was live at the time of measurement, so the tempting inference was one cause. changeClassFor's fall-through makes them independent.
Decision Record impact
none — additive fields on an internal record, no ADR authority touched.
Related
#18164 / PR #18165 — restores the home node; this restores the home's size.
#18175 / PR #18176 — same operation, independent cause; cited above so the next reader does not re-merge them.
#18151 — the epic on the dock asking consumers to re-derive engine knowledge; adjacent in spirit, since a consumer cannot restore an extent the model never recorded.
Handoff Retrieval Hints
query_raw_memories: "edge-zone home extent not restored sole occupant tear-out"
- Anchors:
Document.captureNodeHome, Document.restoreNodeHome, Document.setZoneNodeId, Document.dockZoneDescriptorKeys, Operations.restoreTab
- Engine head where measured:
862edb31b3
Origin Session ID: e8ea32a2-b9a8-43ea-84ba-b83081ec4b3d
Context
#18164/ PR#18165made a torn-out pane come home to its own node instead of the first one, including when the node collapsed while the pane was out. That fix is doing its job. What it does not restore is the home's size: an edge-zone slot whose only pane was torn out is rebuilt at the projection's default width rather than the width it had.Measured on a consumer dock host at
862edb31b3(which carries#18165), a single-pane right edge zone, host-relative:The side is correct — that is
#18165— so this is the narrower property left over. It is user-visible and it costs work: a user who widens a docked panel loses that width by popping the panel out and closing it.Not a symptom of
#18175.restoreTabreaching the dispatch table without a change class was a map-completeness red;changeClassForalready fell through totopology, so runtime behaviour was correct throughout. I checked that first precisely becauserestoreTabis the shared surface, and its body settles it — the two are independent.Live latest-open sweep: checked the latest 20 open issues at 2026-09-03T09:03:51Z; no equivalent found.
The Problem
Document.captureNodeHomerecords what is needed to put a node back, and it records different amounts depending on the parent type:orientation,size(the child's own fraction, defaulted to0.5), and a sibling anchor.{parentId, slot}and nothing else.So a split home restores its geometry and an edge-zone home does not. The member's own JSDoc explains the asymmetry, but on a different axis than the one that bites:
That is true and it is about resolvability — the id and key survive, so no sibling anchor is needed. It has been generalised into "an edge-zone home needs nothing else recorded", which does not follow: the id surviving says nothing about the extent surviving.
Worth stating why this is an oversight rather than a contract, since a lone asymmetry can be either.
Document.setZoneNodeIdspreads the descriptor it finds and overwrites onlynodeId:edgeZoneNode.zones[edge] = { ...(Document.isJsonRecord(edgeZoneNode.zones[edge]) ? edgeZoneNode.zones[edge] : {}), nodeId }Preserving
extentacross a repoint is therefore the intended behaviour wherever the descriptor is still present — the helper is written for it, andDocumentdocuments it as "repoints one nested edge-zone descriptor while preserving its extent and policy". The gap is that on a sole-occupant tear-out there is no descriptor left to spread: the slot was cleared when the node went away, so the restore writes{nodeId}alone and the extent is simply absent.The Architectural Reality
Document.dockZoneDescriptorKeys = new Set(['nodeId', 'extent', 'resizable']), validated as a finite number in the open interval(0, 1).Operations.restoreTabmints a node when the home is gone —doc.nodes[nodeId] = {activeItemId: itemId, items: [itemId], type: 'tabs'}, id-collision-safe viaDocument.genId— then callsDocument.restoreNodeHome.Document.restoreNodeHomere-points the slot throughsetZoneNodeIdonce it has established the slot is unoccupied or already this node's (the ownership check#18165added).resizeEdgeZoneis what writesextentin the first place, so the value being lost is a committed user gesture, not a default someone forgot to configure.The whole path is therefore correct except for one missing pair of fields in the record.
The Fix
Document.captureNodeHome: when the parent is anedge-zone, record the descriptor'sextentandresizablealongsideslot, the way the split branch already recordssizeandorientation.Document.restoreNodeHome: when writing the slot, re-apply the recordedextent/resizable— additively, so a descriptor that survived keeps what it has and only an emptied slot is rebuilt from the record.setZoneNodeId's spread already gives the right merge semantics; the recorded values supply what the spread cannot find.Keep the validation boundary: an out-of-range or non-finite recorded
extentmust be dropped in favour of the default rather than written into the document, sinceDocument's own validator rejectsextentoutside(0, 1)and a restore must not be able to author an invalid document.Acceptance Criteria
captureNodeHomeon a node whose parent is anedge-zonereturns a record carrying the slot'sextent; a slot with noextentyields a record with none, not an invented default.captureNodeHomealso carriesresizablewhen the descriptor declares it, so a non-resizable edge does not come back resizable.extent, not the projection default.extentintact; the record does not overwrite a live value.extentthat failsDocument's(0, 1)finite check is discarded on restore and the document stays valid; asserted with a red-first arm that writes the invalid value.sizeandorientation.extent, item detached and restored, ends with the sameextent. This is the public witness for the measurement in Context and needs no app.captureNodeHomeJSDoc states what an edge-zone home records and why, so the next reader does not re-derive the resolvability argument as a completeness argument.Out of Scope
#18165ownership rule and the occupied-slot refusal. Untouched; this only adds fields to the record it already writes.resizeEdgeZoneitself, and any change to howextentis validated or projected.Avoided Traps
extentfrom a stale record would discard a newer gesture — the same class of mistake as overwriting an occupied slot, which is what#18165had to guard.extentis the value under test, which is why AC-7 is model-level and the pixel figures stay in Context as the symptom that found it.#18175.restoreTabis the shared surface and the red was live at the time of measurement, so the tempting inference was one cause.changeClassFor's fall-through makes them independent.Decision Record impact
none— additive fields on an internal record, no ADR authority touched.Related
#18164/ PR#18165— restores the home node; this restores the home's size.#18175/ PR#18176— same operation, independent cause; cited above so the next reader does not re-merge them.#18151— the epic on the dock asking consumers to re-derive engine knowledge; adjacent in spirit, since a consumer cannot restore an extent the model never recorded.Handoff Retrieval Hints
query_raw_memories: "edge-zone home extent not restored sole occupant tear-out"Document.captureNodeHome,Document.restoreNodeHome,Document.setZoneNodeId,Document.dockZoneDescriptorKeys,Operations.restoreTab862edb31b3Origin Session ID: e8ea32a2-b9a8-43ea-84ba-b83081ec4b3d