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

@ -0,0 +1,81 @@
<script lang="ts">
export interface Toast {
id: number;
message: string;
}
let {
notifications,
onDismiss,
}: {
notifications: Toast[];
onDismiss: (id: number) => void;
} = $props();
</script>
<div class="toast-stack">
{#each notifications as t (t.id)}
<div class="toast">
<span class="toast-msg">{t.message}</span>
<button
class="toast-x"
onclick={() => onDismiss(t.id)}
aria-label="Dismiss notification"
>×</button>
</div>
{/each}
</div>
<style>
.toast-stack {
position: fixed;
top: 36px;
right: 12px;
display: flex;
flex-direction: column;
gap: 6px;
z-index: 100;
pointer-events: none;
}
.toast {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
min-width: 220px;
max-width: 320px;
padding: 8px 10px 8px 14px;
background: #1f1f1f;
color: #ddd;
border: 1px solid #3a5a8c;
border-left-width: 3px;
border-radius: 4px;
font-size: 12px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
animation: slide-in 180ms ease-out;
}
.toast-msg {
flex: 1 1 auto;
line-height: 1.3;
word-break: break-word;
}
.toast-x {
flex: 0 0 auto;
background: transparent;
border: none;
color: #777;
font-size: 16px;
line-height: 1;
padding: 2px 6px;
cursor: pointer;
border-radius: 3px;
}
.toast-x:hover {
background: #2a2a2a;
color: #ddd;
}
@keyframes slide-in {
from { transform: translateX(20px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
</style>