Theme scrollbars + global broadcast toggle

Two small QoL additions:

- styles.css: WebKit pseudo-element styling for .xterm-viewport
  scrollbars (8px wide, dark thumb #2a2a2a on transparent track,
  hover lighten). Matches the rest of the dark theme so the right
  edge of each terminal stops looking like default OS chrome.

- tree.ts: setAllBroadcast(root, on) helper that flips every leaf's
  broadcast flag to the given value, preserving object identity
  where nothing changed.

- App.tsx: titlebar 📡 button showing global broadcast state
  ("all off" / "all on" / "N/M"). Click toggles every pane between
  all-broadcasting and all-off. Orange when any panes are
  broadcasting; darker orange when partial.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-22 18:18:56 +01:00
parent 774b8633dc
commit 369ec8e2fd
4 changed files with 86 additions and 0 deletions

View file

@ -47,6 +47,15 @@
color: #cce6ff;
border-color: #2a5a8c;
}
.palette-btn.bcast-all.on {
background: #4a3010;
color: #f0c060;
border-color: #c98a1f;
}
.palette-btn.bcast-all.on.partial {
background: #2a2010;
color: #c98a1f;
}
.preset-btn {
min-width: 28px;
text-align: center;

View file

@ -21,6 +21,7 @@ import {
changeDistro,
changeLabel,
toggleBroadcast as toggleBroadcastInTree,
setAllBroadcast,
serialize,
deserialize,
presetSingle,
@ -282,6 +283,29 @@ export default function App() {
[paletteOpen, tree],
);
// ---- global broadcast state (derived from tree) -------------------------
const broadcastStats = useMemo(() => {
let on = 0;
let total = 0;
for (const leaf of walkLeaves(tree)) {
total++;
if (leaf.broadcast) on++;
}
return { on, total };
}, [tree]);
const toggleBroadcastAll = useCallback(() => {
// If any pane is broadcasting, turn them all off. Otherwise turn them all on.
setTree((t) => setAllBroadcast(t, broadcastStats.on === 0));
}, [broadcastStats.on]);
const broadcastBtnLabel =
broadcastStats.on === 0
? "📡 all off"
: broadcastStats.on === broadcastStats.total
? "📡 all on"
: `📡 ${broadcastStats.on}/${broadcastStats.total}`;
const onPalettePick = useCallback((leafId: string) => {
setActiveLeafId(leafId);
setPaletteOpen(false);
@ -321,6 +345,20 @@ export default function App() {
<button className="preset-btn" title="2 × 2 grid" onClick={() => applyPreset(presetTwoByTwo)}>2×2</button>
</span>
<button
className={`palette-btn bcast-all${broadcastStats.on > 0 ? " on" : ""}${broadcastStats.on > 0 && broadcastStats.on < broadcastStats.total ? " partial" : ""}`}
onClick={toggleBroadcastAll}
title={
broadcastStats.on === 0
? "Click to broadcast input to ALL panes"
: broadcastStats.on === broadcastStats.total
? "All panes broadcasting — click to disable"
: `${broadcastStats.on} of ${broadcastStats.total} panes broadcasting — click to disable all`
}
>
{broadcastBtnLabel}
</button>
<button
className="palette-btn"
onClick={() => setPaletteOpen(true)}

View file

@ -198,6 +198,19 @@ export function toggleBroadcast(root: TreeNode, leafId: NodeId): TreeNode {
});
}
/** Force every leaf's broadcast flag to `on`. Returns the same root reference
* when nothing actually changed, so callers can skip a state update if so. */
export function setAllBroadcast(root: TreeNode, on: boolean): TreeNode {
if (root.kind === "leaf") {
if (!!root.broadcast === on) return root;
return { ...root, broadcast: on };
}
const a = setAllBroadcast(root.a, on);
const b = setAllBroadcast(root.b, on);
if (a === root.a && b === root.b) return root;
return { ...root, a, b };
}
export function serialize(root: TreeNode): string {
return JSON.stringify(root);
}

View file

@ -37,3 +37,29 @@ body {
.xterm { height: 100%; }
.xterm-viewport { background: #0c0c0c !important; }
/* Themed scrollbars — Chromium pseudo-elements (WebView2 supports these). */
.xterm-viewport::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.xterm-viewport::-webkit-scrollbar-track {
background: transparent;
}
.xterm-viewport::-webkit-scrollbar-thumb {
background: #2a2a2a;
border-radius: 4px;
border: 1px solid #1a1a1a;
}
.xterm-viewport::-webkit-scrollbar-thumb:hover {
background: #3a3a3a;
}
.xterm-viewport::-webkit-scrollbar-corner {
background: transparent;
}
/* Firefox fallback (and the new spec) not strictly needed in WebView2
but free-and-correct. */
.xterm-viewport {
scrollbar-width: thin;
scrollbar-color: #2a2a2a transparent;
}