diff --git a/11-Features.md b/11-Features.md index fc2155b..260b0e2 100644 --- a/11-Features.md +++ b/11-Features.md @@ -4309,3 +4309,38 @@ each key one at a time, as the brief asked, and contradicted me on two entries: already covered, and `worktreeRoot` was uncovered and missing from my list. Both corrections were checked again on merge with a third mutation. **A list in a ticket is a starting point, not a measurement** — re-derive it, and say so when it disagrees. + +--- + +## Teardown now closes the tab a member really sits in + +**What it does.** `HerdrPeerLauncher.stop` used to look for a tab to clean up only when one of *its +own* configured profiles used tab placement (`usesTabPlacement()`). It now resolves the pane's real +tab every time, and the single-occupant check decides whether that tab is closed. That check is +unchanged: a tab holding other panes is never closed. + +**On.** Always on (fleetd #342). + +**Why it exists.** `CompositePeerLauncher` routes a stop through the adapter recorded in +`spawnedBy`, and that map is in memory only — a daemon restart empties it. On a miss in a +one-daemon fleet, the stop goes to `delegates.getFirst()`. When two adapters share one herdr daemon +and disagree on `tab` versus `pane` placement, teardown could run through an adapter whose config +says nothing true about how that pane was placed, and the whole tab-cleanup block was skipped. The +member still stopped, but its now-empty tab stayed. Nothing reaps an orphaned tab, so the operator's +herdr session collected one more of them on every affected teardown, silently. + +**Why not fix the routing instead.** Probing for the pane's real owner looks like the obvious fix +and does nothing here. `probeOwner` groups candidates in an `IdentityHashMap` keyed by +`HerdrClient`, so two delegates sharing one daemon collapse to whichever was inserted first — that +is `delegates.getFirst()` again, the same answer the shortcut already gave. The routing cannot tell +these two adapters apart at all. + +**One thing to know for maintenance.** The single-occupant check is now the *only* thing protecting +a shared tab, so treat it as load-bearing. Measured on merge: weakening it from `tabPaneCount() == 1` +to `>= 1` fails two tests, including the pane-placement one. One case is uncovered on purpose — a +pane-placement member that is the sole occupant of its tab will now have that tab closed. +`spawnAsPane` splits an existing tab, so the count is normally at least 2, and the tab is empty +after the member's pane goes anyway. + +The same in-memory `spawnedBy` breaks `clearContext` a different way: it plainly no-ops on a cache +miss. Open as fleetd #352, and the consequence is not measured yet.