feat(canvas): distinguish new user from cleared canvas; stop demo on backend error

Cleared canvas re-showed the demo, and backend errors silently fell back to
demo — hiding real outages and forcing users to wipe demo nodes before bulk
edits. Now:
- backend load reports `initialized` (CanvasState row exists = ever saved)
- decideCanvasLoad() picks real | empty | demo; empty is kept empty
- backend down/error shows an error banner + toast, never the demo
- data-new-user flag reserved as the Getting Started walkthrough hook

ha-relevant: yes
This commit is contained in:
Pouzor
2026-07-18 23:02:44 +02:00
parent 3f030959be
commit bf8551015c
6 changed files with 169 additions and 32 deletions
@@ -0,0 +1,26 @@
import { describe, it, expect } from 'vitest'
import { decideCanvasLoad, isNewUserCanvas } from '../canvasLoadDecision'
describe('decideCanvasLoad', () => {
it('renders real nodes when the canvas has any', () => {
expect(decideCanvasLoad(true, true)).toBe('real')
// hasNodes wins even if the initialized flag is somehow false
expect(decideCanvasLoad(true, false)).toBe('real')
})
it('keeps an initialized-but-empty canvas empty (user cleared it)', () => {
expect(decideCanvasLoad(false, true)).toBe('empty')
})
it('shows the demo only for a brand-new, uninitialized canvas', () => {
expect(decideCanvasLoad(false, false)).toBe('demo')
})
})
describe('isNewUserCanvas', () => {
it('is true only for the demo mode', () => {
expect(isNewUserCanvas('demo')).toBe(true)
expect(isNewUserCanvas('empty')).toBe(false)
expect(isNewUserCanvas('real')).toBe(false)
})
})
+25
View File
@@ -0,0 +1,25 @@
/**
* Decides what to render when a design's canvas loads.
*
* Three cases must stay distinct (issue: clearing a canvas re-showed the demo):
* - `real` → the canvas has saved nodes; render them.
* - `empty` → the canvas is initialized (ever saved / explicitly created) but
* has no nodes; the user intentionally cleared it — keep it empty.
* - `demo` → brand-new, never-initialized canvas; seed the demo so first-time
* users see an example (and, later, the Getting Started walkthrough).
*
* Backend errors are handled by the caller, NOT here — a failed load must show an
* error and must never fall back to `demo` (that would hide the real failure).
*/
export type CanvasLoadMode = 'real' | 'empty' | 'demo'
export function decideCanvasLoad(hasNodes: boolean, initialized: boolean): CanvasLoadMode {
if (hasNodes) return 'real'
if (initialized) return 'empty'
return 'demo'
}
/** A new user is one who lands on the demo canvas — the Getting Started hook. */
export function isNewUserCanvas(mode: CanvasLoadMode): boolean {
return mode === 'demo'
}