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.
149 lines
6.9 KiB
Markdown
149 lines
6.9 KiB
Markdown
# 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 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.
|