From 66173f87be75d14cd14809fb7721f4966d3fdb59 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Thu, 6 Nov 2025 20:56:21 +0100 Subject: [PATCH] fix --- frontend/src/desktop/DesktopWorkspace.jsx | 48 +++++- frontend/src/desktop/useDocumentDrag.js | 2 + frontend/src/desktop/workspaceEngine.js | 193 +++++++++++++++++++++- 3 files changed, 226 insertions(+), 17 deletions(-) diff --git a/frontend/src/desktop/DesktopWorkspace.jsx b/frontend/src/desktop/DesktopWorkspace.jsx index bb69003..1811a92 100644 --- a/frontend/src/desktop/DesktopWorkspace.jsx +++ b/frontend/src/desktop/DesktopWorkspace.jsx @@ -198,14 +198,6 @@ const DesktopWorkspace = ({ engine.setEnsureDocumentSize(ensureDocumentSize); }, [engine, ensureDocumentSize]); - useEffect(() => { - engine.ensureLayoutForItems(); - }, [engine, docSizeVersion]); - - useEffect(() => { - engine.setItemRefs(itemRefs); - }, [engine, itemRefs]); - const workspaceSnapshot = useWorkspaceSnapshot(engine, useSyncExternalStore); const { layout: layoutSnapshot, @@ -215,8 +207,48 @@ const DesktopWorkspace = ({ tagDropTargetId, pendingTagDocId, pendingRemovalTag, + initialLoadDone, } = workspaceSnapshot; + useEffect(() => { + const isDevEnv = () => { + if (typeof globalThis === 'undefined') { + return true; + } + const proc = globalThis.process; + if (!proc || typeof proc !== 'object') { + return true; + } + const env = proc.env; + if (!env || typeof env !== 'object') { + return true; + } + return env.NODE_ENV !== 'production'; + }; + + const shouldWaitForPersisted = allowLayoutPersistence && !initialLoadDone; + + if (isDevEnv()) { + console.log('[desk][layout] React effect -> ensureLayoutForItems', { + docSizeVersion, + initialLoadDone, + itemCount: items.length, + allowLayoutPersistence, + shouldWaitForPersisted, + }); + } + + if (shouldWaitForPersisted) { + return; + } + + engine.ensureLayoutForItems(); + }, [engine, docSizeVersion, initialLoadDone, items.length, allowLayoutPersistence]); + + useEffect(() => { + engine.setItemRefs(itemRefs); + }, [engine, itemRefs]); + const layoutRef = useRef(layoutSnapshot); layoutRef.current = engine.layout; diff --git a/frontend/src/desktop/useDocumentDrag.js b/frontend/src/desktop/useDocumentDrag.js index b22af37..3c4a605 100644 --- a/frontend/src/desktop/useDocumentDrag.js +++ b/frontend/src/desktop/useDocumentDrag.js @@ -550,6 +550,7 @@ const useDocumentDrag = (options = {}) => { setDragTransform(item.docId, payload); const node = itemRefs.current.get(item.docId); applyDomTransform(node, payload); + console.log('[desk] drag update', item.docId, payload.centerX, payload.centerY); }); state.lastClientX = event.clientX; @@ -668,6 +669,7 @@ const useDocumentDrag = (options = {}) => { setDragTransform(state.docKey, transformPayload); const primaryNode = itemRefs.current.get(state.docKey); applyDomTransform(primaryNode, transformPayload); + console.log('[desk] drag update', state.docKey, transformPayload.centerX, transformPayload.centerY); const offsetX = pointerCanvasX - currentCenterX; const offsetY = pointerCanvasY - currentCenterY; diff --git a/frontend/src/desktop/workspaceEngine.js b/frontend/src/desktop/workspaceEngine.js index 65eb177..3da8c02 100644 --- a/frontend/src/desktop/workspaceEngine.js +++ b/frontend/src/desktop/workspaceEngine.js @@ -17,6 +17,23 @@ export const ANGULAR_DAMPING = 11; export const TORQUE_TO_ACCELERATION = 0.006; export const SETTLE_ANGULAR_VELOCITY = 1.2; +const getNodeEnv = () => { + if (typeof globalThis === 'undefined') { + return undefined; + } + const proc = globalThis.process; + if (!proc || typeof proc !== 'object') { + return undefined; + } + const env = proc.env; + if (!env || typeof env !== 'object') { + return undefined; + } + return env.NODE_ENV; +}; + +const isDevEnvironment = () => getNodeEnv() !== 'production'; + export const applyDomTransform = ( node, { @@ -416,6 +433,17 @@ export class WorkspaceEngine { } updateConfig({ allowLayoutPersistence, tenantId, viewId }) { + if (isDevEnvironment()) { + console.log('[desk][config] updateConfig -> received', { + allowLayoutPersistence, + tenantId, + viewId, + currentAllow: this.allowLayoutPersistence, + currentTenant: this.tenantId, + currentView: this.viewId, + initialLoadDone: this.initialLoadDone, + }); + } const allowChanged = typeof allowLayoutPersistence === 'boolean' && allowLayoutPersistence !== this.allowLayoutPersistence; @@ -423,6 +451,21 @@ export class WorkspaceEngine { const viewChanged = viewId !== undefined && viewId !== this.viewId; if (!allowChanged && !tenantChanged && !viewChanged) { + if (isDevEnvironment()) { + console.log('[desk][config] updateConfig -> no changes'); + } + if ( + this.allowLayoutPersistence + && this.tenantId + && this.viewId + && !this.initialLoadDone + && !this.loadingPersisted + ) { + if (isDevEnvironment()) { + console.log('[desk][config] updateConfig -> triggering load for pending persistence'); + } + this.loadPersistedLayout(); + } return; } @@ -437,6 +480,9 @@ export class WorkspaceEngine { } if (!this.allowLayoutPersistence) { + if (isDevEnvironment()) { + console.log('[desk][config] updateConfig -> persistence disabled, clearing state'); + } this.persistedLayout = new Map(); this.layoutDirty = false; this.emit(); @@ -444,10 +490,22 @@ export class WorkspaceEngine { } if (!this.tenantId || !this.viewId) { + if (isDevEnvironment()) { + console.log('[desk][config] updateConfig -> missing tenant/view, waiting', { + tenantId: this.tenantId, + viewId: this.viewId, + }); + } return; } if (!this.initialLoadDone) { + if (isDevEnvironment()) { + console.log('[desk][config] updateConfig -> fetching persisted layout', { + tenantId: this.tenantId, + viewId: this.viewId, + }); + } this.loadPersistedLayout(); } } @@ -455,8 +513,23 @@ export class WorkspaceEngine { setItems(items) { const normalized = Array.isArray(items) ? items : []; this.items = normalized; - this.ensureLayoutForItems(); + const canGenerateLayoutImmediately = + !this.allowLayoutPersistence + || !this.tenantId + || !this.viewId + || this.initialLoadDone; + if (canGenerateLayoutImmediately) { + this.ensureLayoutForItems(); + } this.recalcVisibleDocIds(); + if (isDevEnvironment()) { + console.log('[desk][config] setItems', { + itemCount: this.items.length, + canGenerateLayoutImmediately, + allowLayoutPersistence: this.allowLayoutPersistence, + initialLoadDone: this.initialLoadDone, + }); + } } setDocumentLookup(map) { @@ -490,6 +563,14 @@ export class WorkspaceEngine { this.ensureLayoutForItems(); this.recalcVisibleDocIds(); this.emit(); + if (isDevEnvironment()) { + console.log('[desk][config] setCanvasSize', { + width, + height, + allowLayoutPersistence: this.allowLayoutPersistence, + initialLoadDone: this.initialLoadDone, + }); + } } setDraggingId(docId) { @@ -828,7 +909,31 @@ export class WorkspaceEngine { } ensureLayoutForItems() { - if (!this.canvasSize.width || !this.canvasSize.height) { + if (this.allowLayoutPersistence && !this.initialLoadDone) { + if (isDevEnvironment()) { + console.log('[desk][layout] ensureLayoutForItems -> waiting for persisted layout to load'); + } + return; + } + + const persistenceReady = !this.allowLayoutPersistence || !this.tenantId || !this.viewId || this.initialLoadDone; + const canvasReady = Boolean(this.canvasSize.width && this.canvasSize.height); + const sizesReady = !this.items.some((doc) => !this.ensureDocumentSize(doc)); + + if (!persistenceReady) { + if (isDevEnvironment()) { + console.log('[desk][layout] ensureLayoutForItems -> waiting for persisted layout to load', { + allowLayoutPersistence: this.allowLayoutPersistence, + initialLoadDone: this.initialLoadDone, + }); + } + return; + } + + if (!canvasReady) { + if (isDevEnvironment()) { + console.log('[desk][layout] ensureLayoutForItems -> canvas missing size', this.canvasSize); + } return; } if (!this.items.length) { @@ -839,8 +944,10 @@ export class WorkspaceEngine { return; } - const missingSizes = this.items.some((doc) => !this.ensureDocumentSize(doc)); - if (missingSizes) { + if (!sizesReady) { + if (isDevEnvironment()) { + console.log('[desk][layout] ensureLayoutForItems -> waiting for document sizes'); + } return; } @@ -849,8 +956,11 @@ export class WorkspaceEngine { const canvasWidth = this.canvasSize.width || DESK_DEFAULT_CANVAS_WIDTH; const canvasHeight = this.canvasSize.height || DESK_DEFAULT_CANVAS_HEIGHT; const docsNeedingLayout = []; + let generatedCount = 0; const currentEntries = new Map(this.layout); + let persistedAppliedCount = 0; + let reusedCurrentCount = 0; this.items.forEach((doc) => { if (!doc?.id) { @@ -870,11 +980,11 @@ export class WorkspaceEngine { const minCenterY = DESK_CANVAS_PADDING + halfHeight; const maxCenterY = Math.max(minCenterY, canvasHeight - DESK_CANVAS_PADDING - halfHeight); - const persisted = this.persistedLayout.get(docKey) || null; - let existing = currentEntries.get(docKey) || null; - if (persisted) { - existing = existing ? { ...existing, ...persisted } : { ...persisted }; - } + const persisted = this.persistedLayout.get(docKey); + const currentEntry = currentEntries.get(docKey) || null; + const hasPersisted = Boolean(persisted); + const hasCurrent = !hasPersisted && Boolean(currentEntry); + let existing = persisted || currentEntry; if (existing) { const defaultCenterX = (minCenterX + maxCenterX) / 2; const defaultCenterY = (minCenterY + maxCenterY) / 2; @@ -886,6 +996,11 @@ export class WorkspaceEngine { const z = existing.z ?? maxZ; maxZ = Math.max(maxZ, z); next.set(docKey, { centerX, centerY, rotation, z, width: docWidth, height: docHeight }); + if (hasPersisted) { + persistedAppliedCount += 1; + } else if (hasCurrent) { + reusedCurrentCount += 1; + } return; } @@ -912,14 +1027,31 @@ export class WorkspaceEngine { ); generatedLayout.forEach((entry, docId) => { next.set(docId, entry); + generatedCount += 1; }); maxZ = Math.max(maxZ, updatedMaxZ); + if (isDevEnvironment()) { + console.log('[desk][layout] ensureLayoutForItems -> generated layout for items', { + requested: docsNeedingLayout.length, + applied: generatedCount, + }); + } } this.layout = next; this.zCounter = Math.max(this.zCounter, maxZ); this.syncLayoutSnapshot(); this.recalcVisibleDocIds(); + if (isDevEnvironment()) { + console.log('[desk][layout] ensureLayoutForItems -> applied layout', { + items: this.items.length, + layoutSize: this.layout.size, + persistedAppliedCount, + reusedCurrentCount, + generatedCount, + initialLoadDone: this.initialLoadDone, + }); + } } recalcVisibleDocIds() { @@ -1073,6 +1205,7 @@ export class WorkspaceEngine { tagDropTargetId: this.tagDropTargetId, pendingTagDocId: this.pendingTagDocId, pendingRemovalTag: this.pendingRemovalTag, + initialLoadDone: this.initialLoadDone, }; } @@ -1089,14 +1222,38 @@ export class WorkspaceEngine { async loadPersistedLayout() { if (!this.allowLayoutPersistence || !this.tenantId || !this.viewId) { + if (isDevEnvironment()) { + console.log('[desk][load] loadPersistedLayout -> aborted, missing config', { + allowLayoutPersistence: this.allowLayoutPersistence, + tenantId: this.tenantId, + viewId: this.viewId, + }); + } return; } if (this.loadingPersisted || this.initialLoadDone) { + if (isDevEnvironment()) { + console.log('[desk][load] loadPersistedLayout -> already loading or done', { + loadingPersisted: this.loadingPersisted, + initialLoadDone: this.initialLoadDone, + }); + } return; } this.loadingPersisted = true; try { + if (isDevEnvironment()) { + console.log('[desk][load] loadPersistedLayout -> fetching', { + tenantId: this.tenantId, + viewId: this.viewId, + }); + } const records = await fetchLayoutRecords({ tenantId: this.tenantId, viewId: this.viewId }); + if (isDevEnvironment()) { + console.log('[desk][load] loadPersistedLayout -> fetched records', { + count: records.length, + }); + } const map = new Map(); records.forEach((record) => { if (!record || !record.documentId) { @@ -1119,11 +1276,29 @@ export class WorkspaceEngine { this.layoutSnapshot = new Map(this.layout); this.ensureLayoutForItems(); this.initialLoadDone = true; + if (isDevEnvironment()) { + console.log('[desk][load] loadPersistedLayout -> applied persisted layout', { + layoutSize: this.layout.size, + persistedSize: this.persistedLayout.size, + }); + } this.emit(); } catch (error) { console.warn('[desk] Failed to load persisted layout', error); } finally { this.loadingPersisted = false; + if (!this.initialLoadDone) { + this.initialLoadDone = true; + if (!this.layout.size) { + this.ensureLayoutForItems(); + } + if (isDevEnvironment()) { + console.log('[desk][load] loadPersistedLayout -> fallback to generated layout', { + layoutSize: this.layout.size, + }); + } + this.emit(); + } } } }