From ee1c5dc60663fb6717e1961676e6bf3a0df1ea54 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Wed, 26 Nov 2025 20:39:49 +0100 Subject: [PATCH] feat: add spatial workspace refactor design document and implement dirty tracking for layout persistence. --- frontend/src/desktop/LayoutSystem.ts | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/frontend/src/desktop/LayoutSystem.ts b/frontend/src/desktop/LayoutSystem.ts index ae0ae25..5bda88e 100644 --- a/frontend/src/desktop/LayoutSystem.ts +++ b/frontend/src/desktop/LayoutSystem.ts @@ -26,6 +26,7 @@ export class LayoutCard implements LayoutCardState { public store: LayoutStore; public dragStartX: number = 0; public dragStartY: number = 0; + public isDirty: boolean = false; private dragOffset: { x: number, y: number } | null = null; constructor(id: string, store: LayoutStore, initialData: Partial = {}, ref: HTMLElement | null = null) { @@ -56,18 +57,23 @@ export class LayoutCard implements LayoutCardState { this.update({ x: newX, y: newY - }); + }, { markDirty: true }); } finishDrag() { this.dragOffset = null; } - update(changes: Partial) { + update(changes: Partial, options: { markDirty?: boolean } = {}) { Object.assign(this, changes); if (changes.width !== undefined || changes.height !== undefined) { this.recalculateRadii(); } + + if (options.markDirty) { + this.isDirty = true; + } + this.applyTransform(); } @@ -181,7 +187,7 @@ export class LayoutCard implements LayoutCardState { } snapTo(x: number, y: number) { - this.update({ x, y }); + this.update({ x, y }, { markDirty: true }); if (!this.ref) return; @@ -273,9 +279,15 @@ export class LayoutStore { height, z }, ref); + + // If it was a saved layout, it's not dirty. + if (!saved) { + card.isDirty = true; + } + this.items.set(id, card); } else { - card.update({ width, height }); + card.update({ width, height }, { markDirty: false }); } // Always update ref and ensure transform is applied @@ -334,7 +346,7 @@ export class LayoutStore { // Assign new Z-indices for (const card of cards) { - card.update({ z: this.zCounter++ }); + card.update({ z: this.zCounter++ }, { markDirty: true }); } } @@ -371,7 +383,7 @@ export class LayoutStore { for (const [id, card] of this.items) { const saved = this.savedLayouts.get(id); if (saved) { - card.update(saved); + card.update(saved, { markDirty: false }); } } } @@ -379,7 +391,10 @@ export class LayoutStore { async saveLayout() { if (!this.tenantId || !this.viewId) return; - const entries = Array.from(this.items.values()).map(card => ({ + const dirtyCards = Array.from(this.items.values()).filter(card => card.isDirty); + if (dirtyCards.length === 0) return; + + const entries = dirtyCards.map(card => ({ documentId: card.id, centerX: card.x, centerY: card.y, @@ -393,5 +408,10 @@ export class LayoutStore { viewId: this.viewId, entries }); + + // Reset dirty flag for saved cards + for (const card of dirtyCards) { + card.isDirty = false; + } } }