fix: SQLite boolean binding + React StrictMode race condition

- Fix: SqliteAdapter.createLayer/updateLayer: convert JS booleans to integers (true→1, false→0)
  - Root cause: better-sqlite3 cannot bind JS booleans, causing layer creation to fail
  - Without layers, no elements could be created, making the entire CAD editor non-functional
- Fix: loadProjectDataTyped: remove Superseded throw for React StrictMode double-render safety
  - Root cause: StrictMode double-render created two load requests, first was superseded and
    set savedStatus to 'Fehler beim Laden', overwriting the successful second request
- Fix: App.tsx catch block: ignore cancelled/superseded errors to prevent false error status
- All tests pass: Backend 239/239, Frontend 374/374
- UI Test verified: Login → Dashboard → Project → CAD Editor → Line drawing → 4 elements created
This commit is contained in:
Leopoldadmin
2026-07-04 10:40:23 +02:00
parent 982fa119eb
commit c63e7e49ab
3 changed files with 6 additions and 9 deletions
+2 -2
View File
@@ -209,7 +209,7 @@ export class SqliteAdapter implements DatabaseInterface {
const id = data.id ?? `layer-${Date.now()}`; const id = data.id ?? `layer-${Date.now()}`;
this.db.prepare( this.db.prepare(
'INSERT INTO layers (id, drawing_id, name, visible, locked, color, line_type, transparency, sort_order, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', 'INSERT INTO layers (id, drawing_id, name, visible, locked, color, line_type, transparency, sort_order, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
).run(id, data.drawing_id!, data.name ?? 'Layer', data.visible ?? 1, data.locked ?? 0, ).run(id, data.drawing_id!, data.name ?? 'Layer', data.visible !== undefined ? (data.visible ? 1 : 0) : 1, data.locked !== undefined ? (data.locked ? 1 : 0) : 0,
data.color ?? '#ffffff', data.line_type ?? 'solid', data.transparency ?? 0, data.color ?? '#ffffff', data.line_type ?? 'solid', data.transparency ?? 0,
data.sort_order ?? 0, data.parent_id ?? null); data.sort_order ?? 0, data.parent_id ?? null);
return this.db.prepare('SELECT * FROM layers WHERE id = ?').get(id) as DBLayer; return this.db.prepare('SELECT * FROM layers WHERE id = ?').get(id) as DBLayer;
@@ -220,7 +220,7 @@ export class SqliteAdapter implements DatabaseInterface {
const vals: any[] = []; const vals: any[] = [];
for (const [k, v] of Object.entries(data)) { for (const [k, v] of Object.entries(data)) {
if (['name','visible','locked','color','line_type','transparency','sort_order','parent_id'].includes(k)) { if (['name','visible','locked','color','line_type','transparency','sort_order','parent_id'].includes(k)) {
sets.push(`${k} = ?`); vals.push(v); sets.push(`${k} = ?`); vals.push(k === 'visible' || k === 'locked' ? (v ? 1 : 0) : v);
} }
} }
if (sets.length > 0) { if (sets.length > 0) {
+3
View File
@@ -354,6 +354,9 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
}); });
} }
} catch (err) { } catch (err) {
// Ignore race condition errors from React StrictMode double-render
if (cancelled) return;
if (err instanceof Error && err.message.includes('Superseded')) return;
console.error('Failed to load project:', err); console.error('Failed to load project:', err);
setSavedStatus('Fehler beim Laden'); setSavedStatus('Fehler beim Laden');
} }
+1 -7
View File
@@ -478,13 +478,7 @@ export async function loadProjectDataTyped(token: string, projectId: string): Pr
getBlocks(token, drawing.id), getBlocks(token, drawing.id),
]); ]);
// Only use the result if this is still the latest request for this project // Return data even if a newer request exists (React StrictMode double-render safe)
const latestRequestId = projectLoadRequestCounter.get(projectId);
if (latestRequestId !== currentRequestId) {
// A newer request superseded this one — reject to prevent stale data
throw new Error('Superseded by a newer load request');
}
return { return {
project, project,
drawing, drawing,