Persist dashboard layout server-side, not only in localStorage #22

Open
opened 2026-07-28 16:53:45 +00:00 by claude-bot · 1 comment
Collaborator

Problem

GridStackLayout.setStorageKey(...) persists drag/resize state to browser localStorage (see frontend/components/grid-stack.ts). That means the layout is:

  • per browser and per device — nothing carries over,
  • lost when site data is cleared,
  • not shareable or inspectable server-side.

There is a second, subtler gap: only positions are persisted, not which widgets exist. Once widgets are closable (#14) and addable (widget registry issue), restoring positions alone is not enough — the restore silently skips saved nodes whose gs-id no longer exists in the DOM, and a widget added at runtime is gone after reload.

Proposal

  • Add a DashboardLayoutStore abstraction persisting, per user/session, a list of (widgetTypeId, itemId, x, y, w, h).
  • Start with a session- or file-backed implementation; the interface is the point, the backend can be swapped later.
  • Feed it from the existing GridStackLayout.addLayoutChangeListener, which already delivers List<GridStackItem.Position> server-side.
  • On view construction, rebuild the grid from the stored layout instead of the hardcoded initial positions.
  • Keep localStorage as a fast path or drop it — but do not let the two disagree; pick one source of truth.
  • "Layout zurücksetzen" must clear the server-side layout too, not just call clearStorage().

Acceptance criteria

  • Layout survives a reload in a different browser / after clearing site data.
  • Widgets added or closed at runtime are reflected after reload.
  • Reset restores the default layout and default widget set.
  • Playwright e2e covering the reload round-trip — the component tree alone cannot assert this.

Depends on

Widget registry (needs a widget type id to rebuild from, not just a position).

Scope

Medium to large. Coordinate with the registry issue; doing them together may be cleaner than sequentially.

## Problem `GridStackLayout.setStorageKey(...)` persists drag/resize state to browser `localStorage` (see `frontend/components/grid-stack.ts`). That means the layout is: - per browser and per device — nothing carries over, - lost when site data is cleared, - not shareable or inspectable server-side. There is a second, subtler gap: only *positions* are persisted, not *which widgets exist*. Once widgets are closable (#14) and addable (widget registry issue), restoring positions alone is not enough — the restore silently skips saved nodes whose `gs-id` no longer exists in the DOM, and a widget added at runtime is gone after reload. ## Proposal - Add a `DashboardLayoutStore` abstraction persisting, per user/session, a list of `(widgetTypeId, itemId, x, y, w, h)`. - Start with a session- or file-backed implementation; the interface is the point, the backend can be swapped later. - Feed it from the existing `GridStackLayout.addLayoutChangeListener`, which already delivers `List<GridStackItem.Position>` server-side. - On view construction, rebuild the grid from the stored layout instead of the hardcoded initial positions. - Keep `localStorage` as a fast path or drop it — but do not let the two disagree; pick one source of truth. - "Layout zurücksetzen" must clear the server-side layout too, not just call `clearStorage()`. ## Acceptance criteria - Layout survives a reload in a different browser / after clearing site data. - Widgets added or closed at runtime are reflected after reload. - Reset restores the default layout and default widget set. - Playwright e2e covering the reload round-trip — the component tree alone cannot assert this. ## Depends on Widget registry (needs a widget *type* id to rebuild from, not just a position). ## Scope Medium to large. Coordinate with the registry issue; doing them together may be cleaner than sequentially.
claude-bot added the enhancement label 2026-07-28 16:53:45 +00:00
pitfriedrich added the ai-ready label 2026-07-28 20:55:56 +00:00
claude-bot added ai-wip and removed ai-ready labels 2026-07-28 20:56:45 +00:00
Author
Collaborator

Needs human — blocked, not fixing via autofix.

Checked the stated dependency first: the widget registry (WidgetRegistry/WidgetDefinition) already exists and widgets are already closable/addable at runtime, so that part of the "depends on" note is resolved.

The remaining scope is still too large/unclear for autofix:

  1. No identity to persist against. The repo has no auth/user/session concept at all (no User, no login). The issue suggests starting with a "session- or file-backed" store, but the acceptance criteria require the layout to "survive a reload in a different browser / after clearing site data" — a session (cookie-bound) or a single shared file (global, not per-user) can't satisfy that without picking an actual identity model first. That's a product decision, not an implementation detail.
  2. Diff size. A real fix touches: a new DashboardLayoutStore abstraction, DashboardView rebuilt to construct from stored (widgetTypeId, itemId, x, y, w, h) instead of the hardcoded default widgets, persistence wired into every mutation path (drag/resize via addLayoutChangeListener, addWidget, widget close, KPI tiles), resetLayout() reworked to clear the server store and rebuild the default widget set (today it only clears client localStorage and relies on the DOM's existing gs-* attributes), plus a new Playwright e2e for the reload round-trip per the acceptance criteria. That's comfortably over the ~200-line diff guideline for a single autofix pass.

Recommend scoping this as a human-planned feature: decide the identity model first (even a placeholder single-user store would need an explicit choice), then split "store abstraction + wiring" from "reset semantics" from "e2e coverage" if it's still too big for one PR.

Needs human — blocked, not fixing via autofix. Checked the stated dependency first: the widget registry (`WidgetRegistry`/`WidgetDefinition`) already exists and widgets are already closable/addable at runtime, so that part of the "depends on" note is resolved. The remaining scope is still too large/unclear for autofix: 1. **No identity to persist against.** The repo has no auth/user/session concept at all (no `User`, no login). The issue suggests starting with a "session- or file-backed" store, but the acceptance criteria require the layout to "survive a reload in a different browser / after clearing site data" — a session (cookie-bound) or a single shared file (global, not per-user) can't satisfy that without picking an actual identity model first. That's a product decision, not an implementation detail. 2. **Diff size.** A real fix touches: a new `DashboardLayoutStore` abstraction, `DashboardView` rebuilt to construct from stored `(widgetTypeId, itemId, x, y, w, h)` instead of the hardcoded default widgets, persistence wired into every mutation path (drag/resize via `addLayoutChangeListener`, `addWidget`, widget close, KPI tiles), `resetLayout()` reworked to clear the server store and rebuild the default widget set (today it only clears client `localStorage` and relies on the DOM's existing `gs-*` attributes), plus a new Playwright e2e for the reload round-trip per the acceptance criteria. That's comfortably over the ~200-line diff guideline for a single autofix pass. Recommend scoping this as a human-planned feature: decide the identity model first (even a placeholder single-user store would need an explicit choice), then split "store abstraction + wiring" from "reset semantics" from "e2e coverage" if it's still too big for one PR.
claude-bot added ai-blocked and removed ai-wip labels 2026-07-28 20:57:50 +00:00
pitfriedrich added this to the Fix dashboard imporvements milestone 2026-07-29 05:59:11 +00:00
pitfriedrich added this to the Dashboard improvements project 2026-07-29 06:00:25 +00:00
Sign in to join this conversation.