Visual Designer V2 - #100
Conversation
|
It also addresses #97 |
Thanks for this PR — we reviewed it in depth and the embedding is textbook: byte-identical What the next releases bring youWe've turned our conversation into the designer's next milestone import { mount } from 'odl-drawcustom-designer'; // npm package (#103)
const handle = mount(container, {
targets, // [{id, label, capabilities}] — display picker in the designer (#106)
states, // live states with HA friendly names (#107)
actions: [{ id: 'send', label: 'Send to display' }], // your buttons, our chrome (#108)
onAction: (id, payload, targetId) => hass.callService(...),
renderPreview: (payload, targetId) => …, // drawcustom dry-run → overlay (#109)
});
handle.getPayload(); // read the design directly — no Save-button scraping (#104)
We'll sequence around what unblocks you first — #103 (npm) and #104 ( |
…frequency setStates Upstream PR OpenDisplay/Home_Assistant_Integration#100 pushes the full HA entity registry via setStates up to 4x/s, even on ticks where nothing changed. useProjectState.applyStates previously replaced mockStates/ mockAttributes wholesale on every push, re-evaluating templates and re-rendering the canvas continuously. - hostStatesEqual (src/embed/hostContract.ts): cheap structural equality over the raw host states payload — linear scan, short-circuits on the first difference, no intermediate allocation. An identical push now returns before any conversion, setState, or re-render happens. - mockStatesEqual / mergeMockAttributes: for a push that does change something, bail per-part via the setState functional-updater form (React skips re-rendering a part whose updater returns the same reference), and reuse each unaffected entity's attribute object so any future per-entity memoization (e.g. a referenced-states panel row, ADR-018) can skip work. - useProjectState.ts: a new lastHostStatesRef, set synchronously inside applyStates itself (the ref-paired-with-setter convention used elsewhere in this file), gates the fast path. Investigated interaction safety (issue #110 requirement 3): a host push during an active canvas drag does not disrupt it. DesignerCanvas's drag state is React state mirrored into a ref for the hot path; the drag-start element snapshot and pointer-move math never read live elements/props; the rendered element stack is frozen for the drag's duration (frozenElements). A mockStates/mockAttributes push cannot reach any of that — proven with a test rather than adding deferral machinery for a non-problem. Tests (red-first): tests/embed/host-states.test.ts gains direct unit coverage for the three new pure helpers (verified failing on pre-fix code via git stash). tests/embed/host-states-push-diff.test.ts exercises useProjectState directly (renderHook + a stub DesignerHost capturing the push target, same pattern as tests/ui/hooks/use-project-state-history.test.ts): an identical push causes zero additional renders and preserves previewElements/mockContext identity (red on pre-fix code); a changed-subset push updates only the changed entity and preserves the unaffected entity's attribute object identity (red on pre-fix code); a push mid-coalesced-edit does not disrupt the drag (already green pre-fix — proves existing resilience, not a new fix). Full gate green: npm test (1503 tests), npm run lint, npm run build, npm run build:lib, npm run test:e2e (49 tests). Closes #110 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…frequency setStates (#114) * perf(embed): diff host state pushes; bound re-render cost under high-frequency setStates Upstream PR OpenDisplay/Home_Assistant_Integration#100 pushes the full HA entity registry via setStates up to 4x/s, even on ticks where nothing changed. useProjectState.applyStates previously replaced mockStates/ mockAttributes wholesale on every push, re-evaluating templates and re-rendering the canvas continuously. - hostStatesEqual (src/embed/hostContract.ts): cheap structural equality over the raw host states payload — linear scan, short-circuits on the first difference, no intermediate allocation. An identical push now returns before any conversion, setState, or re-render happens. - mockStatesEqual / mergeMockAttributes: for a push that does change something, bail per-part via the setState functional-updater form (React skips re-rendering a part whose updater returns the same reference), and reuse each unaffected entity's attribute object so any future per-entity memoization (e.g. a referenced-states panel row, ADR-018) can skip work. - useProjectState.ts: a new lastHostStatesRef, set synchronously inside applyStates itself (the ref-paired-with-setter convention used elsewhere in this file), gates the fast path. Investigated interaction safety (issue #110 requirement 3): a host push during an active canvas drag does not disrupt it. DesignerCanvas's drag state is React state mirrored into a ref for the hot path; the drag-start element snapshot and pointer-move math never read live elements/props; the rendered element stack is frozen for the drag's duration (frozenElements). A mockStates/mockAttributes push cannot reach any of that — proven with a test rather than adding deferral machinery for a non-problem. Tests (red-first): tests/embed/host-states.test.ts gains direct unit coverage for the three new pure helpers (verified failing on pre-fix code via git stash). tests/embed/host-states-push-diff.test.ts exercises useProjectState directly (renderHook + a stub DesignerHost capturing the push target, same pattern as tests/ui/hooks/use-project-state-history.test.ts): an identical push causes zero additional renders and preserves previewElements/mockContext identity (red on pre-fix code); a changed-subset push updates only the changed entity and preserves the unaffected entity's attribute object identity (red on pre-fix code); a push mid-coalesced-edit does not disrupt the drag (already green pre-fix — proves existing resilience, not a new fix). Full gate green: npm test (1503 tests), npm run lint, npm run build, npm run build:lib, npm run test:e2e (49 tests). Closes #110 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embed): invalidate host-push cache on local Simulator mock writes Adjudicated review finding on PR #114: `lastHostStatesRef` (issue #110) made an identical host push a no-op, but local Simulator writers (setMockState, addMockEntity, removeMockEntity, setMockAttribute, renameMockAttribute, removeMockAttribute, clearElements, loadDemo) never invalidated that cache. A Simulator edit landing between two otherwise- identical host pushes was then never reconciled back to host truth (pre-#110 behavior: every push overwrote deterministically). Each local mock-mutation path now nulls lastHostStatesRef synchronously in the same callback as its setState call, matching the file's existing ref-paired-with-setter convention. Added a red-first behavior test (host push -> Simulator edit -> identical host push -> mock state reconciles back to host value) with a pointer that issue #107 will disable the Simulator under host-fed states, making this a transition-period guarantee. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(embed): real-browser mid-drag proof for host state pushes; document setStates aliasing contract Adjudicated Copilot findings on PR #114: - The hook-level test in host-states-push-diff.test.ts proved state isolation (applyStates never touches elements/selectedIndices/history) but called the hook's coalescing methods directly, with no real DesignerCanvas drag session, frozen-elements overlay, or pointer capture. Add tests/e2e/embed-host-push-mid-drag.spec.ts: drives a real pointer drag on the demo host page's canvas, pushes setStates() through window.designerHandle mid-gesture (pointer still down, DesignerCanvas's own cursor:grabbing state confirms the drag session is live), then finishes the drag and asserts the element lands at its true final position with the pushed state applied afterward. Reworded the hook-level test's name/comments to claim exactly what it proves. - lastHostStatesRef (and the diff) alias the caller's pushed object rather than cloning it, so a host that mutates the same object in place and re-pushes gets a false "unchanged". Document the ownership contract instead of cloning (which would cost what the diff saves): docs/embedding.md's `states` section, JSDoc on HostStates/setStates in src/embed/types.ts, and a code comment on the ref itself. Red-first validation for the new e2e spec: confirmed it fails when the expected post-drag coordinates are inverted to the pre-drag values, and confirmed it fails when DesignerCanvas is deliberately remounted mid-push (temporary sabotage key tied to pushed state, reverted after observing red) — proving the spec genuinely detects a canvas losing its drag session mid-gesture, not just asserting a tautology. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Add a visual designer like initially proposed in #44 but based on the work of @schlomo in https://github.com/schlomo/odl-drawcustom-designer