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".
8.7 KiB
mCables — Project Instructions
Project Overview
Cable-management framework + solver for m's setup. m declares his devices and the connection requirements between them ("NAS must connect to Switch via RJ45"). mCables runs a solver that emits the cable plan + bundle recommendations, optimising for maximum bundling — prefer fewer trunks even at higher total length. The visual editor is still there for tweaking the plan, but the solver is the headline.
Each cable-managed environment (LOFT, OFFICE, …) is a separate
mCables project, and each project is backed by exactly one Excalidraw
drawing. The framework provides a visual web interface backed by a Go
HTTP API and SQLite, plus an export pipeline that writes .excalidraw
files via mExDraw.
Memory group_id: mcables
No CLI. Frontend-first — every interaction is through the visual
interface. The backend serves the UI and the API; there is no
mcables shell binary intended for humans.
Goal
- A reusable framework for tracking devices, ports, cables, cable types, bundles, frames — scoped per project (LOFT and OFFICE are separate projects, each a separate drawing).
- A solver that, given the project's devices + connection requirements, emits the cable plan + bundle recommendations. Objective: maximum bundling (fewer trunks, even at higher total length).
- A hybrid device-type catalog: built-in types (NAS, PC, Mac, TV, Switch, fritz, ChromeCast, SteamLink, Notebook, Soundbar, IOx-3/6/8) with default port profiles, extensible per project. Picking a type on device-create seeds the device's ports automatically; m overrides per instance.
- A visual editor for switching projects, adding frames/devices, declaring requirements, running the solver, and tweaking the resulting plan.
- A one-way export from the DB to the corresponding
.excalidrawdrawing onmxdrw.msbls.dewhenever m clicks Export — DB is authoritative, Excalidraw is the projection.
Architecture
| Layer | Tech | Notes |
|---|---|---|
| DB | SQLite | ./data/mcables.db (project-local, gitignored). Driver: modernc.org/sqlite (cgo-free). |
| Backend | Go | net/http HTTP API + static frontend via embed.FS. Standard library + minimal deps. Single binary. |
| Frontend | Vanilla JS modules + SVG, no build step | TypeScript types via JSDoc, optional tsc --noEmit in CI. Preact-via-CDN-ESM is the documented fallback if vanilla state gets painful — no build step either way. |
| Diagram I/O | mExDraw HTTP API | PUT https://mxdrw.msbls.de/api/drawings/<name>.excalidraw with Authorization: Bearer $MEXDRAW_TOKEN. (The mcp__mexdraw__* MCP tools are not currently configured for this project — workers use the raw HTTP API.) |
Hierarchy
- Project (
projectstable) is the top-level concept. LOFT, OFFICE, HOMELAB, … are separate projects. One project ↔ one.excalidrawdrawing in mExDraw.projects.drawing_namedefaults to<name>.excalidrawserver-side when omitted on create; editable later. - Frames sub-divide a project (LOFT has
desk,rack,media; OFFICE hasdesk,server). Frames are not projects — they're zones within one drawing. - Every device, port, cable, IO marker, bundle, and connection
requirement is project-scoped (
project_iddenormalised onto every row, withON DELETE CASCADEfromprojects).UNIQUE (project_id, devices.name)— no two devices in one project share a name. - Cable types are global. A single shared
cable_typestable — noproject_id. The five defaults (Power/USB/HDMI/DP/RJ45) are seeded by migration 001 once, not per project. Renaming or recolouring a type affects every project's legend immediately. - Device types are hybrid.
device_typesis one global table withproject_idNULL for the 11 built-in catalog rows (seeded by migration 002) andproject_id = currentfor project-custom types. Eachdevice_typecarries adevice_type_portsprofile that seedsportsrows when a device of that type is created. m can extend the catalog per project; built-ins are read-only from the API. - Connection requirements (
connection_requirementstable) are the solver's input. m declares "from_device ↔ to_device, preferred cable type, must_connect"; the solver assigns ports and emits cables. - Project deletion guardrail.
DELETE /api/projects/:pidrequires?confirm=<name>matching the project's current name. 400 otherwise. - Solver-owned vs. user-owned cables.
cables.auto = 1= created by the solver and replaceable on re-solve.auto = 0= hand-drawn by m, left alone by the solver. PATCHing endpoint or type of an auto cable promotes it to manual (explicit "Promote to manual" button in the inspector, per design v4 §5b.3).
Branch Strategy
main= production-deployable.mai/<worker>/<slug>= worker branches via the mai workflow.- No
devbranch — too small a project for staging. - Merge with
--no-ffto main, delete branches after merge.
Tech Stack
- Go for the backend (matches m's other tools:
m,mai, youpcms, mExDraw). - SQLite via
modernc.org/sqlite(cgo-free → cleanscratch/distroless container, no toolchain pain). - mExDraw via HTTP for diagram export. Never edit raw
.excalidrawfiles directly outside the mExDraw API. - Vanilla JS + SVG for the frontend — no build step. JSDoc-typed.
Deployment — mDock, raw docker (NOT Dokploy)
mCables runs on mDock (192.168.178.131 on the LAN, Tailscale mdock)
as a plain docker-compose service. Dokploy is for public mlake/mRiver
stuff; mDock uses raw docker compose per the conventions of the existing
mDock services (mgreen, mgeo, msports-garmin, paperless, …).
- Repo layout on mDock:
/home/m/stacks/mcables/withdocker-compose.yml,data/bind-mount, secrets in/home/m/secrets/mcables/.env. - Image:
mgit.msbls.de/m/mcables:latest(built and pushed by a Gitea Actions workflow on push tomain, runs on the self-hosted runner on mDock with labelself-hosted:host). - Port mapping:
7777:7777, exposed on the LAN — no reverse proxy. - Restart policy:
unless-stopped. - LAN URL:
http://mdock:7777. - No auth — LAN-trusted.
Local dev (no Docker): go run ./cmd/mcables against ./data/mcables.db.
Seed drawing — visual grammar reference, not a runtime importer
mxdrw.msbls.de/draw/Cable-Management.excalidraw is reference material
only. mCables does not auto-ingest it. m will rebuild LOFT and OFFICE
from scratch inside the tool — the seed exists so the exporter mimics
its visual grammar:
- Devices = rectangles with a text label.
- Ports = small ellipses (~12×9) positioned on a device edge. Positional, not containerId-bound. Stroke colour = cable type.
- Cables = arrows with
startBinding/endBindingto ports or devices or IO diamonds. - Cable types = colour, with a legend at the top-left of the project's first frame listing the project's cable_types.
- IO markers = small diamonds. Semantically wall outlets / power entry points — a cable terminating at an IO marker means "this end is plugged into a wall socket outside the diagram". They are not inter-frame bridges and they do not pair up.
- Frames = sub-zones inside a project (
desk,rack,media, …). - Lines = decorative only (legend separators in the seed). Ignored on export.
Legend colours (global, seeded once by migration 001):
| Type | Hex |
|---|---|
| Power | #e03131 |
| USB | #2f9e44 |
| HDMI | #1971c2 |
| DP | #9c36b5 |
| RJ45 | #ffd500 |
Out of scope (v0)
- Multi-user. mCables is m-only.
- Auth / sharing — LAN-trusted on mDock.
- Mobile / responsive — desktop browser only.
- Cable inventory beyond visual structure (no length, no purchase history, no SKU). Strictly visual structure for v0.
- Import from
.excalidrawat runtime. If a one-shot migration is ever needed, a separatemcables-migrateCLI tool is the right shape, not a hot API endpoint.
Worker Preferences
- Inventor shifts (design passes): conventions, schema, API, export
pipeline, mDock deploy plan, UI flows, slices. Output:
docs/design.md- open questions for m. v1–v4 are versioned in the doc's header callout.
- Coder shifts (after m's go on a design version): build to the
current design.md. Current state: slice 1 (project CRUD + global
cable_types) and slice 2 (frames + devices + drag) are merged; design
v4 reshapes slices 3+ (IO + cable-type editing → device-type catalog →
device-type manage → connection-requirements UI → solver → manual
port/cable draw → export). See
docs/design.md§8 for the current sequence. - Use Sonnet for both — structure matters more than depth.