Files
design-v3/00-understanding.md
T

194 lines
13 KiB
Markdown
Raw Normal View History

2026-09-19 11:31:50 -04:00
# Extrudex UI redesign: what exists today
Written 2026-09-18, before any design work. Every source here was read, not
changed.
| Source | Version read |
|---|---|
| CTXD `extrudex`: CONTEXT.MD v3, DECISIONS.MD v4, GLOSSARY.MD v3, HANDOFFS.MD v32 (skimmed) | 2026-09-07 / 2026-09-17 |
| Repository `dev` at `321ce0d` (2026-09-17) | read only |
| Printer-connections feature branch, local ref `origin/feature/7efc2030-…-printer-connections` at `4e17f9e` (2026-09-17) | read with `git diff`, no checkout, no fetch |
| Design System v2.0: `http://pages.ccl/design-v2.0/` | HTTP 200, tokens extracted |
## Scope agreed on 2026-09-18
- Designs are based on the **current `dev` code**, not on the 2026-08-12
picture in CTXD. CTXD is out of date (see "Differences between CTXD and the
code" at the end).
- **Screen 1 is a new standalone Dashboard.** Today, the home route `/` is
`Floor.tsx`.
- **The Printers screen** uses the fields stored on `dev` plus the lifecycle and
staleness fields from the unmerged printer-connections feature branch. The
branch fields are labeled "on feature branch, not in production".
---
## 1. Fields shown on each existing screen
### Floor (`pages/Floor.tsx`, route `/`, the de facto dashboard)
Polls every 10 s. It has two columns.
**Primary column**
- Printers view (`pages/Printers.tsx` embedded; see below)
- "Today's prints", from midnight America/New_York, with an "All prints" link
- Print list (`components/PrintList.tsx`), one row per print:
- job name (click to edit; locked while running with "name is captured from the printer")
- printer name · started at
- status badge: `running` / `finished` / `failed` / `unknown`
- duration (`—` when null), plus a **`+gap` marker** when `connection_gaps > 0`. The tooltip says the figure is "an upper bound rather than a measurement".
- grams used (click to edit) or a grams input; multi-color prints show "N g total"
- spool picker ("274 g · Sunlu Mint Green · PLA · Matte"), with a suggested spool
- cost, or "cost pending" on multi-color prints
- use type select: Personal / Business / "Use not recorded"
- archive control
- per-color rows for multi-color prints: slot, spool, grams, color hex, suggested spool
- Empty states: "Nothing printed today." / "No prints captured yet."
**Secondary column**
- "Running low": spools under `LOW_STOCK_GRAMS` = **200 g**, each with a color swatch, color name, brand and grams
- "Inventory" stats: **Spools**, **Filament left** (g), **Stock value** ($), **Running low** (count, warning style)
- Empty state: "No filament tracked yet."
> **Not shown today:** `weight_source` (see §2 gaps), tare, gross weight.
### Filament (`pages/Filament.tsx` + `components/SpoolTable.tsx`, route `/filament`)
- Header: "Labels outstanding: N", **Download unlabeled labels**, **Manage lists**, **Add spool**
- Filters: search ("Search brand, colour, material, location…"), **Low stock only**, **In storage only**, brand select with counts, material select with counts, sort (Least remaining / Most remaining / Brand A–Z / Colour A–Z / Highest value), **Group identical**, view toggle **Table / Cards**, pagination
- **Table columns:** swatch · color name (click to edit) + brand · Filament (material · type · finish · speed; "Standard"/"Basic" hidden) · Remaining ("N of M g" + bar, red below 200 g) · $/kg ("Not set" when 0) · Value · Location ("In storage · detail" or container name) · Delete
- Grouped rows: combined total, "N of M g each", "$X/kg each", expand/collapse
- **Card view:** swatch, color name, brand, taxonomy line, bar, "N g left", value, "In storage" / "Loaded · container", storage detail, Edit, Delete
- Delete opens `ConfirmDelete`
- Error and status lines, plus empty states
> **Not shown today:** tare, tare source, gross weight, remaining source (`typed`/`weighed`), label printed date.
### Add/Edit spool (`components/SpoolForm.tsx`, a modal `<dialog>`)
Title: "Add spool" / "Edit spool #ID". It has four sections.
1. **Identity:** Brand* · Material* · Type (optional) · Finish* · Speed (optional) · Spool type (optional, "sets the empty-spool weight") · Location (optional, "Shelf A")
2. **Colour:** Colour name (with a suggestion from the pinned FilamentColors.xyz catalog: unique, ambiguous or no match) · 1–3 colors, each with a swatch picker and hex text, plus Add colour / Remove / "Colour not known yet" (stores #888888)
3. **Quantity and cost:** Total grams ("filament, not incl. spool") · Remaining grams · Empty spool (tare, optional, "e.g. 250") with a **tare origin note**:
- `spool`: "This spool's own weight. Saved with it."
- `spool_type`: "From the X spool type", plus "(estimated — weigh your own to be sure)"
- `brand`: "Guessed from the X brand…" (warning style)
- `unknown`: "Recorded on this spool, but nothing says whether it was weighed…"
- `none`: no note
Then Cost per kg.
4. **Weigh it** (only when tare > 0): scale reading, placeholder "full reads N", hint "Remaining is filled in for you: reading minus N g of spool". Otherwise: "Set an empty-spool weight… and you can weigh the whole spool".
- Footer: client-side errors (scale below tare, capacity contradiction), **a single server `error` line**, Cancel, Save changes / Add spool
- **409 today:** the server message ("this spool changed after you loaded it — reload it and reapply your edit, so a newer weigh-in is not overwritten (you loaded version 4, it is now version 5)") appears as plain text in the footer. There is no reload action and no side-by-side comparison.
- **400 today:** also only in the footer, never next to the field that caused it.
Real 400 messages that can be mapped to a field: `color_name is required` · `cost_per_kg cannot be negative` · `remaining_grams cannot be negative` · `remaining_grams cannot exceed total_grams` · `gross_grams must be greater than zero — it is the whole spool on the scale` · `a tare of 0 is not a measurement — leave it unset instead` · `scale_grams is required and must be greater than zero`.
### Printers (`pages/Printers.tsx`, embedded in Floor; `/printers` redirects to `/`)
**This screen already exists on `dev`.** It is not new.
- Header: Add printer / Manage AMS units (admin only; others see "Admin role required…")
- Grouped into **In progress**, **Needs attention** (paused, failed), **Idle**, **Offline**, with counts
- **Full card** (busy/paused/failed): name · state badge (Printing, Paused, Failed, Idle, Preparing, Slicing, Waiting…, Offline) · actions menu (Edit, Remove) · job name or "Job name unavailable" · **camera snapshot** (or "disabled" / "unavailable while offline" / "unavailable") · **Loaded filament, "not confirmed in use"**: each container's label, used/capacity, and each spool's swatch, brand, color and grams (or "Empty") · last print line
- **Compact row** (idle/offline): state marker, name, badge, last job + "Ended …", or "Last contact unavailable" when offline
- Printer form (`PrinterForm.tsx`): name, brand (bambu/elegoo), model, IP, serial, access code (write-only)
---
## 2. Printer data Extrudex stores today
### Stored in SQLite on `dev` (`schema.sql`)
| Table | Field | Notes |
|---|---|---|
| `printer` | `id`, `name` (unique), `brand` (`bambu`/`elegoo`/null), `model`, `ip`, `serial` (unique), `access_code` (never returned; API gives `has_access_code`), `created_at` | brand and model are null together (rows imported from printers.json) |
| `filament_container` | `id`, `printer_id`, `kind` (`ams`/`ams_lite`/`external`), `label`, `created_at`, `version`; `capacity` derived (4/4/1) | every printer gets one "External" holder |
| `spool` | `container_id` | which holder a spool is in; NULL = storage |
| `spool_location_event` | `spool_id`, `container_id`, `at`, `principal_id` | move history, not shown in UI |
| `print` | `printer` (name text), `printer_id`, `job_name`, `job_key`, `status`, `started_at`, `ended_at`, `duration_seconds`, `connection_gaps`, `grams_used`, `weight_source`, `cost_usd`, `deducted_grams`, `charged_rate_per_kg`, `spool_id`, `slice_objects`, `archived_at`, `use_type` | per-print history |
| `print_filament` | `slot`, `spool_id`, `suggested_spool_id`, `grams_used`, `color_hex`, `weight_source`, `deducted_grams`, `charged_rate_per_kg`, `cost_usd` | multi-color components |
| `job_grams` | `job_key`, `grams`, `updated_at` | remembered weights |
### Live, in memory, served by `GET /api/printers` on `dev` (`PrinterStatus`)
`id`, `name`, `connected` (the transport session, not power), `state` (gcode_state: IDLE, PREPARE, RUNNING, PAUSE, FINISH, FAILED, SLICING, or "" until first seen), `job_name` (only while a job runs), `camera` (`snapshot`/`unsupported`/`disabled`), `last_print`. Also `GET /api/printers/{id}/snapshot` returns a JPEG.
### Parsed but **not** stored or exposed
`mc_percent` (progress) and `gcode_file_prepare_percent` are read from MQTT and used internally only. There are **no** temperatures, ETA, layer or AMS `remain` %. Bambu never reports grams.
### On the feature branch only (not on `dev`, not in production)
Added to `PrinterStatus`: `lifecycle` (`connecting` / `synchronizing` / `ready` / `reconnecting` / `invalid_config` / `unsupported` / `stopped`), `ready`, `stale` (state and job are cached from a session that is not ready), `last_report_at`, `last_report_age_seconds`, `failure_category` (`setup_failed` / `setup_timeout` / `synchronization_timeout`), `failure_detail` (a fixed sanitized phrase), `session` (generation counter). There are UI helpers for "Last known: Printing · 4m ago", "Disconnected — reconnecting" and "Last contact 4m ago".
**The dropped-MQTT state the brief asks for** is `connected=false`, which shows as "Offline" on `dev`. On the feature branch it becomes `lifecycle=reconnecting` with `stale=true` and a report age.
---
## 3. Design System v2.0 color palette (from `http://pages.ccl/design-v2.0/`)
It matches `web/src/theme.css` on `dev` exactly. **v2.0 has one (dark) theme only.** There is no light theme.
| Token | Hex | Usage |
|---|---|---|
| `--ccl-charcoal` | `#2c2c30` | page background, near-black (`--surface-page`) |
| `--ccl-charcoal-2` | `#36363a` | raised surface (`--surface-raised`) |
| `--ccl-charcoal-3` | `#1b1b1e` | input / code block, recessed (`--surface-code`) |
| `--ccl-gold` | `#8f7ae0` | brand accent, violet despite the name (`--accent`, `--focus-ring`, alias `--ccl-purple`) |
| `--ccl-gold-dim` | `#7a63cc` | accent hover / pressed (`--accent-hover`) |
| `--ccl-text` | `#f4f4f6` | primary text |
| `--ccl-text-muted` | `#b2b2ba` | secondary text |
| `--ccl-border` | `#3d3d44` | hairline borders |
| `--ccl-border-2` | `#4e4e57` | stronger border / grid lines |
| `--ccl-green` | `#a9d88b` | success |
| `--ccl-red` | `#f08b93` | error |
| `--ccl-white` | `#ffffff` | white |
| `--ccl-ink` | `#2a2431` | text on accent |
| `--ccl-purple-wash` | `rgba(143,122,224,0.12)` | accent tint |
Other v2.0 rules the redesign has to deal with:
- Radius: **`--radius-none/sm/md` are all `0px`** ("the brand is square. 0 is the rule").
- Shadows: **flat brand**. The only shadow is `--shadow-raised: 0 1px 0 0 var(--ccl-border)`.
- Font: JetBrains Mono for everything. Sizes are 11/12/14/15/18/22/28/40 px and weights 400/500/700/800.
- Spacing runs from 4 to 64 px. Motion is 100/160 ms with `cubic-bezier(0.2,0,0,1)`.
- There is **no warning or amber token**. Low stock currently reuses red.
---
## 4. Differences between CTXD and the code
These are recorded so the design follows the code and not the docs.
| CTXD says | `dev` has |
|---|---|
| `pages/Dashboard.tsx` | No such file. `/` is Floor |
| Printer status pages are out of scope | Printers view with camera, AMS containers and CRUD |
| No authentication | OIDC, sessions, API tokens, reader/operator/admin roles |
| Bambu only | Bambu MQTT + Elegoo Centauri SDCP |
| `weight_source` is `typed`/`recalled`/`unknown` | Also **`sliced`** (read from the 3MF over FTPS). The code says `recalled` is "no longer written" |
| Tare source: manufacturer/community/estimated | Two separate things: `spool.tare_source`/`tare_origin` (where this spool's tare came from: spool, spool type, brand, unknown, none) and `spool_type.source` (how trustworthy the published figure is: manufacturer, community, estimated) |
| Design system at pages.cubecraftlabs.com | 404. The live guide is at `pages.ccl` |
---
## 5. Assumptions I'm working from
None of these blocks the design. Each is a default I've picked, and each is
repeated in SUMMARY.md so you can overturn it.
1. **The Dashboard shows `weight_source`, even though `GET /api/prints` does
not return it today.** It is stored in the `print` column, and the cost
payload has it. The mockup marks it as needing an API field.
2. **Weight source has four values** (typed, sliced, recalled, unknown). That
follows the code, not DECISIONS.MD's three.
3. **Tare is shown as two facts:** where this spool's tare came from (origin)
and, when it came from a spool type, that type's trust level (manufacturer,
community, estimated).
4. **The v3 light theme is new.** v2.0 has none, so every light token counts as
"added".
5. **Neumorphism overturns two stated v2.0 brand rules**: 0 radius and flat
shadows. This is recorded as a deliberate departure, not as editing a token
to suit a component.