diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 98dc90b..1ec5c99 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -114,14 +114,58 @@ Four things worth not re-learning: selection change, which can happen mid-sweep, so a held node reference goes stale where a cache entry does not. Re-opening shows the previous still immediately while a fresh sweep replaces it, rather than flashing back to placeholders. -- **The tile is a fixed 128x72, not the row width.** A first pass used `width: 100%` with - `aspect-ratio`, which measured 292x164 tiles and 218px rows in the single list — 1166px of - content in a 640px panel, so a four-input picker scrolled. At a fixed size the name fits - *beside* the tile in the single list (~322px rows) and *below* it in a dual column - (~149px). Two rules, each matching its width. -- **Both open paths trigger it.** Hover (`mouseenter` on the trigger, which already existed - for the cursor) and touch (`toggleDropdown`). The panel's visibility is pure CSS, so that - listener is the only JS signal that a hover-open happened. +- **The tile is a fixed 128x72, not the strip width.** A first pass used `width: 100%` with + `aspect-ratio`, which measured 292x164 tiles. The name sits under the tile, and the strip + scrolls sideways when the inputs don't fit. +- **Both open paths go through `openDropdown()`.** Hover (`mouseenter` on the trigger) and + touch (`toggleDropdown`). That is where the sweep and the volume poll start. + +### The dropdown is laid over the wall (dropdown 2b) + +There is no panel. Opening it (`openDropdown`) fills `#wall-pickers` with one picker per +half — the current input, a strip of 128x72 tiles, and that half's volume — plus a capsule +at the top (Dual/Single, Output volume, Settings). There is no close button. +`renderDropdownInputLists()` kept its name so every existing caller (device change, input +switch by click or key, rename, enable/disable, layout) still refreshes it. + +| View | Multi-view | Pickers | Tap sets | Key chips | +|---|---|---|---|---| +| dual | on | Left half, Right half | that half | none (1-4 set both) | +| dual | off | Both halves | both | 1-4 | +| single | on | Whole wall | left | 1-4 | +| single | off | Whole wall | both | 1-4 | + +`pickerPlan()` is that table in code. Things worth not re-learning: + +- **It closes when the pointer goes below the pickers** (`closeDropdownIfBelow`): past + the lowest picker row plus 48px. The lowest row is the volume slider, not the + thumbnails, or reaching for the slider would close it. A tap there closes it on touch. + It also closes with Esc, or after 30s with no pointer, touch or key activity + (`DROPDOWN_IDLE_MS`), so it can never sit over an unattended wall. +- **`multiView` (default true) is new.** Off means the halves always carry the same input: + `setMultiView(false)` brings the right half in line, and `openInitialStreams` enforces it + at startup. +- **Volume belongs to a side, not an input.** A "Both halves" picker sets both sides. +- **The input-name toast is suppressed while it is open.** The pickers already name what is + on each half, and the toast would only be hidden underneath them. + +### Settings is a side-nav modal + +Four panes: Inputs, Layout, Remote keyboard, Art-Net lighting. The shortcut table and the +separate No-Signal Detection section are gone: the legend has the shortcuts, and capture +moved into each input's No-signal panel ("Capture from left/right half", for whichever half +the device is on). Pure status logic (nav dots, status lines, the Key column, +`remoteKeyUrl`) is in `src/renderer/settings-status.js`. + +- **Remote-keyboard presses go through main (`remote-key-send`)**, like Art-Net and for the + same CORS reason: a renderer `fetch` from `file://` with `X-API-Key` needs a preflight the + device does not answer. +- **Shortcuts ignore every form control**, not just ``: with a ` - 50% - - - - - - @@ -140,13 +118,13 @@ -
- ▲ + -
+
-
Keyboard Shortcuts
+
Keyboard shortcuts
@@ -162,141 +140,151 @@
- +