correct 5.2: the key was never the cause

This commit is contained in:
2026-08-07 10:46:31 +00:00
parent 56ca411cf3
commit e2017cbbf2
+30 -6
View File
@@ -345,9 +345,29 @@ threaten whatever the panel was holding; a panel now holds nothing. A chat panel
declares free. Fix these for the interaction quality they are genuinely about — scroll position, media
playback, transcodes — not as a prerequisite for agent coordination._
A panel's React identity is its position plus `key={child.node.id}` on its **nearest ancestor group
slot** (`WorkspaceRenderer.tsx:178,204`) — the panel's own id is a key nowhere. That's the root cause of
the whole table:
**Measured `56ca411`.** `WorkspaceRenderer.test.tsx` mounts the real renderer against a mount-counting
probe app and lets the real `layout-utils` mutators produce the "after" tree. The table below was written
from reading the code; the test disagrees with its *diagnosis* in every row, and found one row it had
missed entirely. Read this paragraph before acting on the bullets underneath it.
- **The key is not the cause.** A panel's React identity is its position plus `key={child.node.id}` on its
nearest ancestor group slot, but in each remounting case the **element type** at that position also
changes — `PanelSlot` becomes `ResizablePanelGroup`, or the reverse — and React reconciles by type
before it looks at keys. Reusing the panel's id so the key does not flip would therefore have changed
nothing, at the cost of putting a panel id and a group id into collision, which now matters because a
panel id is an agent's address (`agent_panels`).
- **The row that was missing was the only cheap one, and it is fixed.** `ChildEntry` returned
`<>{children}</>` for the first child and `<><Handle/>{children}</>` for every other, so a panel sat in
fragment slot 0 when first and slot 1 when not. Remove the leftmost of three panels and the second one
found a `ResizableHandle` in its old slot and remounted — for nothing that happened to it. The handle
slot is now always present, holding `null` when unneeded.
- **What is left is genuinely structural.** Wrapping a panel in a group, and a two-child group collapsing
to its survivor, both change what kind of thing lives at that position. The only fix that survives
contact with React is the one the last bullet of this section already points at: render panels from a
host that does not move when the tree reshapes, the way maximize is a CSS toggle on the same element.
That is a redesign, not a patch, and it is still Tier C.
The original table, kept because its *observations* hold even where its explanation did not:
| operation | remounts? | why |
| ------------------------------------- | ---------------------- | ------------------------------------------------------------------------- |
@@ -360,10 +380,14 @@ the whole table:
| mobile panel switch | yes, by design | only the active child renders |
| **any ephemeral panel on mobile** | **yes — every panel** | `WorkspaceView.tsx:165` replaces the workspace instead of overlaying |
| **viewport crossing 768px** | **yes, full** | `useIsMobile` flips the branch at `WorkspaceView.tsx:165` |
| **a panel stops being second** | **was yes — fixed** | `ChildEntry`'s fragment slots shifted under it (`56ca411`) |
- [ ] **Reuse the panel's id when wrapping it in a new group**, so the ancestor slot key doesn't flip.
Kills rows 2 and 3.
- [ ] **Don't re-key the survivor when a group collapses** (`layout-utils.ts:68-70, 83-88`). Kills row 4.
- [ ] ~~**Reuse the panel's id when wrapping it in a new group**, so the ancestor slot key doesn't flip.
Kills rows 2 and 3.~~ **Withdrawn `56ca411`** — measured, and it kills neither. The element type at
that position changes too, which React acts on first. It would also collide a panel id with a group
id, and a panel id is an agent's address now.
- [ ] ~~**Don't re-key the survivor when a group collapses** (`layout-utils.ts:68-70, 83-88`). Kills row
4.~~ **Withdrawn `56ca411`**, same reason: the survivor changes type as well as key.
- [ ] **Overlay the mobile ephemeral panel instead of replacing the workspace**
(`WorkspaceView.tsx:165`). Affects `/files`, `/email`, `/chat`, `/browser`, `/dashboards`.
- [ ] **Reference for how it should feel:** maximize (`PanelSlot.tsx:430-457`) is a CSS state toggle on