docs: design v4.1 — schematic-only bundling, setup templates folded in
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.
This commit is contained in:
39
CLAUDE.md
39
CLAUDE.md
@@ -5,9 +5,12 @@
|
||||
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.
|
||||
plan + bundle recommendations. mCables is a **schematic**, not a
|
||||
physical-routing tool — cables are straight lines between endpoints; the
|
||||
"maximum bundling" objective is satisfied by the endpoint-pair rule
|
||||
(when two or more cables share the same A↔B endpoint pair, group them
|
||||
into one bundle). 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
|
||||
@@ -27,18 +30,24 @@ interface. The backend serves the UI and the API; there is no
|
||||
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 `.excalidraw` drawing
|
||||
on `mxdrw.msbls.de` whenever m clicks Export — DB is authoritative,
|
||||
Excalidraw is the projection.
|
||||
requirements, emits the cable plan + bundle recommendations.
|
||||
Objective: maximum bundling via the endpoint-pair rule (schematic
|
||||
only — no path/trunk/cable-tray modelling).
|
||||
- A **hybrid device-type catalog**: 14 built-in types (NAS, PC, Mac,
|
||||
Notebook, TV, Soundbar, Switch, fritz, ChromeCast, SteamLink,
|
||||
IOx-3/6/8, Screen, Keyboard, Mouse) with default port profiles,
|
||||
extensible per project. Picking a type on device-create seeds the
|
||||
device's ports automatically; m overrides per instance.
|
||||
- **Setup templates** for bootstrapping a project from blank to
|
||||
solver-ready: built-ins 'Living Room', 'Home Office', 'Server Rack'
|
||||
stamp their device-types + connection requirements in one transaction.
|
||||
- A visual editor for switching projects, adding frames/devices,
|
||||
declaring requirements, running the solver, and tweaking the
|
||||
resulting plan. Unmet requirements get a one-click quick-fix
|
||||
("+ Add <type> port to <device> and re-solve").
|
||||
- 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.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
374
docs/design.md
374
docs/design.md
@@ -1,8 +1,35 @@
|
||||
# mCables — Design v4
|
||||
# mCables — Design v4.1
|
||||
|
||||
Cable-management **framework + solver** for m's setup. Inventor shift 1
|
||||
design, revised through v2 (rescope to multi-project framework), v3
|
||||
(global cable_types + guardrails), and now **v4 — solver-as-core**.
|
||||
(global cable_types + guardrails), v4 (solver-as-core), and now
|
||||
**v4.1 — six locked answers from m's v4 review**.
|
||||
|
||||
> **What changed in v4.1** (tight pass on v4)
|
||||
> 1. **mCables is a schematic, not a physical-routing tool.** Cables are
|
||||
> straight lines between endpoints; the solver and the renderer do not
|
||||
> care about paths, trunks, frame edges, or cable-tray polylines.
|
||||
> "Maximum bundling" reduces to the v3 rule: **≥2 cables between the
|
||||
> same endpoint pair → bundle them.** All path-routing language has
|
||||
> been stripped from §5b.1, §5b.2, §7, §8, §9.
|
||||
> 2. **Solver fires on the Solve button (v0).** Live-solve stays in §8
|
||||
> slices 9+ as an opt-in toggle.
|
||||
> 3. **Unmet-requirement quick-fix**: when the solver returns
|
||||
> `unsatisfied[]`, the device inspector renders a red badge per unmet
|
||||
> requirement with a single button — **"+ Add <type> port to
|
||||
> <device> and re-solve"** — that POSTs a new port to the
|
||||
> device AND immediately re-runs `POST /api/projects/:pid/solve` in
|
||||
> the same UI action. See §5b.4 + §7 inspector-states.
|
||||
> 4. **Setup templates fold INTO v4.1.** New tables `setup_templates`,
|
||||
> `setup_template_devices`, `setup_template_requirements` in
|
||||
> migration 004 + 3 built-in templates ('Living Room', 'Home Office',
|
||||
> 'Server Rack'). New endpoints `GET /api/setup-templates` and
|
||||
> `POST /api/projects/:pid/apply-template`. UI: a "Templates" panel
|
||||
> in the New Project flow + an "Apply template" action on an empty
|
||||
> project. See new §2.4 + slice 6 fold-in below.
|
||||
> 5. **Catalog distribution: SQL seed** in migration 002 (no change).
|
||||
> 6. **Promote to manual: explicit button** on the cable inspector
|
||||
> (no change).
|
||||
|
||||
Sources: the live `Cable-Management.excalidraw` on mxdrw.msbls.de (used as
|
||||
the *visual-grammar reference*, not a bootstrap import target),
|
||||
@@ -31,10 +58,11 @@ to inspect + tweak the plan) but is no longer the primary surface.
|
||||
> their ports + connection_requirements + frame positions, emits a diff
|
||||
> of `cables` + `bundles`. Two modes: `?preview=1` returns the diff
|
||||
> without applying; default applies.
|
||||
> - **Solver objective: maximum bundling** (§5). Prefer routes that
|
||||
> consolidate cables into trunks even at higher total length. Visually
|
||||
> cleaner setups, easier mental model. v0 uses the v3 same-endpoints
|
||||
> bundle rule; path-based bundling is slice 6+.
|
||||
> - **Solver objective: maximum bundling** (§5b.1). Schematic only: when
|
||||
> two or more cables share the same endpoint pair, group them into one
|
||||
> bundle. No path or trunk geometry — mCables is a wiring schematic,
|
||||
> not a routing tool. v4.1 strips all path/trunk language from the v4
|
||||
> draft.
|
||||
> - **UI: device-type dropdown** on device-create, **Connection
|
||||
> Requirements** left panel, **Solve** button next to Export. Inspector
|
||||
> shows type + ports + unmet requirements (selected device) or the
|
||||
@@ -390,20 +418,28 @@ CREATE INDEX conn_reqs_to_idx ON connection_requirements(to_device_id);
|
||||
|
||||
- **001_init.sql** (v3) — projects, frames, devices (no type_id), ports,
|
||||
cable_types (5 seeded), io_markers, cables, bundles, bundle_cables.
|
||||
- **002_device_catalog.sql** (v4 NEW) — `device_types` +
|
||||
- **002_device_catalog.sql** (v4) — `device_types` +
|
||||
`device_type_ports`. Seeds the built-in catalog (§2.2). Adds
|
||||
`devices.type_id` (`ALTER TABLE devices ADD COLUMN type_id INTEGER
|
||||
REFERENCES device_types(id) ON DELETE SET NULL`) and the matching
|
||||
index.
|
||||
- **003_connection_requirements.sql** (v4 NEW) — `connection_requirements`.
|
||||
- **003_connection_requirements.sql** (v4) — `connection_requirements`.
|
||||
Also adds `cables.auto` (`ALTER TABLE cables ADD COLUMN auto INTEGER
|
||||
NOT NULL DEFAULT 0`) so the solver can distinguish its rows from
|
||||
m's hand-drawn ones (§5b.3).
|
||||
- **004_setup_templates.sql** (v4.1 NEW) — `setup_templates` +
|
||||
`setup_template_devices` + `setup_template_requirements`. Seeds 3
|
||||
built-in templates ('Living Room', 'Home Office', 'Server Rack').
|
||||
|
||||
Slice 1 already shipped 001. Slices 1.5 (catalog) and 1.6 (requirements)
|
||||
land 002 and 003.
|
||||
Slices 1 and 2 already shipped 001. Slice 4 lands 002; slice 5 lands
|
||||
003; slice 6 lands 004 alongside the solver MVP + templates UI.
|
||||
|
||||
### 2.2 Built-in catalog seed (002 INSERTs)
|
||||
|
||||
The 11 built-in types m's setup uses today, with their default port
|
||||
profiles. Stored as `(project_id NULL, built_in 1)`:
|
||||
The 14 built-in types m's setup uses today, with their default port
|
||||
profiles. Stored as `(project_id NULL, built_in 1)`. v4.1 added the
|
||||
three peripheral types (Screen, Keyboard, Mouse) to support the Home
|
||||
Office setup template:
|
||||
|
||||
| `device_types.name` | `kind` | Default ports (cable_type × count) |
|
||||
|---|---|---|
|
||||
@@ -420,6 +456,9 @@ profiles. Stored as `(project_id NULL, built_in 1)`:
|
||||
| IOx-3 | hub | Power × 1; (3× port slots — concrete cable type per slot is set at instantiation; defaults to USB × 3 for v0) |
|
||||
| IOx-6 | hub | Power × 1; USB × 6 |
|
||||
| IOx-8 | hub | Power × 1; USB × 8 |
|
||||
| **Screen** | display | Power × 1; HDMI × 1 |
|
||||
| **Keyboard** | accessory | USB × 1 |
|
||||
| **Mouse** | accessory | USB × 1 |
|
||||
|
||||
"Hub" devices like IOx-* have ambiguous port profiles (the seed drawing
|
||||
shows them in red because most carry Power, but they also hub USB). v0
|
||||
@@ -443,6 +482,101 @@ Trade-off acknowledged: m may want a "re-seed from type" action later
|
||||
(slice 5+) to wipe + reset a device's ports. Out of v0 scope; not
|
||||
blocked by the schema.
|
||||
|
||||
### 2.4 Setup templates (v4.1 NEW)
|
||||
|
||||
A setup template is a named recipe of "device-types to add + connection
|
||||
requirements between them" that bootstraps a project from blank to
|
||||
solver-ready in one click. m's three archetypes:
|
||||
|
||||
| Template name | Devices | Default requirements |
|
||||
|---|---|---|
|
||||
| **Living Room** | TV, Soundbar, ChromeCast | TV ↔ Soundbar (HDMI, must); TV ↔ ChromeCast (HDMI, must) |
|
||||
| **Home Office** | PC, Screen, Keyboard, Mouse | PC ↔ Screen (HDMI, must); PC ↔ Keyboard (USB, must); PC ↔ Mouse (USB, must) |
|
||||
| **Server Rack** | NAS, Switch, fritz | NAS ↔ Switch (RJ45, must); Switch ↔ fritz (RJ45, must); fritz ↔ NAS (Power, nice) |
|
||||
|
||||
> "Screen", "Keyboard", "Mouse" are added to the v4 built-in catalog
|
||||
> alongside the existing 11 (Screen: Power × 1 + HDMI × 1; Keyboard: USB × 1;
|
||||
> Mouse: USB × 1). Migration 002 grows to seed 14 built-ins.
|
||||
|
||||
Schema (`004_setup_templates.sql`):
|
||||
|
||||
```sql
|
||||
-- A named recipe: a list of device types + requirements between them.
|
||||
CREATE TABLE setup_templates (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
built_in INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- The devices a template stamps into a project. suggested_name is
|
||||
-- pre-filled into the apply-template form; m can override.
|
||||
CREATE TABLE setup_template_devices (
|
||||
id INTEGER PRIMARY KEY,
|
||||
template_id INTEGER NOT NULL REFERENCES setup_templates(id) ON DELETE CASCADE,
|
||||
device_type_id INTEGER NOT NULL REFERENCES device_types(id) ON DELETE RESTRICT,
|
||||
suggested_name TEXT, -- "TV", "Bedroom TV", "Mac (work)"
|
||||
sort_order INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX setup_template_devices_template_idx ON setup_template_devices(template_id);
|
||||
|
||||
-- Requirements between devices in the template, addressed by
|
||||
-- `setup_template_devices.id` (not the runtime device id — they're
|
||||
-- resolved at apply time).
|
||||
CREATE TABLE setup_template_requirements (
|
||||
id INTEGER PRIMARY KEY,
|
||||
template_id INTEGER NOT NULL REFERENCES setup_templates(id) ON DELETE CASCADE,
|
||||
from_template_device_id INTEGER NOT NULL REFERENCES setup_template_devices(id) ON DELETE CASCADE,
|
||||
to_template_device_id INTEGER NOT NULL REFERENCES setup_template_devices(id) ON DELETE CASCADE,
|
||||
preferred_cable_type_id INTEGER REFERENCES cable_types(id) ON DELETE SET NULL,
|
||||
must_connect INTEGER NOT NULL DEFAULT 1 CHECK (must_connect IN (0, 1)),
|
||||
CHECK (from_template_device_id != to_template_device_id)
|
||||
);
|
||||
CREATE INDEX setup_template_reqs_template_idx ON setup_template_requirements(template_id);
|
||||
```
|
||||
|
||||
API:
|
||||
|
||||
```
|
||||
GET /api/setup-templates → [SetupTemplate {id, name, description, built_in,
|
||||
devices: [{id, device_type_id,
|
||||
device_type: {…},
|
||||
suggested_name, sort_order}],
|
||||
requirements: [{id, from_template_device_id,
|
||||
to_template_device_id,
|
||||
preferred_cable_type_id,
|
||||
must_connect}]}, …]
|
||||
Read-only; built-ins are not editable via API in v4.1.
|
||||
|
||||
POST /api/projects/:pid/apply-template ← {
|
||||
template_id: <int>,
|
||||
name_overrides: { <template_device_id>: "<name>", … },
|
||||
skip_devices: [<template_device_id>, …] # optional
|
||||
}
|
||||
→ {
|
||||
devices_added: [Device, …],
|
||||
requirements_added: [ConnectionRequirement, …],
|
||||
skipped_devices: [{template_device_id, reason}, …]
|
||||
}
|
||||
Idempotency:
|
||||
- A name collision with an existing device in the
|
||||
project skips that template device (reason = "name
|
||||
already in use"). Caller can pass `name_overrides`
|
||||
to resolve.
|
||||
- Requirements whose endpoints both resolve fire;
|
||||
any whose endpoint was skipped are themselves
|
||||
skipped (logged in `requirements_skipped[]` — same
|
||||
shape).
|
||||
The whole call runs in a single transaction.
|
||||
```
|
||||
|
||||
The seed migration creates the 3 built-ins + their template_devices and
|
||||
template_requirements rows referencing the 14 built-in `device_types` and
|
||||
the 5 built-in `cable_types`. No project_id anywhere — templates are
|
||||
global.
|
||||
|
||||
**FK shape — why `project_id` on every project-scoped row, not just transitively:**
|
||||
|
||||
The structural truth is `cable → port → device → frame → project`. But
|
||||
@@ -578,6 +712,33 @@ POST /api/projects/:pid/solve ← {} (or {?preview=1} to c
|
||||
cables (auto=0 in the cables table; see §5.1) are
|
||||
never touched — the solver only adds/removes its own.
|
||||
|
||||
# v4 — Solver quick-fix combo endpoint (powers the inspector's
|
||||
# "+ Add <type> port to <device> and re-solve" button — §5b.4).
|
||||
POST /api/projects/:pid/devices/:id/ports-and-resolve
|
||||
← {type_id: <int>,
|
||||
label?: <str>,
|
||||
x_offset?: <num>, y_offset?: <num>}
|
||||
→ {port: Port, solve: <solve response>}
|
||||
Single tx: inserts the port + re-runs solve. Used by
|
||||
the quick-fix UI so the unmet badge resolves in one
|
||||
server round-trip.
|
||||
|
||||
# v4.1 — Setup templates
|
||||
GET /api/setup-templates → [SetupTemplate, …]
|
||||
Read-only listing of built-in (and any project-custom,
|
||||
post-v4.1) templates with their device/requirement
|
||||
shapes (see §2.4).
|
||||
POST /api/projects/:pid/apply-template ← {template_id: <int>,
|
||||
name_overrides?: { <template_device_id>: "<name>" },
|
||||
skip_devices?: [<template_device_id>, …]}
|
||||
→ {devices_added: [Device, …],
|
||||
requirements_added: [ConnectionRequirement, …],
|
||||
skipped_devices: [{template_device_id, reason}, …],
|
||||
requirements_skipped: [{template_requirement_id, reason}, …]}
|
||||
Idempotent in spirit: name collisions surface in
|
||||
skipped_devices; m resolves with name_overrides on
|
||||
re-apply. Whole call is one transaction.
|
||||
|
||||
# Sync — export only in MVP
|
||||
POST /api/projects/:pid/sync/export → writes the project's drawing to mExDraw
|
||||
(overwrites previous version; mExDraw keeps
|
||||
@@ -695,21 +856,27 @@ The solver reads a project's `devices` (with their `ports`) and
|
||||
(rows with `auto=1`) + `bundles`. m's hand-drawn cables (`auto=0`) are
|
||||
left strictly alone — the solver only adds and removes its own.
|
||||
|
||||
### 5b.1 Objective: maximum bundling
|
||||
### 5b.1 Objective: maximum bundling — schematic only
|
||||
|
||||
Locked in by m. Prefer routes that consolidate cables into shared trunks
|
||||
even at higher total length. Visually cleaner setups; easier to manage
|
||||
physically (one cable bundle along the floor, not five strands).
|
||||
mCables is a **schematic**, not a physical-routing tool. Cables are
|
||||
straight lines between endpoints; the solver has no model of walls,
|
||||
floors, cable trays, or path geometry. "Maximum bundling" therefore
|
||||
reduces to a single rule on the schematic:
|
||||
|
||||
Concretely: when assigning a cable to a path, the solver minimises the
|
||||
**count of distinct trunks**, breaking ties by total length. v0
|
||||
approximates a "trunk" with the pair of device endpoints (the v3 rule);
|
||||
slice 6+ adds path-based trunks via frame-edge corridors.
|
||||
> When two or more cables share the same endpoint pair (device A ↔
|
||||
> device B), group them into one bundle.
|
||||
|
||||
This is the v3 endpoint-pair rule, applied to the solver's output. m's
|
||||
"visually cleaner setups" benefit comes from the bundle being a single
|
||||
labelled set in the inspector + a single mixed-colour glyph in the
|
||||
render (slice 9+), rather than from any path optimisation. Anything
|
||||
about trunks, frame-edge corridors, or auto-routing is out of scope —
|
||||
filed for "post-v0 ambient" in §8.
|
||||
|
||||
### 5b.2 Algorithm (v0)
|
||||
|
||||
Pure function. No graph search; no LP. Single pass with greedy port
|
||||
allocation.
|
||||
Pure function. No graph search; no LP; no path optimisation. Single
|
||||
pass with greedy port allocation.
|
||||
|
||||
```
|
||||
solve(project) ⇒ {add, remove, bundles, unsatisfied}:
|
||||
@@ -767,30 +934,45 @@ survives every solve. If m wants the solver to take it over, he deletes
|
||||
his hand-drawn cable and re-solves; the solver re-creates an equivalent
|
||||
auto cable.
|
||||
|
||||
### 5b.4 When solver fails
|
||||
### 5b.4 When solver fails — quick-fix UX
|
||||
|
||||
Three classes of failure surface in the response's `unsatisfied[]`:
|
||||
|
||||
1. **No compatible cable type** — `T = ports(from).types ∩
|
||||
ports(to).types` is empty (e.g. a Power-only device to an HDMI-only
|
||||
device). UX: edit the requirement to specify, or add a port on one of
|
||||
the devices.
|
||||
2. **Ambiguous cable type** — `|T| > 1`, no preferred set. UX: pick a
|
||||
type on the requirement.
|
||||
device).
|
||||
2. **Ambiguous cable type** — `|T| > 1`, no preferred set on the
|
||||
requirement.
|
||||
3. **No free port** — the cable type matches but every port on one side
|
||||
is already used. UX: drop a must_connect=0 requirement, or add ports.
|
||||
is already used.
|
||||
|
||||
The solver does **not** auto-add ports to a device. Reason: m said
|
||||
"override per instance"; auto-adding crosses that line. The UI surfaces
|
||||
the unmet requirement with a "+ Add port" affordance on the device
|
||||
inspector instead.
|
||||
The solver does **not** auto-add ports without m's consent. v4.1 ships
|
||||
an explicit one-click quick-fix per class of failure, surfaced as a red
|
||||
badge on the affected device in the inspector (§7) and as a button on
|
||||
each `unsatisfied[]` entry in the preview-diff modal:
|
||||
|
||||
| Failure class | Quick-fix button | What it does |
|
||||
|---|---|---|
|
||||
| No compatible cable type | **"+ Add <preferred_type> port to <device> and re-solve"** | POST `/api/projects/:pid/devices/:id/ports` with `type_id=preferred_type` + sensible default offset, then immediately POST `/solve` again. The preferred_type is the requirement's `preferred_cable_type_id`. If the requirement has no preferred type, the button reads "Specify cable type" and opens an inline cable-type picker on the requirement instead. |
|
||||
| Ambiguous cable type | **"Specify cable type"** | Opens an inline picker on the requirement row with the candidates from `T` pre-listed. On select → PATCH the requirement → re-solve. |
|
||||
| No free port | **"+ Add <type> port to <device> and re-solve"** | Same as the no-compat case but the `type` is already determined (it's the requirement's preferred or auto-picked type). Adds a port on whichever side ran out (the response's `reason` carries `which_side`). |
|
||||
|
||||
All three quick-fixes do their work in a single round-trip request from
|
||||
the UI perspective: the click fires a POST that either chains the port
|
||||
insert + the re-solve server-side, or fires both calls back-to-back from
|
||||
the client (server-side chaining is simpler — see §3.2 for the endpoint
|
||||
shape).
|
||||
|
||||
The quick-fix never adds a port silently; the button text always names
|
||||
the device + cable type so m sees what's about to mutate.
|
||||
|
||||
### 5b.5 Preview vs. apply
|
||||
|
||||
`?preview=1` returns the same shape without writing. The UI shows a diff
|
||||
modal with `add[]`, `remove[]`, `unsatisfied[]`; m clicks Apply to fire
|
||||
the same endpoint without `preview=1`. Default (no flag) applies
|
||||
immediately — useful for live-solve mode (open question §9).
|
||||
immediately. Live-solve (no Solve button — every requirement edit
|
||||
triggers a debounced re-solve) is parked at slice 9+ as an opt-in.
|
||||
|
||||
---
|
||||
|
||||
@@ -869,6 +1051,31 @@ the new project (which has 5 seeded cable types and no frames yet).
|
||||
The currently active project's id is kept in URL state
|
||||
(`/?project=LOFT`) so reload returns to the same project.
|
||||
|
||||
### v4.1 — Flow: apply a setup template
|
||||
|
||||
The New Project modal gains a **"or start from a template"** section
|
||||
under the description field. Each built-in template ('Living Room',
|
||||
'Home Office', 'Server Rack') is a clickable card listing its devices +
|
||||
the requirement edges between them. Selecting one expands an inline
|
||||
override form:
|
||||
|
||||
- A pre-filled name for each template device (m can edit each, e.g.
|
||||
rename `TV` to `Bedroom TV`).
|
||||
- Per-device "skip" checkbox.
|
||||
|
||||
On Create, the server does `POST /api/projects` first; on success,
|
||||
immediately fires `POST /api/projects/:pid/apply-template` with the
|
||||
collected overrides. The response's `devices_added` + `requirements_added`
|
||||
are merged into the local snapshot and the project switches to it,
|
||||
already populated.
|
||||
|
||||
For an already-existing empty project, the inspector's project header
|
||||
shows an **"Apply template"** action that opens the same override form
|
||||
without the project-create round-trip.
|
||||
|
||||
Once the template has stamped its devices + requirements, hit **Solve**
|
||||
(§7 "Flow: run the solver") to produce the wired diagram.
|
||||
|
||||
### Flow: add a frame
|
||||
|
||||
1. `+ Frm` in the left toolbar (or `F`).
|
||||
@@ -996,11 +1203,11 @@ or m explicitly accepts the partial plan.
|
||||
| nothing | empty, with "Bundle suggestions" + "Project requirements" headlines |
|
||||
| project header | name, drawing_name, description (editable), device count, requirement count, Solve / Export buttons |
|
||||
| frame | name (editable), x/y/w/h, contained-device count, delete |
|
||||
| **device** | name + type + icon, ports grid (type / label / connected? / +Port), **unmet requirements list** (red badges with quick-fix), delete |
|
||||
| **device** | name + type + icon, ports grid (type / label / connected? / +Port), **unmet requirements list** with red badges. Each badge carries a single quick-fix button — "+ Add <type> port to <device> and re-solve" (no-compat-type / no-free-port cases) or "Specify cable type" (ambiguous case) per §5b.4. delete |
|
||||
| **port** | type, label, parent device, current cable (if any), delete |
|
||||
| **cable (auto=1)** | source/target, type, driving requirement (clickable → opens requirement edit), parent bundle (if any), label, "Promote to manual" (sets auto=0) |
|
||||
| cable (auto=0) | as v3 — type, source/target, label, delete |
|
||||
| bundle | name, member cables (clickable to focus), trunk segment description, auto-detected flag |
|
||||
| bundle | name, member cables (clickable to focus), the endpoint pair (`Device A ↔ Device B`), auto-detected flag |
|
||||
|
||||
### Keyboard
|
||||
|
||||
@@ -1027,79 +1234,54 @@ output, but it follows the solver instead of leading.
|
||||
| **4 (NEW)** | **Device-type catalog + type-aware device create** | pending | Migration 002: `device_types` + `device_type_ports`, seeded with the 11 built-ins (§2.2). Migration adds `devices.type_id`. API: `GET /api/device-types`, `GET /api/projects/:pid/device-types`. Frontend: the +Dev inline namer becomes a type dropdown + name input; choosing a built-in type seeds the device's ports on the backend. Picking `Custom (no type)` falls back to v3 freeform. m can create a typed NAS + see its Power + RJ45 ports appear on the canvas. |
|
||||
| **4.5 (NEW)** | **Manage device-type catalog (per project)** | pending | Modal: `POST/PATCH/DELETE /api/projects/:pid/device-types` for project-custom rows. Edit affordance hidden for built-ins. Lets m add an exotic device type without contributing to the built-in catalog. Validation: a custom type can't share a name with a built-in (already enforced by `UNIQUE(project_id, name)` + a separate code-level check against built-ins). |
|
||||
| **5 (NEW)** | **Connection requirements UI + CRUD** | pending | Migration 003: `connection_requirements`. API: full CRUD under `/api/projects/:pid/connection-requirements`. Frontend: left-sidebar "Requirements" section, `+ Requirement` modal (autocomplete from project's current devices, cable-type picker, must/nice toggle). Drag from device A to device B gestures the same modal pre-filled. Inspector for a selected device lists its requirements. |
|
||||
| **6 (NEW)** | **Solver MVP + Solve button** | pending | `POST /api/projects/:pid/solve` with `?preview=1` support. v0 algorithm (§5b.2): pure-function, greedy port allocation, endpoint-pair bundling (slice 6.5 is path-based bundling). Migration adds `cables.auto`. Header gains a Solve button that opens the preview-diff modal. m clicks Solve → sees the cable plan → applies. |
|
||||
| **6 (v4.1 EXPANDED)** | **Solver MVP + Solve button + setup templates** | pending | `POST /api/projects/:pid/solve` with `?preview=1` support. v0 algorithm (§5b.2): pure-function, greedy port allocation, endpoint-pair bundling. Migration 003 adds `cables.auto`. Header gains a Solve button that opens the preview-diff modal. m clicks Solve → sees the cable plan + unmet requirements (each with its quick-fix button per §5b.4) → applies. **Folded in v4.1: setup templates.** Migration 004 adds `setup_templates` + `setup_template_devices` + `setup_template_requirements` and seeds 3 built-ins ('Living Room', 'Home Office', 'Server Rack'). API: `GET /api/setup-templates`, `POST /api/projects/:pid/apply-template`. UI: a "Templates" section in the New Project modal + an "Apply template" action on empty projects → seeds devices + requirements in one transaction → Solve produces the wired diagram. |
|
||||
| **7 (was 3, slimmed)** | **Manual port + manual cable draw** | pending | The v3 flow as a tweak path on solver output. `+ Port` on an instance-owned device; click-port → click-port creates a hand-drawn cable (`auto=0`). Used to override the solver's choices or to extend its plan. |
|
||||
| **8 (was 5)** | **Export to mxdrw.msbls.de** | pending | `POST .../sync/export` writes a `.excalidraw` scene per the visual grammar (§4). Bundles ignored on export in v0. |
|
||||
|
||||
Slices 9+ (not promised for the first coder shift):
|
||||
- Path-based bundling: instead of endpoint-pair bundling, group cables that share a frame-edge corridor or a wall-axis (§5b.1 "trunk segment" definition).
|
||||
- Live-solve mode: re-run solver on every device/requirement edit with a debounce + previewed-but-not-applied diff in a toast.
|
||||
- Setup templates (Living Room, Home Office, Server Rack): a `setup_templates` table + `POST .../apply-template` that pre-populates `connection_requirements` from an archetype.
|
||||
- Bundle rendering in the SVG (thick path with mixed-colour fan-out) and in the export.
|
||||
- Live-solve mode: re-run solver on every device/requirement edit with a debounce + previewed-but-not-applied diff in a toast. Opt-in toggle in project settings.
|
||||
- Bundle rendering in the SVG (a single thick line with mixed-colour stops between the endpoint pair, plus a small badge with the cable count). Cables in a bundle still render as their individual lines underneath; the bundle is a visual overlay m can toggle.
|
||||
- "Re-seed from type" action on a device.
|
||||
- Custom setup templates (m authors them in-UI, not just the built-in three).
|
||||
- Cable inventory metadata (length/SKU) if m later wants it.
|
||||
- Dark mode.
|
||||
|
||||
Out of scope, period (would change mCables's mental model): path
|
||||
routing, cable-tray polylines, frame-edge corridors, wall-axis bundling,
|
||||
3D, anything that treats a cable as more than a labelled endpoint pair.
|
||||
|
||||
---
|
||||
|
||||
## 9. Open questions for m — v4
|
||||
## 9. Open questions for m — all closed in v4.1
|
||||
|
||||
v3 closed all its v2 questions. v4 raises six new ones, all about the
|
||||
solver semantics and UX. Worth resolving before slice 4 starts so the
|
||||
coder shift doesn't backtrack:
|
||||
The six v4 questions are now answered. Locked answers:
|
||||
|
||||
1. **Where do paths come from?** v0 draws straight lines port-to-port +
|
||||
bundles by endpoint-pair. Three candidates for slice 6.5/9:
|
||||
(a) auto-route through frame edges (cables exit a device toward the
|
||||
nearest frame edge, traverse along edges, enter the target frame);
|
||||
(b) m draws **cable-tray polylines** on the canvas and cables snap to
|
||||
them; (c) Steiner-tree-ish path optimisation per trunk. I lean (b) +
|
||||
(a) as fallback — m gets the manual override when his layout is
|
||||
non-obvious, otherwise the system routes for him. Confirm direction.
|
||||
1. **Where do paths come from?** → **Nowhere — mCables is a schematic.**
|
||||
Cables are straight lines between endpoints. The solver does not
|
||||
route, the renderer does not route, and "maximum bundling" reduces to
|
||||
the endpoint-pair rule (§5b.1). Anything resembling a path, trunk,
|
||||
cable tray, or frame-edge corridor is **out of scope, period**
|
||||
(§8 "Out of scope, period").
|
||||
2. **Live solve or button-only?** → **Button-only for v0.** Live-solve
|
||||
stays parked at slice 9+ as an opt-in.
|
||||
3. **No-compatible-port-pair UX.** → **Explicit quick-fix.** The
|
||||
unsatisfied-requirement badge in the inspector carries a single
|
||||
button — "+ Add <type> port to <device> and re-solve" —
|
||||
that POSTs the port AND fires `/solve` in one UI action. The button
|
||||
text always names the device + type, so m sees what's about to
|
||||
mutate (§5b.4 + §7).
|
||||
4. **Setup templates.** → **Folded INTO v4.1, in slice 6.** Migration 004
|
||||
adds `setup_templates` + child tables + 3 built-ins. `GET
|
||||
/api/setup-templates` and `POST /api/projects/:pid/apply-template`
|
||||
ship alongside the solver (§2.4 + §3 + slice 6 in §8). Custom
|
||||
templates (m authors his own) parked at slice 9+.
|
||||
5. **Catalog distribution.** → **SQL seed in migration 002.** No
|
||||
external file loader.
|
||||
6. **Promote to manual.** → **Explicit button** on the cable inspector
|
||||
(§7 row "cable (auto=1)"). PATCHes that only update labels stay auto.
|
||||
|
||||
2. **Live solve or button-only?** Two modes available:
|
||||
- **Button-only** (locked default) — m hits Solve, sees the diff,
|
||||
applies. Simple; no surprises.
|
||||
- **Live** — solver re-runs on every device/requirement edit with a
|
||||
debounce, results land in a toast "12 cables, 3 bundles, 1 unmet
|
||||
— review?". More responsive, costs ~10ms of compute per edit.
|
||||
I'd ship button-only first (slice 6); add live as an opt-in toggle
|
||||
(slice 9+). Confirm or escalate to "live always".
|
||||
|
||||
3. **No-compatible-port-pair UX when solving.** Three options:
|
||||
(a) Surface as "unsatisfiable" and let m manually add a port via the
|
||||
device inspector (current §5b.4 stance).
|
||||
(b) Auto-add the missing port to the device on the m's confirmation
|
||||
(single-button "Add HDMI to PC and re-solve").
|
||||
(c) Auto-add silently — bad UX, surprise mutations.
|
||||
I lean (a) with a one-click quick-fix that does (b). Confirm.
|
||||
|
||||
4. **Setup templates — v4 or post-MVP?** "Living Room" / "Home Office" /
|
||||
"Server Rack" archetypes that pre-populate `connection_requirements`.
|
||||
I left this out of the v4 slice list (designed in §8 "slices 9+") to
|
||||
keep v4 tight. m can build the same effect by adding requirements
|
||||
manually first time. Confirm: post-MVP OK, or do you want me to fold
|
||||
it into slice 5/6?
|
||||
|
||||
5. **Catalog distribution: code seed vs. JSON file.** Two paths:
|
||||
(a) seed in migration 002 via SQL INSERTs (today's design — locked-in,
|
||||
schema versioned, no user override of built-ins).
|
||||
(b) seed from a curated JSON file at `internal/db/catalog/builtin.json`
|
||||
that the migration reads. m or contributors can extend the file by
|
||||
PR; rebuild image; new built-ins appear.
|
||||
I lean (a) for v0 (simpler, doesn't need a file-loader). Open
|
||||
question: do you anticipate growing the built-in list often? If yes,
|
||||
(b) starts paying off after the second addition.
|
||||
|
||||
6. **Promoting a solver cable to manual.** §5b.3 says PATCHing an
|
||||
`auto=1` cable flips it to `auto=0` so the next solve doesn't replace
|
||||
it. Two surface variants:
|
||||
(a) Implicit: any PATCH that touches type/from/to on an auto cable
|
||||
promotes it. m never sees the flag.
|
||||
(b) Explicit: a "Promote to manual" button on the cable inspector
|
||||
(current §7 stance). PATCHes that only update labels stay auto.
|
||||
I leaned (b) for clarity ("the solver might overwrite this — promote
|
||||
to protect"). Confirm or override.
|
||||
No open design questions remain. The coder shift is gated on m's
|
||||
go/no-go for v4.1 — not on any unanswered design question from picasso.
|
||||
|
||||
---
|
||||
|
||||
@@ -1248,4 +1430,4 @@ gitignored.
|
||||
|
||||
---
|
||||
|
||||
DESIGN v4 READY FOR REVIEW
|
||||
DESIGN v4.1 READY FOR REVIEW
|
||||
|
||||
Reference in New Issue
Block a user