Widget system Phase 2: full cutover to widget-based rendering
Build and push server image / test (push) Successful in 49s
Build and push server image / build-and-push (push) Successful in 1m56s

device.py's mode-keyed dispatch is replaced by a real compositor:
load a frame's widgets, compute pixel rects via app/grid.py, render
each through its widget module, and composite with render_panel.
Physical NEXT/BACK buttons now execute each frame's assigned
FrameButtonAction rows instead of one hardcoded per-mode action.

api_frames.py, manage.py, and common.py's build_manage_content are
repointed to read/write the frame's widget config rows instead of
the old Frame columns, and every settings page (Photos/Calendar/
Whiteboard tabs) now pre-fills its form from the same widget config
the write endpoints actually save to -- previously the read and
write sides would have silently diverged. The old mode selector and
photo-inlay checkbox are removed along with their now-inert wiring;
arbitrary widget placement subsumes what the fixed inlay split did.

Ships together with Phase 1 (per-type render/action modules) since
splitting the read/write cutover across deploys would have left
settings changes with no visible effect.
This commit is contained in:
2026-07-24 09:26:28 -04:00
parent f48daa71c8
commit 37bd657299
26 changed files with 1070 additions and 791 deletions
+4 -5
View File
@@ -1,7 +1,7 @@
// Calendar tab: view/week-start/photo-inlay settings, per-user opt-in,
// and the rendered preview. Extracted from frame_config.js when the
// Calendar card became its own tab (window.FRAME_API is set by the
// template; checkboxes are always sent explicitly as "true"/"false").
// Calendar tab: view/week-start settings, per-user opt-in, and the
// rendered preview. Extracted from frame_config.js when the Calendar
// card became its own tab (window.FRAME_API is set by the template;
// checkboxes are always sent explicitly as "true"/"false").
// Week-view-only settings (days/layout/start-offset) only matter when
// View is actually "Week"; "Week starts on" also matters for Month, so
@@ -31,7 +31,6 @@ document.getElementById('calendar-config-form').addEventListener('submit', async
calendar_week_days: document.getElementById('calendar_week_days').value,
calendar_week_layout: document.getElementById('calendar_week_layout').value,
calendar_week_start_offset: document.getElementById('calendar_week_start_offset').value,
calendar_photo_inlay: String(document.getElementById('calendar_photo_inlay').checked),
});
try {
const resp = await fetch(`${window.FRAME_API}/config`, {
+4 -32
View File
@@ -1,8 +1,8 @@
// Page-header controls shared by every per-frame page (Photos/
// Configuration/Calendar/Stats): the frame-name pencil-edit and the
// mode selector, both now living outside the tab structure since they
// apply regardless of which tab is open. Depends on window.FRAME_API
// (set per-page) and common.js's showStatus/apiError.
// Configuration/Calendar/Whiteboard/Stats): the frame-name pencil-edit,
// living outside the tab structure since it applies regardless of which
// tab is open. Depends on window.FRAME_API (set per-page) and
// common.js's showStatus/apiError.
(function () {
var view = document.getElementById('frame-name-view');
@@ -55,31 +55,3 @@
if (e.key === 'Escape') closeEdit();
});
})();
(function () {
var sel = document.getElementById('frame-mode-select');
if (!sel || !window.FRAME_API) return;
var previous = sel.value;
sel.addEventListener('change', async function () {
var mode = sel.value;
try {
const resp = await fetch(`${window.FRAME_API}/config`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ mode }),
});
if (!resp.ok) throw new Error(await apiError(resp));
previous = mode;
var modeLabels = { photos: 'Photos', calendar: 'Calendar', whiteboard: 'Whiteboard' };
showStatus(true, `Switched to ${modeLabels[mode] || mode} mode.`);
var calTab = document.querySelector('.tabs a[href$="/calendar"]');
if (calTab) calTab.classList.toggle('tab-disabled', mode !== 'calendar');
var wbTab = document.querySelector('.tabs a[href$="/whiteboard"]');
if (wbTab) wbTab.classList.toggle('tab-disabled', mode !== 'whiteboard');
} catch (e) {
sel.value = previous;
showStatus(false, e.message);
}
});
})();
-5
View File
@@ -514,8 +514,6 @@ code {
}
.frame-name-edit button { margin-top: 0; }
.frame-mode-select { width: auto; margin-top: 0; padding: 7px 10px; font-size: 13px; font-weight: 600; }
.control-banner {
display: flex;
align-items: center;
@@ -558,9 +556,6 @@ code {
.device-status-row { column-gap: 16px; }
}
.mode-picker-row { display: flex; align-items: center; gap: 8px; margin-bottom: 18px; }
.mode-picker-label { font-size: 13px; font-weight: 600; color: var(--text-muted); }
/* Mobile: sidebar off-canvas, hamburger in a slim top bar. */
.mobile-bar { display: none; }
.sidebar-backdrop { display: none; }