diff --git a/docs/workspace-panel-todo.md b/docs/workspace-panel-todo.md index 2492538a..5f008006 100644 --- a/docs/workspace-panel-todo.md +++ b/docs/workspace-panel-todo.md @@ -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 `<>{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