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
Mirrors IP scan flow: POST /zigbee/import-pending now creates a
ScanRun(kind=zigbee, status=running) and returns immediately.
Networkmap fetch + pending upsert run in the background, status
transitions to done/error when finished.
Frontend: import modal closes on submit, scan history shows the
run with a ZIG/IP kind chip and toasts on completion. Pending
modal auto-refreshes when run finishes.
scan_runs.kind column added (default 'ip', idempotent migration).
Existing zigbee tests refactored to exercise _persist_pending_import
directly (background tasks don't see the test session); route test
verifies the run is created with kind=zigbee.
Pre-push code review flagged the inline `with suppress(OperationalError)`
ALTERs as silently eating real errors. The project still uses inline
init_db migrations (CLAUDE.md aspires to Alembic but the runtime path
is inline), so as a minimum: add a `_try_migrate` helper that
distinguishes already-applied (debug log) from genuine failures
(warning log). Apply it to the Zigbee-related migrations only — the
legacy ALTERs above remain unchanged in this commit's scope.
Coordinator auto-approves to a canvas Node; routers/end devices land in
pending_devices keyed by IEEE. Discovered parent->child edges are stored
in pending_device_links so that approving a pending device later
auto-creates the Edge once both endpoints exist as canvas Nodes.
- new POST /api/v1/zigbee/import-pending (default mode in modal)
- new pending_device_links table; ieee_address on nodes + pending_devices
- pending_devices.ip migrated to nullable (table rebuild on existing DBs)
- approve / bulk-approve return auto-created edges; sidebar pushes them
into the canvas store with bottom -> top-t handles
- ZigbeeImportModal: radio toggle pending vs canvas; reset on close
- PendingDeviceModal: zigbee badge, IEEE/LQI/vendor/model rows, services
hidden for zigbee
- Sidebar pending row: ZIG source badge, LQI badge, friendly_name fallback
- SearchBar: null-safe IP, also searches friendly_name and ieee_address
- Tooltip trigger uses asChild to avoid nested-button hydration error
- Add VERSION file at repo root as single source of truth for app version
- frontend/vite.config.ts reads VERSION file instead of package.json
- backend config.py exposes APP_VERSION read from VERSION (dev) or /app/VERSION (Docker)
- database.py backs up DB to homelab.db.back-{version} before running migrations
(skipped if DB doesn't exist or backup already exists — fully idempotent)
- Dockerfile.backend and Dockerfile.frontend copy VERSION into the image
- Add test_db_backup.py with 4 tests covering create/skip/idempotent/version cases
Add waypoints JSON column to edges table, include it in all edge
schemas (EdgeBase, EdgeUpdate, canvas CanvasEdge) and add the
idempotent ALTER TABLE migration so existing databases are upgraded
on next startup.
- canvasStore.onConnect: include animated in edge data object (was silently dropped)
- Backend schemas: normalize animated bool/int to string ('none'/'snake'/'flow') via field_validator
- ORM model: change animated column from Boolean to String
- DB migration: convert existing 0/1 boolean rows to 'none'/'snake' strings
Add NodeResizer to BaseNode so users can drag corners to resize any node.
Persist width/height through the full stack: DB model, schemas, canvas
save/load route, and migration for existing databases.
Add tests covering save, update, clear, and load of node dimensions.
- Add animated toggle per edge in EdgeModal (cyan switch, "Flow Animation")
- SVG-native <animate> element for reliable cross-browser dot animation
- Persist animated field: backend model, schemas (EdgeBase/EdgeUpdate/EdgeSave), DB migration
- Include animated in App.tsx edgesToSave serialization so it survives save/reload
- Add animated: bool to EdgeData TypeScript type
On fresh installs the data/ dir may not exist, causing SQLite to create a
stub file without write permissions. mkdir(parents=True, exist_ok=True) runs
at import time before any DB operation.
Also add data/.gitignore to prevent homelab.db from ever being committed.
- Add 65+ icons across 7 categories (Infrastructure, Media, Monitoring,
Storage, Security, Automation, Dev & Containers, Communications) covering
popular self-hosted apps: Home Assistant, Jellyfin, Plex, Grafana, Portainer,
Pi-hole, Vaultwarden, Gitea, Nextcloud, Node-RED, Frigate, etc.
- New nodeIcons.ts utility with ICON_REGISTRY, ICON_MAP and resolveNodeIcon()
- Inline icon picker in NodeModal: collapsible panel with search + grid grouped
by category; click to select, click again or Reset to revert to type default
- BaseNode uses resolveNodeIcon() so custom icon renders live on canvas
- Add custom_icon field to NodeData type, NodeBase/NodeUpdate schemas, Node ORM
model, and database.py idempotent ALTER TABLE migration
- Add custom_color field to EdgeData type, Edge DB model and all schemas
- HomelableEdge applies custom_color as stroke override (before selected highlight)
- EDGE_DEFAULT_COLORS extracted to utils/edgeColors.ts (react-refresh compliant)
- EdgeModal: color picker row showing effective color (custom or type default)
with hex value, Reset button when custom color is active
- Auto-migration adds custom_color column to edges table
- Add resolveNodeColors() utility merging type defaults with per-node overrides
- Default colors per node type (cyan=isp/router/lxc/ap, green=switch/nas,
purple=server/vm, orange=proxmox, amber=iot, gray=generic)
- Remove glowColor prop from BaseNode — colors now come from node data
- ProxmoxGroupNode uses resolveNodeColors for group border/header/icon
- NodeModal: Appearance section with 3 color swatches (border, background, icon)
— click swatch to open native color picker; Reset to defaults button
- custom_colors persisted as JSON in DB (backend model + schemas + migration)
- 7 new unit tests for resolveNodeColors covering all node types + partial overrides
- Double-click any link to open Edit Link modal (type, label, VLAN ID, delete)
- Add updateEdge / deleteEdge actions to canvasStore
- Add container_mode field to proxmox nodes (backend model, schemas, migration)
- NodeModal shows container toggle for proxmox type (defaults ON)
- ProxmoxGroupNode renders as regular BaseNode when container_mode is OFF
- setProxmoxContainerMode store action handles structural changes atomically
(children parentId/extent, node dimensions)
- CanvasContainer filters edges between container proxmox and its children
- Canvas load respects container_mode when assigning parentId/extent