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:
parent
c8234442f1
commit
2a0c096095
1 changed files with 18 additions and 9 deletions
|
|
@ -41,7 +41,7 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
||||||
if (!editingLabel) return;
|
if (!editingLabel) return;
|
||||||
orch.setLabel(leaf.id, labelDraft);
|
orch.setLabel(leaf.id, labelDraft);
|
||||||
setEditingLabel(false);
|
setEditingLabel(false);
|
||||||
}, [editingLabel, orch, leaf.id, labelDraft]);
|
}, [editingLabel, orch.setLabel, leaf.id, labelDraft]);
|
||||||
const cancelLabel = useCallback(() => setEditingLabel(false), []);
|
const cancelLabel = useCallback(() => setEditingLabel(false), []);
|
||||||
const onLabelKey = useCallback(
|
const onLabelKey = useCallback(
|
||||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
|
@ -67,7 +67,7 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
||||||
setDistroOpen(false);
|
setDistroOpen(false);
|
||||||
if (d !== leaf.distro) orch.setDistro(leaf.id, d);
|
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
|
// Dismiss popover on outside click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -95,14 +95,17 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
return () => clearInterval(id);
|
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 ---------------------------------------------------------
|
// ---- broadcast ---------------------------------------------------------
|
||||||
const onTerminalInput = useCallback(
|
const onTerminalInput = useCallback(
|
||||||
(b64: string) => {
|
(b64: string) => {
|
||||||
if (isBroadcasting) orch.broadcastFrom(leaf.id, b64);
|
if (isBroadcasting) orch.broadcastFrom(leaf.id, b64);
|
||||||
},
|
},
|
||||||
[isBroadcasting, orch, leaf.id],
|
[isBroadcasting, orch.broadcastFrom, leaf.id],
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---- focus / active highlighting ---------------------------------------
|
// ---- focus / active highlighting ---------------------------------------
|
||||||
|
|
@ -114,20 +117,26 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) {
|
||||||
|
|
||||||
const onPaneClick = useCallback(() => {
|
const onPaneClick = useCallback(() => {
|
||||||
orch.setActive(leaf.id);
|
orch.setActive(leaf.id);
|
||||||
}, [orch, leaf.id]);
|
}, [orch.setActive, leaf.id]);
|
||||||
|
|
||||||
const onPaneSpawned = useCallback(
|
const onPaneSpawned = useCallback(
|
||||||
(paneId: number) => {
|
(paneId: number) => {
|
||||||
orch.registerPaneId(leaf.id, paneId);
|
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(() => {
|
useEffect(() => {
|
||||||
return () => orch.registerPaneId(leaf.id, null);
|
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) => {
|
const onStatus = useCallback((msg: string, ok: boolean) => {
|
||||||
setStatus(msg);
|
setStatus(msg);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue