Repository navigation
Stacks, display Modes, takeovers, and moving widgets between tabs - #378
Conversation
Stacks: several widgets in one spot, swiped between, while the rest of the dashboard stays put (like an iPhone Smart Stack). - Make one on the wall (Move/Resize: hold a widget over another) or under Admin -> Widgets -> Stacks, which also sets the tabs it shows on. - A quick sideways flick turns it, also inside plugin frames (the frame's touch script relays a flick only while the plugin is in a Stack). Dots and arrows show on touch, then fade. - Goes back to its first widget after a while untouched, and "Bring to front when..." shows a widget while a Home Assistant entity is (not) in a state (GET /api/devices/:name/stacks/ha). - Widgets shown in a placed Stack no longer count as "enabled but on no tab" in Admin, so saving doesn't ask to add them again. Move/Resize: - Drag a widget onto a dock tab, or hold it at the screen edge, to carry it to another tab (phone style), or onto + for a new tab. - Long-press a widget: Move to tab, Duplicate to tab (or a new tab), Widget settings, Remove from dashboard (asks, then Undo). On a Stack also: the showing widget's settings, the others', Stack settings, Unstack. PUT /widget-assignments/entry restores a removed entry. Display Modes (Admin -> Displays -> Modes): a wall display changes through the day without anything moving. A mode turns on by schedule, or from Home Assistant (a phone's next alarm, an entity's state, only while someone is home); it can start on a tab, dim the screen to off, and change what each Stack shows (which widgets, which first, go-back time, cycle through widgets, pause Bring to front). A mode label beside the dock pins or switches modes. HomeGlow can make a dropdown helper in Home Assistant (Automatic, Normal and the modes) kept in step both ways, and report the mode showing as a sensor. Takeovers: a widget can take over the top or bottom half, or the whole screen, while a Home Assistant rule is on, then hand the wall back. Two can share the screen; plugins can raise or close their own with HomeGlow.attention(). Docs: README features, frontend reference, plugin guide (takeovers). Tests: client (Vitest) and server routes for both Home Assistant reads, the restore-entry route and the flick relay.
|
@jasonstantler drap and drop to tab has some hitches: Test 1
Test 2
3. The widget just needs the tab reset in admin panel when it panics. I also saw it work correctly, where it prompted for a tab create and moved automatically to the new tab. Most of the time it just drops the tab from the widget and it needs to be reapplied.
Stacks seem to work well. |
Modes no longer rearrange the dashboard; comments and a test name still talked about mode layouts and the layout editor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3
Over the dock (a tab or +), a drag counted as hovering a widget lying underneath it: let go there and the two were stacked instead of the widget moving, so it seemed to vanish from its tab. A Stack target is now only the topmost widget under the finger, never past the dock, the grid's drop shadow or the dragged widget; "Stack with..." clears when a widget is carried to another tab. Moving or duplicating to another tab is one server write (POST widget-assignments/transfer, which also takes where it lands), so a widget is never left on no tab and keeps its per-tab settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3
The drop outline jumped a whole grid cell in one frame (transitions are off while a widget is selected); it now glides, as do the carry and "Stack with..." outlines. While "Stack with..." shows, the grid's drop outline hides, so only one outline is on screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3
…herforth#185) Making a Stack with Weather in Move/Resize threw "Maximum update depth exceeded" from the forecast chart: its data was a new array on every render, and the chart's store updated over and over. The chart data and its dot style are now stable between renders. Also: hidden Stack pages are inert (inert="" was read as false in React 19). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3
|
@jasonstantler still a little buggy:
|
…witch Positions saved from Move/Resize reached the server but not the dashboard's own copy, which the grid redraws a tab from when you come back to it, so they showed in their old spots until a refresh. The grid now hands the saved rows to app.jsx, which updates its copy right away. Also: a save still waiting for one tab is sent instead of dropped when another tab saves within the half second, and a save landing mid-drag doesn't redraw the tab under the finger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3
…takeovers redraw only on change The Add devices box got a new empty list on every redraw, which it read as a new pick and cleared the typing. Takeovers re-checked their rules every second and handed back a new answer each time, rebuilding every widget; the answer is now kept unless something changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@jasonstantler when this is good, have claude rebase so the conflicts are resolved and then we can get it in. |
|
This is a neat addition! A few issues that came up on a "take a quick look at this PR" on Claude.
Also worth a look:
For the rebase onto #375: the new writers ( |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3
- Stacking keeps the widgets' tab entries, marked in_stack, so their per-tab settings stay (Weather saves no longer 404) and come back when unstacked; Remove takes them along and Undo puts them all back. New PATCH widget-assignments/in-stack; older Stacks are repaired once. - Admin's Stacks and tab pickers mark/unmark instead of deleting. - Unstacking puts widgets back where they were when that spot is free. - Drags: the dock counts only on its buttons, the side of the screen only after moving toward it and a longer wait, and stacking takes a resting hold on the middle of the other widget. - An alarm that rang after "until" turns no mode on. - Home Assistant: one read at a time, backing off on failures; a short blip keeps the last states; takeovers forget nothing while it's away. The server remembers a failed read briefly and shares reads. - A plugin repeating the same attention() changes nothing: no second chime, and a closed takeover stays closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3
…overs) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3
|
@jasonstantler Thanks, this is close. On the latest head (12b25f5), server tests (745), client tests (1018),
Two things need fixing before merge: 1. A Stack made by holding one widget on another breaks the page until a refresh. This is my Test 1 from the first comment. Steps:
After a refresh, the same Stack selects, drags to the other tab and takes its widgets along, with no errors. So the trouble is in the session where the Stack was made. The loop shows up in Chores' 2. The display-mode Home Assistant routes can write to any
An
The sensor that Small and unrelated: under Stacks and the Move/Resize changes otherwise look good. Once 1 and 2 are fixed, I'll recheck and merge. 🤖 Generated with Claude Code |
Stack writes are now one write each on the server, so a display never sees
a Stack without its widgets (server/utils/stackEntries.js):
- Moving or duplicating a Stack carries its widgets in one transaction
(transfer with members/merge). Before, a refresh mid-move ran the repair
and split the Stack: widgets popped back on the old tab, blank copies on
the new one. The saved Stack decides which widgets go, not the list the
display sends; a widget still marked but no longer in it stays, unmarked.
- Removing a Stack takes its widgets in the same write (?members=1) and
returns what it took; Undo puts it all back in one write (entries), and
only what isn't on the tab again since (onlyMissing).
- Unstacking is one write (new POST widget-assignments/unstack).
- A new Stack is placed and its widgets marked in one write (in-stack place).
- Deleting a tab keeps widgets in their Stack when it lands on Home too.
Undo:
- Notices are queued (utils/undoNotices.js): another notice no longer
throws away a pending Undo.
- A remove that timed out but happened still offers Undo.
- A double tap undoes one removal, not two.
- Undo turns the widget back on if a removal turned it off.
Admin -> Stacks no longer deletes widgets' entries: taken out of a Stack,
a widget with a spot or settings comes back on its own (in-stack release,
unstack dropBlank). Failures stop the save and say so.
Modes:
- Alarm windows longer than 12 hours work when "until" is later the same
day ("until 00:00" is the end of that day); they end on the clock across
a clock change.
- A wall pin survives Home Assistant being away, and a pin made while it
was away survives it coming back.
- The wall's helper choice is pending until Home Assistant shows it, and
is sent again when it comes back; a stale or slow read no longer cancels
the pin.
Also: a 13th widget can't be stacked (notice), stacking/unstacking failures
show a notice, a failed Stack repair retries after a minute, moves and
removes time out after 20 s, and the API docs list the new routes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015FyTJ6Vw8qPecynm2Ho3r8
Stacks on a touch screen: - A flick no longer turns a Stack when it starts on something that takes sideways drags itself: a slider or text field, a list that scrolls sideways, an element with touch-action none/pan-y (a graph you scrub), or anything marked data-no-flick; nor inside a dialog a stacked widget opened. Same rule inside plugin frames (server/utils/pluginFrameTouch.js, client/src/utils/flickTarget.js); documented for plugin authors. - A carry the browser cancels (pointercancel/touchcancel) puts the widget back and returns to its tab, instead of dropping it. - "Bring to front" with no return time: checked again once the Stack has been left alone, so an alert that came in just after a touch isn't missed. Stack data: - The Stack repair also marks a Stack's widget that's on the tab on its own (a deleted tab's Stack landing on Home where it already was, an Admin save that didn't finish), so it doesn't show twice; settings kept. A mark for a Stack that's gone doesn't block it. - Admin saves a Stack by id, so a retry after a half-done save doesn't add a second copy. - Stacking on the wall: if placing fails, the new Stack (or the added widget) is taken back out of the saved Stacks, read fresh, unless the write turns out to have gone through. Undo and dimming: - An Undo whose answer is lost but went through finishes (turns the widget on, shows the tab) instead of saying it failed. - A late Undo keeps a Stack mark the widget got since (onlyMissing). - A mode letting go of its dim goes back to idle dimming only if the screen has been idle that long; otherwise it's bright. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ6Vw8qPecynm2Ho3r8
The rest of the previous commit, left out of it by mistake: - A flick inside a dialog or menu a stacked widget opened no longer turns the Stack (React passes those touches up to it). - Tick boxes, radio buttons and buttons don't block a flick (text, range and other drag-taking inputs still do); any contenteditable does; pan-left / pan-right count as the browser's; a sideways scroller must have a real range (over 8 px), so an up-and-down list a pixel too wide doesn't block. Same in the plugin-frame copy, which can no longer throw. - Stacking on the wall: when the placing write got no answer, the tab is checked; if it went through, the Stack is kept. A rollback now starts from the Stacks as saved now (the new Stack taken out, or the one added to put back as it was), so a change made meanwhile isn't lost. - The Stack repair treats a mark for a Stack that's gone, or doesn't hold the widget, as no mark, so such a widget doesn't show twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FyTJ6Vw8qPecynm2Ho3r8
|
@jasonstantler Rechecked on 18a777b. Server tests (764), client tests (1031), 1 (a Stack made live breaks the page) is fixed. In Edge, holding Photos on Chores makes the Stack with no "Maximum update depth" errors, and none come later. The Stack stays one card wide, nothing is left floating, and dragging it onto a new tab in the dock moves it with both widgets (their settings kept, still marked 2 (the display-mode Home Assistant routes) is unchanged, so that's what's left before merge:
The fix suggested last time still fits: keep the stored 🤖 Generated with Claude Code |
Resolve conflict in client/src/app.jsx dock hidden prop: keep both the carry-drag exception from jherforth#378 and the dialog-open hide from this PR, per jherforth's review on jherforth#383.
…rforth#382, jherforth#384) and origin/test/all-combined into test/all-combined Upstream: Stacks/display Modes/takeovers (jherforth#378), routine complete-all (jherforth#380), mirror re-measure (jherforth#381), shared New Chore dialog (jherforth#382), admin PIN chooses the mode helper (jherforth#384). Fork side: Open-Meteo provider, precipitation probability, bonus-chore return to pool, Google Tasks no-due-date fix, dock-hide-on-dialog, HA panel test seed. Conflicts (fork's jherforth#380/jherforth#382 branches vs upstream's merged versions): took upstream's refinements (scheduleOnly, tickRemainingStepsOrThrow); app.jsx combines both (dock stays hidden while dragging is false and a dialog is open). Auto-seed now strips the HA mode-helper link from seeded settings, so a new display never inherits another display's helper (jherforth#384: only the admin PIN chooses it). The Stacks tests pre-create their devices in before(), while no device has widgets yet, so auto-seed leaves them empty.


Three things that work together on a wall display, all tested on a portrait touch screen.
Stacks
Several widgets in one spot, swiped between, while the rest of the dashboard stays put (like an iPhone Smart Stack).
Move/Resize
Display Modes and takeovers (Admin → Displays → Modes)
HomeGlow.attention()(documented in the plugin guide).API
GET /api/devices/:name/stacks/haand/stacks/ha/entities/api/devices/:name/modes/ha(states, entities, helper, sync, select, report)PUT /api/devices/:name/widget-assignments/entry(Undo for Remove)Tests
Client (Vitest, 972 passing) and
check:i18n(English and Spanish). Server route tests for both Home Assistant reads, the restore-entry route and the flick relay. The fourgoogleTimeoutstests need internet access, which my test sandbox does not have; they are unrelated to this change.🤖 Generated with Claude Code
https://claude.ai/code/session_015FyTJ3CQ7VFGMpS8JsXcc3