fix: restore saved gridstack layout faithfully (#13) #15

Merged
pitfriedrich merged 1 commits from ai/issue-13-persist-layout into main 2026-07-26 18:07:49 +00:00
Collaborator

Closes #13

Das Layout wurde bereits in localStorage gespeichert, beim Wiederbetreten der Route aber nicht zuverlässig so wiederhergestellt, wie es verlassen wurde. Drei Ursachen:

1. Wiederherstellung Element für Element (grid-stack.ts)
restore() hat die gespeicherten Knoten einzeln per grid.update() gesetzt. Ein später gesetztes Widget konnte mit einem bereits platzierten kollidieren und es von seiner gespeicherten Position wegschieben — zurückgeschoben wurde es nie. Jetzt grid.load(nodes, false): sortiert die Knoten, nimmt sie vor dem Neuplatzieren aus der Engine und läuft in einem Batch. addRemove = false, weil Flow besitzt, welche Kinder existieren.

2. Größe 1 ging verloren (grid-stack.ts, GridStackLayout.java)
gridstacks save() lässt w/h weg, wenn sie 1 sind. Die manuelle Wiederherstellung reichte sie als undefined weiter, und der Server las sie als 0 und schrieb ein nulldimensioniertes Widget auf das Item zurück. load() setzt gridstacks eigene Defaults wieder ein; onLayoutChange liest ein fehlendes w/h jetzt als 1.

3. Ausstehender Save beim Verlassen der Route verworfen (grid-stack.ts)
Drag/Resize direkt gefolgt von einer Navigation hat das Element innerhalb des 150-ms-Debounce abgehängt — der ausstehende Save ging verloren. disconnectedCallback() schreibt ihn jetzt zuerst nach localStorage (ohne Server-Benachrichtigung, die View verschwindet ohnehin).

Test

  • ./mvnw test → 3/3 grün (GridStackViewTest)
  • npx tsc --noEmit → sauber

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ue9ZtWUBQF4SuSHpzZ3zwq

Closes #13 Das Layout wurde bereits in `localStorage` gespeichert, beim Wiederbetreten der Route aber nicht zuverlässig so wiederhergestellt, wie es verlassen wurde. Drei Ursachen: **1. Wiederherstellung Element für Element (`grid-stack.ts`)** `restore()` hat die gespeicherten Knoten einzeln per `grid.update()` gesetzt. Ein später gesetztes Widget konnte mit einem bereits platzierten kollidieren und es von seiner gespeicherten Position wegschieben — zurückgeschoben wurde es nie. Jetzt `grid.load(nodes, false)`: sortiert die Knoten, nimmt sie vor dem Neuplatzieren aus der Engine und läuft in einem Batch. `addRemove = false`, weil Flow besitzt, welche Kinder existieren. **2. Größe 1 ging verloren (`grid-stack.ts`, `GridStackLayout.java`)** gridstacks `save()` lässt `w`/`h` weg, wenn sie `1` sind. Die manuelle Wiederherstellung reichte sie als `undefined` weiter, und der Server las sie als `0` und schrieb ein nulldimensioniertes Widget auf das Item zurück. `load()` setzt gridstacks eigene Defaults wieder ein; `onLayoutChange` liest ein fehlendes `w`/`h` jetzt als `1`. **3. Ausstehender Save beim Verlassen der Route verworfen (`grid-stack.ts`)** Drag/Resize direkt gefolgt von einer Navigation hat das Element innerhalb des 150-ms-Debounce abgehängt — der ausstehende Save ging verloren. `disconnectedCallback()` schreibt ihn jetzt zuerst nach `localStorage` (ohne Server-Benachrichtigung, die View verschwindet ohnehin). ## Test - `./mvnw test` → 3/3 grün (`GridStackViewTest`) - `npx tsc --noEmit` → sauber 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Ue9ZtWUBQF4SuSHpzZ3zwq
claude-bot added 1 commit 2026-07-26 18:00:30 +00:00
fix: restore saved gridstack layout faithfully (#13)
CI / build-and-test (pull_request) Successful in 1m17s
85bb0c8c0b
The layout was saved to localStorage, but restoring it did not reliably
reproduce it:

- restore() applied the saved nodes one by one via grid.update(). A later
  item could collide with one already put back and push it off its saved
  spot, and nothing moved it back — so the restored layout was not the one
  the user left. Use grid.load(nodes, false) instead: it sorts the nodes,
  removes them from the engine before re-placing them, and runs in a single
  batch. addRemove is off because Flow owns which children exist.
- gridstack's save() omits w/h when they are 1. The manual restore passed
  them through as undefined, and the server read them as 0 and wrote a
  zero-sized widget back onto the item. load() re-applies gridstack's own
  defaults; onLayoutChange now reads a missing w/h as 1.
- A drag followed immediately by navigating to another route detached the
  element inside the 150ms persist debounce, dropping the pending save.
  disconnectedCallback now flushes it to localStorage first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue9ZtWUBQF4SuSHpzZ3zwq
pitfriedrich approved these changes 2026-07-26 18:04:23 +00:00
pitfriedrich left a comment
Owner

Alles ok

Alles ok
@@ -125,4 +143,1 @@
for (const node of nodes) {
if (!node.id) continue;
const el = this.querySelector(`:scope > [gs-id="${node.id}"]`) as HTMLElement | null;
Owner

Excellent fix 👍

Excellent fix 👍
pitfriedrich merged commit f242bbd8a6 into main 2026-07-26 18:07:49 +00:00
pitfriedrich deleted branch ai/issue-13-persist-layout 2026-07-26 18:07:49 +00:00
Sign in to join this conversation.