Files
CableGUI/CLAUDE.md
mAi c690113ea1 docs: design v3 — global cable_types, device UNIQUE, delete guardrail
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.
2026-05-15 16:32:20 +02:00

6.9 KiB
Raw Blame History

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 .excalidraw drawing on mxdrw.msbls.de whenever 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 (projects table) is the top-level concept. LOFT, OFFICE, HOMELAB, … are separate projects. One project ↔ one .excalidraw drawing in mExDraw. projects.drawing_name defaults to <name>.excalidraw server-side when omitted on create; editable later.
  • Frames sub-divide a project (LOFT has desk, rack, media; OFFICE has desk, server). Frames are not projects — they're zones within one drawing.
  • Every device, port, cable, IO marker, and bundle is project-scoped (project_id denormalised onto every row, with ON DELETE CASCADE from projects). UNIQUE (project_id, devices.name) — no two devices in one project share a name.
  • Cable types are global. A single shared cable_types table — no project_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/:pid requires ?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 dev branch — too small a project for staging.
  • Merge with --no-ff to 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 → clean scratch/distroless container, no toolchain pain).
  • mExDraw via HTTP for diagram export. Never edit raw .excalidraw files 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/ with docker-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 to main, runs on the self-hosted runner on mDock with label self-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 / endBinding to 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 .excalidraw at runtime. If a one-shot migration is ever needed, a separate mcables-migrate CLI 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 14 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.