Live portal: custom layouts are left alone — new streams chime and wait in a draggable tray; Arrange re-grids and returns to auto

layout store (auto/custom + pending), marked custom on manual move (onDidMovePanel outside arrange), sash drag,
stream/group rename. Tray chips: HTML5 drag accepted via onUnhandledDragOver/onDidDrop (position -> direction),
click = new column. WebAudio two-note chime. Gateway page instructions updated. Verified headless (custom vs auto).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 14:36:01 -06:00
parent b2e7e9a4bb
commit 645f447015
8 changed files with 115 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from 'react'
import { names } from './names.js'
import { layout } from './layout.js'
// Tab for a stream panel: double-click to rename (viewer-side label, kept in this browser). Empty = back to user · title.
export default function Tab({ api, params }) {
@@ -11,7 +12,7 @@ export default function Tab({ api, params }) {
const ref = useRef(null)
useEffect(() => { if (editing) { ref.current?.focus(); ref.current?.select() } }, [editing])
const start = e => { e.stopPropagation(); setDraft(custom || ''); setEditing(true) }
const commit = () => { if (!editing) return; const v = draft.trim(); names.rename(id, v); api.setTitle(v || base); setEditing(false) }
const commit = () => { if (!editing) return; const v = draft.trim(); names.rename(id, v); api.setTitle(v || base); layout.markCustom(); setEditing(false) }
return (
<div className="vtab" title={`${base}\ndouble-click to rename`} onDoubleClick={start}>
{editing