m's bundling primitive: a clamp is a physical anchor on the canvas;
cables route through clamps in order; cables that share a consecutive
clamp pair are visibly bundled on that segment. Overlap is the bundle —
no detection pass.
Section covers:
- 11.1 Schema: clamps table + cable_clamps join, migration 007. Clamps
carry frame_id so frame-drag carries them.
- 11.2 Cable rendering: <polyline> through [from, clamp₁..n, to];
endpoint-replug handles stay on first/last vertices.
- 11.3 Bundle visualisation: shared segments rendered as a 2+N px
striped line; clamp icon shows ×N count when shared. Computed live
on every renderCanvas — O(C·N̄), trivial at v0 scale.
- 11.4 UI: +Clamp tool (C shortcut), mid-segment drag-to-snap (snap
radius ~16 px / zoom), clamp inspector, right-click remove-from-cable.
- 11.5 Existing bundles table: keep, repurpose. Implicit bundles are
derived from shared clamp segments; explicit named bundles still live
in the table.
- 11.6 Solver coupling: v0 solver still emits straight cables; m
hand-routes after. v5.1 future work for solver-suggested clamps.
- 11.7 Export: clamps export as small grey diamonds; cable arrows use
Excalidraw's points array for mid-vertices. Bundle stripes are
viewer-only (Excalidraw can't represent them losslessly).
- 11.8 API additions: clamp CRUD, attach/detach/reorder cable clamps.
Snapshot grows clamps + cable_clamps arrays.
- 11.9 Five open questions for m (icon shape, snap radius scaling,
cascade-on-delete confirm, stripe order, solver respect for manual
clamp routing).
- 11.10 6-step slice plan post-approval.
DESIGN v5 READY FOR REVIEW
m's actual hardware: IOx-3/6/8 are power strips, not USB hubs. v4 seeded
them as Power × 1 + USB × N which doesn't match reality. Multi-plug 3-6
and Wifi-plug from v5 lumped every Power port on the same bottom edge
without distinguishing input from outputs.
Migration 006 wipes and re-seeds the port profile for all 8
power-distribution types with the canonical 2-row layout:
Power In × 1 on top (back, sort_order 0)
Power Out × N on bottom (front, sort_order 1)
N for each:
IOx-3 / Multi-plug 3 → 3
IOx-6 / Multi-plug 6 → 6
IOx-8 → 8
Multi-plug 4 → 4
Multi-plug 5 → 5
Wifi-plug → 1 (pass-through outlet)
Existing device instances keep their already-seeded ports per design
§2.3 (ports are instance-owned). m needs to delete + recreate any
IOx-* / Multi-plug-* / Wifi-plug instances to pick up the new layout.
Tests:
- TestSeed_PortProfiles: comments updated; totals unchanged (Power In 1
+ Power Out N matches old Power 1 + USB N / Power N).
- TestSeed_PowerHubs (was TestSeed_PowerCatalog, rewritten): table-drives
all 8 affected types. Asserts exactly 2 port rows — top/Power In/1 and
bottom/Power Out/N — plus kind/icon for the v5 catalog entries.
Design §2.2 catalog table refreshed to match.
Tight pass on m's review of v4 (single commit per head's instruction).
Six locked answers integrated:
1. mCables is a schematic, not a physical-routing tool. Stripped
'trunk', 'frame-edge corridor', 'cable tray', 'path optimisation'
from §5b.1, §5b.2, §7, §8, §9. Bundling reduces to the v3 endpoint-
pair rule: ≥2 cables between the same A↔B endpoint pair → group as
one bundle. Anything path-shaped is "out of scope, period" (§8).
2. Solver button-only for v0 (no change). Live-solve parked at 9+.
3. Unmet-requirement quick-fix: red badge on the affected device in the
inspector with a single "+ Add <type> port to <device> and re-solve"
button per §5b.4. New endpoint
POST /api/projects/:pid/devices/:id/ports-and-resolve chains the
port insert + the solve re-run in one transaction.
4. Setup templates fold INTO v4.1. New §2.4 with the schema for
setup_templates + setup_template_devices + setup_template_requirements
(migration 004), 3 built-in templates seeded (Living Room, Home
Office, Server Rack). New API: GET /api/setup-templates,
POST /api/projects/:pid/apply-template. New UI flow: "or start from
a template" section in the New Project modal + an "Apply template"
action on empty projects. Built-in catalog grows to 14 types
(adds Screen, Keyboard, Mouse).
5. Catalog SQL seed in migration 002 (no change).
6. Promote-to-manual: explicit button on cable inspector (no change).
§8 slice 6 absorbs the templates work alongside the solver MVP.
§9 closes all six v4 questions; no open design questions remain.
Trailer changes to "DESIGN v4.1 READY FOR REVIEW".
CLAUDE.md mirrors: schematic-only framing, 14-type catalog, setup
templates as a first-class feature, quick-fix UX note.
Big rescope driven by m's product-vision clarification: mCables is a
cable-management framework with a solver as its core value prop, not a
manual draw-and-click editor. m declares devices + required connections
between them; the solver emits the cable plan + bundle recommendations,
optimising for maximum bundling.
Schema additions (migrations 002 + 003):
- device_types (catalog) — built-ins (project_id NULL) + project-custom
(project_id non-null). 11 built-in types seeded with default port
profiles (NAS, PC, Mac, TV, Soundbar, Switch, fritz, ChromeCast,
SteamLink, IOx-3/6/8, Notebook).
- device_type_ports (profile rows: cable_type × count × edge).
- devices.type_id (nullable). Picking a type seeds ports once;
instance-owned thereafter (no retroactive re-seed).
- connection_requirements (per-project, from/to device + preferred type
+ must_connect flag, with order-normalised pair_lo/pair_hi for
duplicate prevention).
- cables.auto (slice 5.5 migration) — distinguishes solver-owned cables
from user-drawn ones.
API additions:
- GET /api/device-types (built-ins only, read-only) and
GET /api/projects/:pid/device-types (built-ins + project-custom merged)
- POST/PATCH/DELETE under /api/projects/:pid/device-types (project-custom
only; built-ins are 403)
- /api/projects/:pid/connection-requirements full CRUD
- POST /api/projects/:pid/solve with ?preview=1 — pure-function solver
(greedy port allocation, endpoint-pair bundling for v0); returns
add[], remove[], bundles_added[], unsatisfied[], warnings[]
Solver algorithm (§5b):
- Read project devices + ports + connection_requirements + manual cables
- Assign each requirement a (port_a, port_b) using the preferred cable
type (or auto-pick if exactly one type matches both ends)
- Bundle by endpoint-pair (v3 rule, applied to auto cables only)
- Surface unsatisfied requirements per class (no compat type / ambiguous
type / no free port) — does NOT auto-add ports; UI quick-fix instead
- ?preview=1 returns the diff without writing; default applies in a tx
UI additions:
- Device-create modal: type dropdown (built-ins grouped by kind, then
project-custom, then "Custom (no type)" for the v3 freeform fallback)
- Left-sidebar Requirements section with + Requirement button
- Header Solve button (S keybinding) → preview modal → Apply
- Inspector for selected device: type, ports grid, unmet requirements
with red badges + quick-fix actions
- Inspector for selected auto cable: driving requirement, parent bundle,
Promote-to-manual button
Slice reshape (§8):
- Slices 1, 2 shipped. v4 inserts: 4 = catalog + type-aware device create,
4.5 = catalog management, 5 = requirements CRUD + UI, 6 = solver MVP +
Solve button. Old "manual port + manual cable draw" slides to slice 7
as a tweak path on solver output. Export becomes slice 8.
Six new open questions (§9) for m to gate before slice 4:
1. Path source (auto-route through frame edges / user cable-trays /
Steiner-tree)?
2. Live-solve vs. button-only?
3. UX when solver has no compatible port pair?
4. Setup templates in v4 or post-MVP?
5. Catalog as code seed or JSON file?
6. Auto-promote vs. explicit Promote-to-manual on solver cable edits?
CLAUDE.md updated to reflect the solver-core framing, hybrid catalog,
connection-requirements model, and auto/manual cable distinction.
Trailer changes to "DESIGN v4 READY FOR REVIEW".
Tight pass on round-4 answers (single commit per head's request):
- cable_types is GLOBAL — drop project_id, UNIQUE(name). Migration 001
seeds the 5 defaults once; POST /api/projects no longer seeds them.
API moves to top-level /api/cable-types. Renaming/recolouring affects
every project. CASCADE from projects does not touch cable_types.
- devices: UNIQUE (project_id, name) added.
- projects: drawing_name defaults to "<name>.excalidraw" server-side
on POST when omitted; editable via PATCH.
- DELETE /api/projects/:pid requires ?confirm=<name>; server checks
name match, returns 400 if missing or mismatched.
- io_markers: no type_id (Power-by-convention, UI soft-warn). Confirmed
v0 stance.
- Bundles ignored on export — carries over from v2.
- §0 changelog rewritten as "what changed in v3 / what carried over".
- §2 schema rewritten; FK-shape paragraph updated to call out the one
global table.
- §3 endpoints: cable-types moved to top level; POST/DELETE projects
show new defaults + guardrail semantics.
- §4 export table notes cable_types pulled from global.
- §7 "edit cable type" flow gains the cross-project-effect banner +
ON DELETE RESTRICT inline-error UX.
- §8 slice 1 rewritten: no per-project seeding; legend reads global.
- §9 all six v2 questions marked resolved with the v3 answer per item.
- Trailer changes to "DESIGN v3 READY — coder shift gated".
- CLAUDE.md mirrors: global cable_types, device UNIQUE per project,
drawing_name default, delete guardrail.
Revision after m's answers (2026-05-15):
- mCables is a framework. Top-level `projects` table; LOFT and OFFICE
are separate projects, each backed by one drawing. project_id is
denormalised onto every row for cheap project-scoped queries; CASCADE
from projects wipes a project's whole subgraph.
- IO diamonds are wall-outlet terminators (type=Power), not inter-frame
bridges. paired_with_id removed.
- No runtime importer. The seed Cable-Management.excalidraw is the
visual-grammar reference for the exporter only. /api/sync/import is
dropped from MVP; only /api/sync/export remains (one-way, manual).
- No cable inventory fields. Strictly visual structure for v0.
- DB at ./data/mcables.db (project-local, gitignored).
- Deploy: raw docker on mDock under /home/m/stacks/mcables/ (NOT Dokploy).
Conventions verified live (mgreen, mgeo, msports-garmin patterns).
Port 7777, container_name mcables, image from Gitea registry, Gitea
Actions self-hosted runner builds + deploys on push to main.
- Bind 0.0.0.0:7777 on the LAN. No auth.
- UI gains a projects picker; all CRUD endpoints scoped under
/api/projects/:pid/.
- Slices re-planned: empty bootstrap → frame+device → port+cable →
IO+cable-type editing → export.
- Open questions trimmed; six new ones (drawing-name policy, device
uniqueness, non-Power IO, bundle export, cross-project cable types,
delete guardrail).
Ends with DESIGN v2 READY FOR REVIEW.
Inventor shift 1. Reads the live Cable-Management.excalidraw on
mxdrw.msbls.de, lands on:
- vanilla JS modules + SVG diagram (no build step) served by a single
Go binary with embed.FS
- SQLite schema with frames, devices, ports, cable_types, cables,
io_markers, bundles (cgo-free driver)
- HTTP API under /api/, /api/state as the editor's one-shot loader
- importer that respects port-on-edge geometry (ports are positional,
not containerId-bound) and resolves arrow endpoints to port/device/IO
- bundle detection MVP: same-endpoints-pair → suggestion
- sync model: DB authoritative, Excalidraw is a projection, manual
import/export buttons with element-ID stability and conflict flagging
- five vertical slices for the coder shift, smallest end-to-end first
- nine open questions for m before code starts
Ends with DESIGN READY FOR REVIEW.