Commit Graph

652 Commits

Author SHA1 Message Date
Pouzor 6302c43e06 fix: persist top/left/right connection-point counts (issue #243)
The backend only stored bottom_handles, so top/left/right_handles were dropped
on canvas save and reset to defaults on reload — a left/right snappoint would
vanish after reload.

- models.py: add top_handles (default 1), left_handles (0), right_handles (0)
- database.py: ALTER TABLE migrations for the three columns
- schemas: add fields to NodeBase, NodeUpdate, and the canvas-save node schema
- tests: save+reload round-trip and default-fallback coverage

ha-relevant: no
2026-07-04 00:04:24 +02:00
Pouzor 63e664efdd feat: widen node modal, two-column layout, unclip side handles
- NodeModal: widen to max-w-3xl; split into Information (left) and Design
  (right) columns with headers; Notes moved to left as a resizable textarea
- Connection Points: spatial cross of -/N/+ steppers around a node preview
  (replaces the four stacked sliders)
- BaseNode: drop overflow-hidden from the node root so left/right handles are
  no longer clipped to an ungrabbable sliver (inner sections keep their own
  clipping; rounded corners preserved)

ha-relevant: yes
2026-07-04 00:03:09 +02:00
Pouzor b708a08fd0 feat: customizable connection points per side (issue #243)
Generalize the bottom-only handle machinery to all four sides. Top/Bottom
keep their single-handle default; Left/Right are opt-in (default 0) so
existing diagrams render unchanged. Handle IDs stay keyed off the bare side
name at slot 0, keeping saved edges valid.

- handleUtils: side-generic API (handleId, handlePositions, clampHandles,
  sideDefault, removedHandleIds, sideHandleCount); legacy bottom fns kept as
  delegating aliases
- BaseNode + ProxmoxGroupNode render all sides via new shared SideHandles
- updateNode remaps edges on any side's shrink (fallback to slot-0 / bottom)
- serializer round-trips top/left/right_handles
- NodeModal: spatial cross of -/N/+ steppers around a node preview, replacing
  the four stacked sliders; seeds per-type defaults
- CustomStyleModal: per-type default connection points per side

ha-relevant: yes
2026-07-03 21:25:14 +02:00
Pouzor - Rémy Jardient c9a35b730d Merge pull request #248 from Pouzor/docs/features-md
docs: add user-facing FEATURES.md
2026-07-03 14:51:24 +02:00
Pouzor d573cffe0e docs: link README to FEATURES.md before install section 2026-07-03 14:44:37 +02:00
Pouzor b59476f2f9 docs: add user-facing FEATURES.md
Feature guide with table of contents: one line per feature plus how to
enable and use it. Covers zones, groups, multiple canvases, style,
floor plan, scanner, zigbee/zwave import, device inventory, live status,
export, live view, gethomepage, MCP, settings.

Untrack FEATURES.md (was ignored as internal meta) now that it ships as
public docs.

ha-relevant: no
2026-07-03 14:30:48 +02:00
Pouzor - Rémy Jardient 9b40ba0b7c Merge pull request #247 from Pouzor/fix/mesh-import-duplicate-nodes
fix: mesh (zigbee/zwave) import — duplicate-node crash + coordinator to pending
2026-07-03 12:16:47 +02:00
Pouzor 6f82c15c69 fix: backfill inventory row for on-canvas mesh devices missing one
A device already on a canvas (Node exists) but with no pending_devices row
never showed in the discovery inventory — the inventory lists pending_devices,
not nodes. This stranded legacy auto-placed coordinators: on a canvas yet
invisible in the inventory list.

Both mesh imports now, in the already-approved-node branch, ensure an
inventory row exists: create one as status="approved" when missing, or
refresh its metadata (preserving status) when present. New/unplaced devices
still land as status="pending"; hidden rows stay hidden.

Tests updated: approved-node path now backfills an approved inventory row
(zigbee + zwave), plus a regression that a hidden inventory row is not
revived.

ha-relevant: yes
2026-07-03 12:08:53 +02:00
Pouzor 1de6e91ba3 feat: colour device-inventory role badge by node-type accent
The role/type badge on each inventory tile was always flat grey
(bg-[#21262d] text-muted-foreground), which read as disabled and gave no
visual cue about the device kind. Colour it with the same per-type accent
the node uses on the canvas — resolved through the active theme / style
section via resolveNodeColors — so a zigbee_coordinator, zwave_router, etc.
gets its style-section colour (translucent background + solid text).

Applies to every source (IP, zigbee, zwave) since all node types carry an
accent in the theme.

Test: role badge renders with the node-type accent colour, not the grey
muted class.

ha-relevant: yes
2026-07-03 11:13:18 +02:00
Pouzor 3f6e9b00f7 fix: send zigbee/zwave coordinator to pending inventory, not auto-canvas
The coordinator was special-cased in both mesh imports to auto-create a
canvas Node, so it never appeared in the pending inventory and users could
not approve/hide/type it like every other device.

Remove the auto-placement: the coordinator now flows through the shared
pending path (upsert into pending_devices with suggested_type
zigbee_coordinator / zwave_coordinator). An already-approved coordinator
Node still gets its properties refreshed on re-import via the shared
approved-node path. The response's coordinator/coordinator_already_existed
fields are retained (now always unset) for backward-compatible shape; the
frontend already ignored them.

Tests updated: coordinator lands in pending (counts include it), no Node
auto-created, pending metadata carried, approved coordinator refresh + no
pending re-list.

ha-relevant: yes
2026-07-03 02:43:42 +02:00
Pouzor 60383bee64 fix: tolerate same device on multiple canvases in zigbee/zwave import
Zigbee and Z-Wave imports looked up canvas nodes by ieee_address with
scalar_one_or_none(), assuming one node per IEEE globally. A device placed
on two designs (one Node per canvas — a supported feature) made re-import
crash with MultipleResultsFound.

- Both mesh imports now refresh properties on every matching node instead
  of a single row (loop over .scalars().all()).
- approve_device guards against a true duplicate: same IEEE already on the
  SAME design reuses that node instead of inserting a second one.
- New node_dedupe service: loss-free repair keyed on (ieee, design_id).
  Collapses only genuine same-canvas duplicates (merges properties/services/
  missing fields, re-points edges + parent_id, drops self-loops/parallel
  edges). Cross-design placements preserved. Runs at start of both imports
  and bulk-approve. No-op on healthy DBs.
- IP correlation path already handled multiple nodes; left unchanged.

Tests: dedupe unit tests (collapse, cross-design preservation, edge/parent
re-point, idempotent), zigbee + zwave multi-canvas regression, approve
no-dupe guard.

ha-relevant: yes
2026-07-03 01:52:56 +02:00
Pouzor - Rémy Jardient b0cf0deab0 Merge pull request #207 from pranjal-joshi/pranjal/floorplan
feat: floor plan map, LQI edge coloring, zigbee path highlighting
2026-07-03 00:48:39 +02:00
Pouzor bdde03cdfa feat: floor plan selection handles, dbl-click edit, bottom z-index
- Resize handles only render when the plan is selected (click to select,
  click outside to deselect); previously always shown when unlocked.
- Double-clicking an unlocked plan opens the canvas edit modal (via a
  floorMapEditNonce signal the Sidebar watches).
- Floor plan always sits at the bottom of the canvas (z-index -1), behind
  nodes and edges, locked or not.
- Remount the edit modal on every open (key bump) so it re-seeds from the
  current floor plan; fixes Save clobbering a canvas-side resize/move with
  stale modal dimensions.
- Drop the no-op history snapshot on floor-plan edits (floorMap isn't part
  of undo history).
2026-07-03 00:22:53 +02:00
Pouzor 6160090919 revert: drop zigbee LQI edge coloring and path highlighting
Split out of this PR — will land separately with reworked zigbee edge
capture/storage. Keeps only the floor plan + generic media upload work.

- Remove getLqiColor + LQI-derived iot edge stroke (edges/index.tsx)
- Remove zigbee path highlight effect (DetailPanel.tsx)
- Remove highlightedPath/setHighlightedPath store state (canvasStore.ts)
- Delete utils/zigbeePathfinding.ts
- Revert associated test scaffolding and package-lock churn
2026-07-02 17:16:51 +02:00
Pouzor 1ed013bde2 feat: floor plan viewport rendering, per-canvas config, server media upload
- Render floor plan inside React Flow ViewportPortal so it pans/zooms with
  nodes (was screen-fixed, desynced on pan/zoom); zoom-stable resize handles.
- Move floor plan config from the left panel into the canvas (design) edit
  modal; attach per-design and fix cross-design bleed on load.
- Store images via a new generic backend media endpoint (POST/GET/DELETE
  /api/v1/media) on disk under <data_dir>/uploads, not base64 in the canvas.
- Disable floor plans in standalone mode (no backend to upload/serve); drop
  base64 localStorage persistence. See ADR-001 in CLAUDE.md.
- Tests: backend media route, DesignModal floor plan + upload, store floorMap.

ha-relevant: maybe
2026-07-02 16:36:25 +02:00
Pranjal Joshi 046c99e219 fix: null -> undefined for StandaloneCanvas.floorMap 2026-07-02 10:33:57 +05:30
Pranjal Joshi 8821b05b7b feat: floor plan map, LQI edge coloring, zigbee path highlighting, eslint/test fixes, vite audit fix 2026-07-02 02:00:04 +05:30
Pouzor c0c42d5f46 chore: gitignore docs/database-model.md 2026-06-30 14:54:15 +02:00
Rémy 332b6fb4f3 Merge pull request #246 from Pouzor/release/2.6.1
Release 2.6.1: Z-Wave manual node type + standalone multi-canvas
v2.6.1
2026-06-30 14:45:53 +02:00
Rémy a8d365c1ee Merge pull request #245 from Pouzor/feat/standalone-multi-canvas
build: typecheck with tsc -b to match the production build
2026-06-30 14:45:36 +02:00
Pouzor 6e21218f05 chore: bump version to 2.6.1 2026-06-30 14:39:06 +02:00
Pouzor fb33e5cb16 feat: add Z-Wave node types to add/edit node modal
Z-Wave (controller/router/end device) types existed in the model, theme
accents, default icons and the custom-style editor, but were missing from
the node Add/Edit type selector, so they could not be placed manually like
Zigbee nodes. Add a Z-Wave group and default mesh-radio nodes to no status
check (not IP-reachable), matching Zigbee behaviour.
2026-06-30 14:38:02 +02:00
Pouzor 52de302f2b build: typecheck with tsc -b to match the production build
The `typecheck` script ran `tsc --noEmit` against the root tsconfig, which has
`files: []` and only project references — so without `-b` it checked nothing in
src and let real type errors (e.g. theme_id) reach Docker CI. Switch to `tsc -b`
(same as the build) so pre-commit and the Quality job catch them locally.

ha-relevant: no
2026-06-30 09:49:38 +02:00
Rémy f2162a663a Merge pull request #244 from Pouzor/feat/standalone-multi-canvas
feat: multi-canvas (designs) support in frontend-only standalone mode
2026-06-30 01:10:52 +02:00
Pouzor 1f2d298419 fix: type standalone canvas theme_id as ThemeId
The Docker CI build (tsc -b, stricter than the local tsc --noEmit) rejected
passing the storage theme_id (string) to setTheme/setCustomStyle which expect
ThemeId. Type StandaloneCanvas.theme_id as ThemeId so the standalone load path
matches the theme store API.

ha-relevant: no
2026-06-30 00:58:42 +02:00
Pouzor d1c052c9e8 feat: hide header View link in standalone mode
The "View" link opens the read-only live view of the canvas. In frontend-only
standalone mode the editor already renders the only (localStorage) copy, so the
live view adds nothing — hide the link. Kept in full mode.

ha-relevant: no
2026-06-30 00:48:56 +02:00
Pouzor 79cac7d80f feat: hide node stats footer in standalone mode
The Total/Online/Offline counts reflect live status-check results, which
require a backend scanner. In frontend-only standalone mode there is nothing to
populate them, so hide the footer (kept in full mode and live view).

ha-relevant: no
2026-06-30 00:33:42 +02:00
Pouzor c356a65a5f fix: repair live view + settings in standalone multi-canvas mode
Two gaps surfaced after adding per-design localStorage storage:

- Live view (/view) still read the legacy bare `homelable_canvas` key, which
  no longer exists once canvases are keyed per design — the read-only tab
  rendered empty. Now passes the active design id (?design=<id>) and reads that
  design's canvas, falling back to the first design.
- The Settings modal was gated out entirely in standalone, leaving the Settings
  button dead even though canvas prefs (snap, hide-IP) work without a backend.
  Mount it in standalone; only the backend status-check section stays hidden.

ha-relevant: no
2026-06-30 00:05:13 +02:00
Pouzor ca171089c2 feat: support multi-canvas (designs) in frontend-only standalone mode
Standalone mode (VITE_STANDALONE=true) skipped the designs system entirely:
the design list stayed empty (switcher hidden) and all canvases collapsed onto
a single localStorage key. Add a localStorage-backed design layer mirroring the
backend designs API.

- standaloneStorage util: list/create/update/delete designs + per-design canvas
  storage (homelable_designs + homelable_canvas:<id>). ensureSeed migrates a
  legacy single-canvas install into a default design so existing data survives.
- App.tsx: load/seed designs, save + switch canvases per design id in standalone.
- Sidebar.tsx: create/update/delete dispatch to standaloneStorage when standalone.
- Tests for the new storage util.

ha-relevant: no
2026-06-29 23:13:56 +02:00
Rémy e153b42b2e Merge pull request #241 from Pouzor/docs/zwave-import
docs: add Z-Wave import documentation
2026-06-29 02:10:46 +02:00
Pouzor 20dc9069d7 docs: add Z-Wave import section to README and full guide 2026-06-29 02:08:06 +02:00
Rémy c7b4db206b Merge pull request #240 from Pouzor/release/2.6.0
chore: bump version to 2.6.0
v2.6.0
2026-06-29 01:51:18 +02:00
Pouzor a05c82b4f6 chore: bump version to 2.6.0 2026-06-29 01:37:58 +02:00
Rémy 9a066a3d6e Merge pull request #239 from Pouzor/fix/export-printing-165
fix: white background option for export + Firefox download (#165)
2026-06-29 01:17:48 +02:00
Pouzor f0af367c34 fix: force export background on the live react-flow element
The html-to-image style-option override did not reliably beat the
react-flow root's colorMode background, so white exports stayed black.
Set the background colour directly on the live element for the duration
of the capture (then restore it), which html-to-image reads when it
clones computed styles.

Refs #165
2026-06-29 01:13:55 +02:00
Pouzor 46185d187f fix: actually apply white export background over react-flow root
The .react-flow root paints its own opaque background via colorMode,
so html-to-image's backgroundColor option (which only fills transparent
areas) stayed black. Override the root background with an inline style
so the chosen export background is visible.

Refs #165
2026-06-29 01:02:00 +02:00
Pouzor fd2c50c1aa fix: white background option for export + Firefox download
Fixes two issues reported in #165:

- Export always produced a black background, making prints with a
  white page wasteful/unreadable. Adds a Dark/White background choice
  in the export modal, threaded through both PNG and SVG export.
- Firefox refused the download because the programmatic anchor was not
  attached to the document. The anchor is now appended before click and
  removed after, which Firefox requires.

Closes #165

ha-relevant: yes
2026-06-29 00:47:39 +02:00
Rémy 8b9972ca78 Merge pull request #238 from Pouzor/feat/svg-export-211
feat: add SVG export option to canvas export modal
2026-06-29 00:27:11 +02:00
Pouzor 313feb8ceb feat: add SVG export option to canvas export modal
Adds an SVG format choice below the Ultra quality option in the
export modal. PNG quality options still drive raster export; the
new SVG button exports a scalable vector via html-to-image toSvg.

Closes #211

ha-relevant: yes
2026-06-28 23:52:28 +02:00
Rémy 23673eb0ca Merge pull request #237 from Pouzor/fix/mcp-create-design-id
fix: attach MCP-created nodes/edges to a design (#225)
2026-06-28 15:52:39 +02:00
Pouzor cbc2bc03c2 fix: attach MCP-created nodes/edges to a design (#225)
create_node/create_edge persisted rows with design_id=null when the
client omitted it (the MCP write tools), so they existed in the DB but
never rendered on the canvas until a container restart reconciled them.
Both routes now fall back to the first design, matching bulk-approve.

Also fix MCP resource reads (homelable://canvas, homelable://edges):
the framework passes a pydantic AnyUrl, not a str, which raised
"'AnyUrl' object has no attribute 'startswith'". Coerce to str.

EdgeResponse now exposes design_id for symmetry with NodeResponse.

ha-relevant: no
2026-06-28 14:00:59 +02:00
Rémy 7e99d77edc Merge pull request #236 from Pouzor/fix/scan-stop-cancellation
fix: make scan stop interrupt in-flight nmap range (#218)
2026-06-28 11:24:27 +02:00
Pouzor da2c1c356a fix: make scan stop interrupt in-flight nmap range
Stop button had no effect: run_scan only checked the cancel flag between
CIDR ranges and between hosts, never inside the per-range nmap call. For a
single /24 the whole scan is one blocking call, so cancel was ignored for
minutes and the run status stayed 'running'.

- thread run_id into _nmap_scan/_ping_sweep/_nmap_port_scan; check cancel
  before each phase and skip queued hosts once cancelled
- flip ScanRun status to 'cancelled' eagerly in the stop endpoint so the UI
  reacts immediately instead of waiting for a checkpoint

Fixes #218

ha-relevant: yes
2026-06-28 11:13:14 +02:00
Rémy 3cedb40d17 Merge pull request #235 from Pouzor/fix/auto-layout-child-port-order
fix: order auto-layout children by parent port number
2026-06-28 01:09:13 +02:00
Pouzor 7fdce6af37 fix: order auto-layout children by parent port number
Dagre orders sibling nodes by node-insertion order and ignores handle
ids, so after auto-layout a host's children were frequently laid out in
the reverse of the ports they plug into. Add a post-pass that keeps
Dagre's X slots but reassigns which child sits in each, sorted by the
parent's bottom-port index, shifting each child's whole subtree by the
same delta so nested nodes follow their parent. Peer-group members are
skipped so the existing peer layout is untouched.

ha-relevant: yes
2026-06-28 01:03:38 +02:00
Rémy c1b0c42f0c Merge pull request #234 from Pouzor/feat/center-new-nodes-on-screen
feat: drop new nodes at centre of visible canvas
2026-06-28 00:37:51 +02:00
Pouzor 2a8c9d618b feat: drop new nodes at centre of visible canvas
New nodes previously landed at a fixed canvas origin, often off-screen,
forcing the user to drag them into view. Add a projector (registered by
CanvasContainer inside ReactFlowProvider) that maps the visible-canvas
centre into flow coordinates, and use it for add-node, group rect, text,
Zigbee/Z-Wave imports, and pending-device approval (single + bulk).

ha-relevant: yes
2026-06-28 00:27:18 +02:00
Rémy e811d83ceb Merge pull request #233 from Pouzor/feat/inventory-timestamps
feat: surface inventory timestamps on nodes
2026-06-27 23:39:32 +02:00
Pouzor c8d25c2383 fix: bulk-approve places every selected device onto the active canvas
Bulk-approve filtered status=='pending', so a device already approved onto
another canvas (status is global, canvas membership is per-design) — or whose
node was later deleted — was silently skipped. Selecting 64 devices on an
empty canvas produced only the ~28 still pending.

Approve now places a node on the target design for any selected, non-hidden
device that isn't already on that design (deduped by ip/ieee_address, including
within the batch). Returned device_ids/node_ids stay index-aligned so the
client places them all.

ha-relevant: yes
2026-06-27 19:02:28 +02:00
Pouzor 3020b400ed fix: declare transient node timestamp attrs on PendingDevice
mypy flagged the per-request node_* timestamp attributes as undefined on the
model. Declare them as class-level defaults (like canvas_count) and set
__allow_unmapped__ so SQLAlchemy 2.0 doesn't try to map the Optional[datetime]
annotations as columns.
2026-06-27 14:29:22 +02:00