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.
6.9 KiB
mCables — Project Instructions
Project Overview
Cable-management framework for m's setup. 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 visual editor in the browser: switch projects, add frames/devices/ports, click ports to wire up cables, pick cable types from a per-project legend.
- A one-way export from the DB to the corresponding
.excalidrawdrawing onmxdrw.msbls.dewhenever m clicks Export — DB is authoritative, Excalidraw is the projection. - Bundle detection: parallel cables along the same path within a project get grouped + colour-bundled in the diagram.
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, and bundle 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. - Project deletion guardrail.
DELETE /api/projects/:pidrequires?confirm=<name>matching the project's current name. 400 otherwise.
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
- First shift = inventor (design pass): conventions, schema, API,
export pipeline, mDock deploy plan, UI flows, slices. Output:
docs/design.md+ open questions for m. - Second shift = coder (after m's go on the design): bootstrap repo skeleton (Go module, SQLite migrations, server, exporter, frontend scaffold). Take slices 1–4 first (project CRUD, frames/devices, ports and cables, IO + cable-type editing); slice 5 (Excalidraw export) closes the round-trip.
- Use Sonnet for both — greenfield, structure matters more than depth.