Add M4 orchestration: broadcast, idle notifications, palette

tree.ts
- LeafNode gains broadcast?: boolean
- walkLeaves(root) generator; toggleBroadcast helper

ops.ts (PaneOps)
- toggleBroadcast, broadcastFrom, setActivePane, registerPaneId,
  notify; activeLeafId data field.

XtermPane.svelte
- onSpawn(paneId), onInput(b64), onDataReceived(),
  and focusTrigger prop. All optional; backward-compatible.

LeafPane.svelte
- 📡 broadcast toggle; 5s idle detection -> ops.notify (once per
  idle cycle); active + broadcasting border colors; click-to-focus
  via setActivePane + focusTrigger bump.

New Notifications.svelte
- Top-right toast stack, slide-in, 5s auto-dismiss + click ×.

New Palette.svelte
- Modal overlay, backdrop, filtered leaf list with ↑/↓ + Enter,
  Escape to close.

App.svelte
- paneIdByLeaf Map for routing; notifications array + auto-dismiss;
  activeLeafId; Ctrl+K global listener; broadcastFrom routes via
  walkLeaves + writeToPane to all other broadcast leaves; ⌘K button
  in titlebar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-22 13:08:40 +01:00
parent 64b90ebddb
commit 3c2f6b8640
8 changed files with 578 additions and 28 deletions

View file

@ -19,6 +19,12 @@ export interface LeafNode {
cwd?: string;
/** Optional user label shown in the pane toolbar. */
label?: string;
/**
* If true, keystrokes typed in this pane are mirrored to every other
* leaf with `broadcast === true`. Toggle via the 📡 button in the
* pane toolbar.
*/
broadcast?: boolean;
}
export interface SplitNode {
@ -174,6 +180,24 @@ export function leafCount(root: TreeNode): number {
return leafCount(root.a) + leafCount(root.b);
}
/** Iterate all leaves in left-to-right order. */
export function* walkLeaves(root: TreeNode): Generator<LeafNode> {
if (root.kind === "leaf") {
yield root;
} else {
yield* walkLeaves(root.a);
yield* walkLeaves(root.b);
}
}
/** Toggle a leaf's broadcast flag. Metadata-only — does NOT swap the id, so the pane is not respawned. */
export function toggleBroadcast(root: TreeNode, leafId: NodeId): TreeNode {
return replaceById(root, leafId, (node) => {
if (node.kind !== "leaf") return node;
return { ...node, broadcast: !node.broadcast };
});
}
export function serialize(root: TreeNode): string {
return JSON.stringify(root);
}