Fix broadcast no-op: stop depending on orch object in LeafPane effects

The bug: clicking 📡 made the visual update (orange border) but typing
in a broadcasting pane only wrote to that pane — peers never received
the keystrokes.

Root cause: the orch context value (useMemo'd over activeLeafId,
distros, and the operation callbacks) is recreated every time
activeLeafId changes (i.e. every click). useEffect cleanups in
LeafPane that had `orch` in their deps fired their cleanup-then-setup
cycle on every click. The unmount-cleanup for paneId registration
ran `orch.registerPaneId(leaf.id, null)`, silently deleting paneIds
from App's paneIdByLeafRef map — so when broadcastFrom later walked
the tree looking up peers, the map returned undefined for every leaf
and the actual writeToPane calls never happened.

Fix: depend on the specific stable method references
(`orch.registerPaneId`, `orch.notify`, etc.) instead of the orch
object itself. The methods are all useCallback'd with stable deps
in App.tsx, so their references don't change across orch object
recreations — effect deps stay stable, no spurious cleanup.

Applied the same fix to all orch-using effects/callbacks in
LeafPane (commitLabel, pickDistro, onPaneClick, onPaneSpawned,
onXtermFocus, onTerminalInput, idle interval, paneId cleanup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-22 18:46:56 +01:00
parent c8234442f1
commit 2a0c096095

View file

@ -41,7 +41,7 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
if (!editingLabel) return;
orch.setLabel(leaf.id, labelDraft);
setEditingLabel(false);
}, [editingLabel, orch, leaf.id, labelDraft]);
}, [editingLabel, orch.setLabel, leaf.id, labelDraft]);
const cancelLabel = useCallback(() => setEditingLabel(false), []);
const onLabelKey = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
@ -67,7 +67,7 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
setDistroOpen(false);
if (d !== leaf.distro) orch.setDistro(leaf.id, d);
},
[orch, leaf.id, leaf.distro],
[orch.setDistro, leaf.id, leaf.distro],
);
// Dismiss popover on outside click
useEffect(() => {
@ -95,14 +95,17 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
}
}, 1000);
return () => clearInterval(id);
}, [leaf.label, leaf.distro, orch]);
// Depend on the stable notify function, not the whole orch object.
// orch is recreated every time activeLeafId/distros change; depending
// on it would tear down and rebuild this interval on every click.
}, [leaf.label, leaf.distro, orch.notify]);
// ---- broadcast ---------------------------------------------------------
const onTerminalInput = useCallback(
(b64: string) => {
if (isBroadcasting) orch.broadcastFrom(leaf.id, b64);
},
[isBroadcasting, orch, leaf.id],
[isBroadcasting, orch.broadcastFrom, leaf.id],
);
// ---- focus / active highlighting ---------------------------------------
@ -114,20 +117,26 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
const onPaneClick = useCallback(() => {
orch.setActive(leaf.id);
}, [orch, leaf.id]);
}, [orch.setActive, leaf.id]);
const onPaneSpawned = useCallback(
(paneId: number) => {
orch.registerPaneId(leaf.id, paneId);
},
[orch, leaf.id],
[orch.registerPaneId, leaf.id],
);
// Unregister on unmount
// Unregister on TRUE unmount only — depending on `orch` here would
// delete the paneId from App's lookup on every activeLeafId change,
// which broke broadcast routing (peers found, but their paneIds
// had been silently removed from the map).
useEffect(() => {
return () => orch.registerPaneId(leaf.id, null);
}, [orch, leaf.id]);
}, [orch.registerPaneId, leaf.id]);
const onXtermFocus = useCallback(() => orch.setActive(leaf.id), [orch, leaf.id]);
const onXtermFocus = useCallback(
() => orch.setActive(leaf.id),
[orch.setActive, leaf.id],
);
const onStatus = useCallback((msg: string, ok: boolean) => {
setStatus(msg);