View-only mode set no minZoom, so React Flow defaulted to 0.5 and big
canvases couldn't zoom out far enough to fit. Match the editor's bounds
(minZoom 0.25, maxZoom 2.5). Add a regression test asserting the props.
ha-relevant: maybe
Address review nits on the close/Enter fix:
- Add statusFilter to the keydown effect deps so Enter dispatches the
correct bulk action regardless of whether the device list changes
- Drop redundant title="Close" on the X (aria-label is enough)
ha-relevant: maybe
In the Hidden view the only bulk action is Restore, but the Enter
shortcut in select mode always called handleBulkApprove — approving
hidden devices. Dispatch on statusFilter: restore when hidden, approve
when pending. Add regression tests for both views.
ha-relevant: maybe
The close X used a raw <button onClick={onClose}>, bypassing Base UI's
dismissal. Outside-click worked (it goes through onOpenChange) but the
synthetic click on the X was dropped on Firefox/Windows. Route the X
through Base UI's DialogClose, the same path as outside-click and the
library's default close button. Add a regression test.
ha-relevant: maybe
Builds a real pre-designs database (nodes/edges without design_id, integer
canvas_state PK) and asserts init_db() adopts everything into a single
default Network Topology design with the viewport preserved, and that the
migration is idempotent across reboots. The rest of the suite uses create_all
and never exercised this in-place upgrade path.
ha-relevant: yes
Make designs (canvases) fully user-manageable: create with a chosen name
and icon, rename, change icon, and delete. Replaces the hardcoded
"New Electrical Design" button with a generic "New Canvas" flow.
- Add Design.icon column + migration that backfills legacy rows
(electrical -> zap, others -> dashboard)
- DesignModal: name input + curated lucide icon picker (create + edit)
- Sidebar switcher gains per-canvas edit/delete; delete guards the last
canvas and confirms
- designStore: addDesign/updateDesign/removeDesign with active reassignment
- Fix data loss on design switch: abort load when the save fails and keep
unsaved edits; skip the save-old step when the previous canvas was deleted
- designsApi create/update carry icon; design_type kept for back-compat
Tests: backend design CRUD (icon + cascade + last-canvas guard), designStore
actions, designIcons resolver, DesignModal create/edit/validation.
ha-relevant: yes
The MCP create_node/update_node inputSchema only advertised
type/label/ip/hostname/status (plus parent_id on update), so LLM
clients could not set documentation/hardware fields the backend
already validates and stores.
Mirror NodeBase/NodeUpdate by spreading a shared _NODE_FIELDS schema
into both tools: os, notes, mac, check_method, check_target, services,
cpu_count, cpu_model, ram_gb, disk_gb, show_hardware, container_mode,
custom_icon, properties. type stays an enum of canonical node types.
_dispatch already forwarded args verbatim, so no dispatch change.
Also extend _slim_canvas NODE_KEEP so get_canvas round-trips the new
documentation fields the LLM can now write.
Refactor tool definitions into a module-level TOOLS list for direct
schema assertions in tests.
ha-relevant: no
The backend stored the MAC, but the frontend built the approved canvas
node locally with properties=[] (non-zigbee) and no mac field. On save,
canvas/save upserts every node field from the in-memory canvas, so the
empty properties overwrote the DB MAC — the address showed after a
no-save reload but vanished once saved.
Now both approve paths (single + bulk) attach a MAC property row and
the mac field to the node added to the canvas, mirroring the backend
build_mac_property. Hidden by default, toggleable from the right panel.
- new buildMacProperty util + unit tests
- bulk-approve modal test asserts MAC propagation (IP device) and
absence for a zigbee device
ha-relevant: yes
The network scan captures each device's MAC, but approving a pending
device dropped it: the new Node had mac=NULL and no MAC in its
properties pane. Now both approve paths (single + bulk) copy the
pending device's MAC to Node.mac and add a MAC row to the node
properties so it shows in the standard properties pane with toggleable
visibility (hidden by default, consistent with zigbee props).
- build_mac_property: MAC NodeProperty row (hidden by default)
- merge_mac_property: append MAC to user-supplied props, idempotent,
preserves existing visibility
- single approve prefers the pending device MAC, falls back to payload
Adds unit tests for the helpers and API tests for both approve paths.
ha-relevant: yes
When a Zigbee device was approved (PendingDevice.status=approved + Node
created) and the user later deleted the canvas Node, the PendingDevice row
was left orphaned as approved. On re-import _persist_pending_import found
the existing row, refreshed its fields but left status=approved, so it
never reappeared in the Pending list (which filters status==pending) —
the import reported devices found but Pending stayed empty.
Reset such orphaned approved rows back to pending on re-import. Hidden
devices stay hidden (explicit user intent). Adds regression tests for
both revive-approved and keep-hidden paths.
ha-relevant: yes
Resolves CVE-2026-47180/47183/47184 flagged by Security workflow
pip-audit on main. Scanner mDNS API (AsyncServiceBrowser, async_request)
unchanged; scan tests pass.
ha-relevant: no
Issue #20: bump MAX_BOTTOM_HANDLES 48 -> 64 (covers 48+4 SFP switches) and add
a per-node "Show Port Numbers" toggle that labels each bottom connection point.
Issue #21: add `fibre` as a first-class edge/connection type (bright cyan with a
subtle glow) alongside ethernet/wifi/iot/vlan/virtual/cluster - selectable in the
edge modal, themeable, registered in the React Flow edgeTypes registry, and
round-tripped through YAML import/export.
Backport of homelable-hacs PR #23.
ha-relevant: yes
- Pad laptop:/mobile: to match each theme's existing key-column width
(dark, light, custom were off by 1-4 spaces).
- Replace hardcoded #ec4899 mobile pink with palette-coherent accents:
light -> #db2777 (contrast on light bg)
neon -> #ff3399 (neon family)
matrix -> #00cc66 (green palette)
default/dark/custom keep #ec4899.
LiveView.tsx passed raw nodes/edges from the store straight to
ReactFlow, bypassing the collapse pipeline that CanvasContainer applies
in the editor. Result: a group or zone marked collapsed in edit mode
still showed all its contents on /view?key=... and in standalone live
view. The flag was being persisted and read correctly — only the
read-only canvas ignored it.
Reuse the same memoized computeCollapseInfo + rewireEdgesForCollapse
pipeline. Add two regression tests that load a /liveview payload with
a collapsed group and assert the children never reach ReactFlow.
Three connected bugs in PR #158's collapse feature:
1. Toggle wired to the wrong component
The chevron was on GroupRectNode and computed children via React Flow
parentId. But in this codebase parentId is set by createGroup() on
type=group nodes, not on groupRect zones — zones are decorative
rectangles. Result: childrenCount was always 0 on every zone and the
button never rendered, so the feature was unreachable from the UI.
Fix:
- Add the same chevron toggle to GroupNode (the actual container).
parentId children are already known there, so the existing BFS in
computeCollapseInfo hides them when data.collapsed flips.
- For GroupRectNode, switch childrenCount to spatial containment so
drawn zones also work: hit-test other nodes' bbox centres against
the zone bbox.
2. Visibility filter ignored spatial zones
Extend computeCollapseInfo with a second pass that hides every node
whose centre lies inside a collapsed groupRect, plus the parentId
subtrees of those nodes (so a Proxmox host inside a collapsed zone
takes its VMs/LXCs with it). Edge rewiring routes vanished endpoints
to the same visible zone via a unified hiddenBy map populated by both
passes.
3. Save dropped data.collapsed for every type except groupRect
The DevTools payload was the smoking gun: for a type=group node the
serializer wrote custom_colors: {show_border: true} with no collapsed
key, so the backend stored a stale false on every save. Only the
groupRect branch of serializeNode/deserializeApiNode stashed and
hoisted the flag. Move the stash + hoist to the general branch too
(backend's custom_colors is dict[str, Any] so no schema change).
Tests: 11 new cases for spatial containment + GroupNode toggle UI, and
4 round-trip cases for collapse on non-groupRect types.
Three follow-ups to PR #158 review:
1. Promote collapsed to NodeData.collapsed
The flag was previously stashed inside NodeData.custom_colors, which is
a colors/style object — semantically wrong. Move it to a first-class
boolean on NodeData. Persistence keeps the existing API shape: serialize
writes it into the custom_colors blob (alongside width/height/z_order,
matching how groupRect already stashes layout metadata), and deserialize
hoists it back. Legacy saves from the original PR shape load correctly.
2. Re-route cross-zone edges to the collapsed ancestor
Previously any edge touching a hidden node was dropped, so a Zigbee
coordinator outside a collapsed mesh lost all visible links to it.
rewireEdgesForCollapse now walks each endpoint up the parentId chain to
its nearest visible ancestor, surfaces a single stub edge on the
collapsed zone, de-dupes parallel rewires (a 20-device mesh becomes one
stub, not twenty), and drops edges that would self-loop on a zone or
reference an orphan.
3. Revert package-lock.json churn
The 63-line diff from the original PR was npm-version drift (libc
arrays stripped from optional deps), unrelated to the feature.
Tests:
- canvasStore.collapse: updated to assert on data.collapsed.
- collapseFilter: 8 cases for visibility + 7 for edge rewire, covering
cross-boundary, nested collapse, sibling self-loop, mesh dedup, and
orphan endpoints.
- canvasSerializer.collapse: round-trip + legacy-shape compat.
Extract getVisibleNodeIds/filterVisibleEdges from CanvasContainer into
src/utils/collapseFilter.ts. Replace inline O(n^2) BFS (nested array
.find per node) with O(n) traversal backed by parentId->children and
id->node Maps, and wrap consumer calls in useMemo so visibility is
recomputed only when nodes/edges change rather than on every render.
Add 12 unit tests covering the filter logic that the original PR left
untested: single-level collapse, multi-level subtree hiding via
collapsed ancestor, sibling isolation when an inner zone is collapsed,
empty zones, missing custom_colors, insertion-order independence, and
edge filtering for hidden source/target.
Personal computing devices (laptops, phones, tablets) currently collapse
into the generic icon because the type vocabulary has no entries for
them. This adds two new NodeTypes with Lucide icons:
- laptop -> Laptop icon (reuses the computer accent color per theme)
- mobile -> Smartphone icon (new pink accent #ec4899 across themes)
Touches:
- types/index.ts NodeType union + NODE_TYPE_LABELS
- utils/nodeIcons.ts Lucide import + ICON_REGISTRY +
NODE_TYPE_DEFAULT_ICONS
- utils/themes.ts nodeAccents in all 6 themes
- canvas/nodes/index.tsx LaptopNode + MobileNode wrappers
- canvas/nodes/nodeTypes.ts register in react-flow nodeTypes
- modals/NodeModal.tsx new "Personal" type group
- modals/CustomStyleModal.tsx expose new types in style editor
- types/__tests__/types.test.ts enumerate new types
- utils/__tests__/themes.test.ts enumerate new types
Backwards-compatible: existing nodes typed as 'generic', 'server', etc.
keep rendering exactly as before. No data migration required.
- All prompted values overridable via env vars (MCP_API_KEY,
MCP_SERVICE_KEY, BACKEND_URL, INSTALL_DIR, etc.).
- Clone the repo into INSTALL_DIR if it isn't already present, so the
script can be fetched and run directly inside a fresh LXC created by
the community-scripts/ProxmoxVE helper (no manual git clone first).
- README: clarify the Proxmox flow (community-scripts creates the LXC,
user runs this script inside it).
Add mcp matrix entry in docker-publish workflow so the MCP server
image is built and pushed alongside backend/frontend. Also wire the
prebuilt compose file so users can pull and run it directly.
Closes#162
Adds scripts/lxc-mcp-install.sh for Proxmox LXC and other non-Docker
hosts. Creates a homelable-mcp systemd service, prompts for
MCP_API_KEY / MCP_SERVICE_KEY (auto-generated on Enter), and skips
prompts when mcp/.env already exists so user edits are preserved.
Closes#132
- Use nullish coalescing operator to provide empty array default
- Prevents 'Cannot read properties of undefined' error when nodes is undefined
- Fixes failing GroupRectNode tests that don't provide mock nodes
Co-authored-by: CyberClaw <noreply@openclaw.ai>
- Replace component render tests with unit tests on types and state logic
- Tests now verify: type definitions, optional properties, toggle logic, nesting support
- Removes complex mocking and React component testing that triggers act() warnings
- Full integration testing is covered by CanvasContainer tests
- Reduces test file from 142 lines to focused unit tests
Co-authored-by: CyberClaw <noreply@openclaw.ai>
- Add collapsed state to NodeData.custom_colors (type=groupRect only)
- Implement toggleNodeCollapsed action in canvasStore (Zustand)
- Extend GroupRectNode UI with smooth chevron toggle button
- Rotating chevron icon (↓ → when collapsed)
- Shows '+N' badge when zone is hidden
- Reduces zone opacity to 0.6 when collapsed
- All transitions target 60 FPS (ease-out 200ms)
- Filter child nodes/edges in CanvasContainer based on parent collapse state
- Breadth-first traversal handles multi-level nesting
- Connecting edges to hidden nodes are automatically hidden
- Add comprehensive test coverage
- Store: toggleNodeCollapsed state mutation, unsaved flag
- Component: chevron rendering, click handlers, opacity transitions
- Persist collapsed state via YAML serialization (part of custom_colors)
Benefits:
- Declutter large Zigbee meshes, multi-building networks
- Preserve layout structure without deleting nodes
- Smooth 60 FPS transitions for UX polish
CONTRIBUTING.md compliance:
- Strict TypeScript types, no 'any'
- Zustand store pattern, no prop drilling
- Tests for store logic and component behavior
- Frontend linting requirements met
Co-authored-by: CyberClaw <noreply@openclaw.ai>