Files
Joshua KingandClaude Opus 5 d9ed1898b9 Add the Extrudex Design System v3 bundle
Tokens (light + dark, neumorphism, 54/54 WCAG AA contrast pairs), the
self-hosted JetBrains Mono fonts, and interactive mockups of every screen
across three iterations. Iteration 3 is final: phone, tablet and desktop
tiers, device preview, and "empty spool weight" terminology. The build
scripts regenerate the screens byte-identically from source.

Implementation is tracked in Maestro feature UI Refresh - V3.0
(224b72a1-8d84-4970-97ea-5dafa4d8ea3b).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 11:31:50 -04:00

143 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Iteration 3: the remaining screens, plus tablet and phone
2026-09-18. Built on iteration 2 (left unchanged in `../iteration-2/`). Same v3
tokens, same shared panel/palette/toast system. **Start at
`screens/index.html`** (flow map, 14 guided flows) or **`screens/devices.html`**
(every screen in live phone, tablet-portrait and tablet-landscape frames).
---
## 1. Your answers, applied
| Question | Your answer | What changed |
|---|---|---|
| One-click suggestion | Fine if accurate; fixable by hand | Kept one click. Every attributed print now has **Change spool** in its panel, and it states the real effect: *"46 g goes back to Elegoo Black #42 and comes off the spool you pick; cost is re-rated."* (That's what `SetPrintSpool` does on `dev`.) Rows attributed before deductions were tracked get the real refusal: reconcile to the same spool first (see print **Test_cube**). |
| Weigh-session order | By weight, with location visible | The session always runs **lightest first**, whatever the table sort. Each step opens with a **"Find it: Shelf C"** strip and "spool 2 of 4 · lightest first". |
| Landing page | Dashboard | Unchanged. |
| Filament badge | Yes: it flags an assumed weight that could be off | **Filament [7]** badge = empty spool weight **unverified**: 3 unknown + 4 estimated (Generic 180 g, Refill 35 g). Its tooltip splits the two. The saved view is renamed **Empty spool weight unverified**, and the Dashboard item reads "7 spools have an unverified empty spool weight: 3 can't be weighed yet, 4 could be off." |
| Palette commands | Makes sense | `load ‹spool› into ‹holder›` and `attribute ‹print› to ‹spool›`. They're typed in stages: the list narrows to spools or prints, then to holders or spools. Full holders show "full · 4/4" and refuse. Two new actions in the empty palette prefill them. |
> **Check my reading of 4a.** I took "identifies an assumed weight" to include
> *estimated* empty spool weights, not just unknown ones. If you meant unknown only, it's a
> one-line change and the badge drops to 3.
---
## 1b. Terminology: "empty spool weight", not "tare" (changed 2026-09-18)
"Tare" isn't how the 3D printing community talks, so every user-facing string
now says **empty spool weight**. The short form **empty spool** is used only
where space is tight: column headers, and the scale equation "262 g empty spool
+ 812 g filament = 1,074 g". Examples:
| Was | Now |
|---|---|
| Tare unverified (view) | Empty spool weight unverified |
| Empty spool (tare) | Empty spool / Empty spool weight |
| tare unknown | empty spool weight unknown |
| Set a tare · Save tare | Set empty spool weight · Save empty spool weight |
| Remaining = reading − 140 g tare | Remaining = reading − 140 g empty spool |
| Brand tare | Brand empty spool weight |
**Not changed:** code and API names (`tare_grams`, `tare_source`,
`effective_tare_grams`, `tare_origin`). Renaming those would be an API break for
no user benefit. CTXD's GLOSSARY.MD still defines "Tare"; I haven't edited CTXD
(read-only for this task). You may want to add "empty spool weight" there as
the user-facing term.
Iterations 1 and 2 still say "tare"; they're kept as snapshots.
---
## 2. New screens
| Screen | Who | What it's for | Built from on `dev` |
|---|---|---|---|
| **05 Lists** | operator+ | The 5 name-part lists + spool types. A name bar shows which part you're editing ("Sunlu **Rapid** PETG CF Silk"). "Used by N spools" links to Filament filtered. Remove is disabled with the reason when in use. Spool types show empty spool weight + trust level; adding one enforces "weight and source together, never 0 g". Demos: rename conflict (409), add error. | `ManageLookups.tsx`, `lookup` store rules |
| **06 Labels** | operator+ | Queue of spools without a label (preselected) → one PDF. Previews at the **real 51 × 21 mm layout** (19 mm QR · Brand / Color · taxonomy · #id). Tabs: all spools (reprint), holders (per-printer holder labels that start scan sessions). A format reference is included. | `labels.go`, `/api/spool-labels/unprinted`, `/api/filament-containers/{id}/label` |
| **07 Access** | admin | Authentik owns people and roles; Extrudex shows its local view. Role cards (reader / operator / admin, from the route policy). People with sessions (idle and hard expiry) and **Revoke…** with an inline confirm. **Machines & API tokens**: new token shown **once** (`exd_…`), per-token revoke. | `AccessAdministration.tsx`; machines/tokens have **API but no screen** on `dev` |
| **08 System** | admin | **Low-filament report**: schedule, threshold, weekly delivery history (retries, errors), **Run this week's now**. **Audit log**: who/what/when with links to the thing changed, filters, paging. **This device**: install, update-ready, offline readiness. | `webhook.go`, `audit.go`, `PWAControls.tsx`; webhook and audit have **API but no screen** on `dev` |
| **09 Scan: holder** (`/c/:id`) | phone-first | Scan a holder label → a session for this tab. Each spool scanned **loads** (from storage), **returns** (already here) or **moves here** (from another holder). No mode picker. Full → refused, nothing changes. Result card + haptic, a session log, "Done scanning". Camera sheet (demo) + type-a-number fallback. Reader view shows contents only. | `ScanSession.tsx`, `ScannedSpoolRoute` |
| **10 Scan: spool** (`/s/:id`) | phone-first | A spool label scanned with no holder session active: big remaining, **Lives in**, one-handed weigh-in (68 px input, before → after, the same two refusals), then open its holder or full details. Empty spool weight unknown → pick a spool type first. | `ScanWeighSpool.tsx` |
| **Printer panels** (on Printers) | admin | **Add / edit printer** (brand filters models from `printerCatalog.ts`; access code write-only; Elegoo has none), **Manage holders** (add AMS/AMS Lite, the external holder can't be removed, loaded holders can't be removed, print holder label), **Remove printer** (type the exact name). | `PrinterForm.tsx`, `ManagePrinterContainers.tsx`, `ConfirmRemovePrinter.tsx` |
| **Devices** | — | Every screen in 3 live frames, dark/light. | — |
The nav now reaches everything: **Manage** (Lists, Labels, Access, System) on
the rail, and a **More** tab on phones.
---
## 3. Things I checked against the code (and corrected in the design)
| Claim I almost made | What `dev` actually does | Design now says |
|---|---|---|
| "Run the report again resends it" | A delivered week returns its existing result; only a retrying week is retried (`runPeriod`) | "Already delivered Monday 08:00; nothing was sent again. Running it never sends a duplicate." |
| "Remove printer, and history is kept" | Also **refused while any holder still has spools**, and an open print is **settled as unknown** | Both shown in the remove panel; confirm disabled while loaded |
| Re-attributing a charged print is blocked | Allowed: grams are **returned** to the old spool and taken from the new one, *except* rows from before tracking (`ErrUnreconciledAttribution`) | "Change spool" with the exact effect, and the real refusal |
| Scanning a spool label always opens it | Only with no holder session active; otherwise it's loaded/moved into the active holder | Both routes designed; each page explains the other |
| Sample token `ctxd_…` | Extrudex tokens are `exd_…` | Fixed |
**Two findings for you** (not design changes):
1. **List names are case-sensitive in the schema** (`UNIQUE`, no `COLLATE
NOCASE`), so "petg" can be added beside "PETG", which is the very problem
the lists exist to prevent. The Lists screen shows a *proposed*
near-duplicate warning, labeled as a proposal.
2. **Audit log, low-filament report and machine tokens have APIs but no
screens on `dev`.** They're designed here and tagged "API exists · no screen
on dev yet".
---
## 4. Responsive specification
| | Phone < 700 px | Tablet 700–1180 px | Desktop > 1180 px |
|---|---|---|---|
| Navigation | Bottom bar: Dashboard · Filament · Prints · Printers · **More** (sheet with Manage + theme), badges on icons | **Compact rail** (88 px): icon + label, badges top-right, Manage group | Full rail with labels, Manage group, theme switch |
| Search | "Search…" pill, no shortcut hint | Full placeholder; shortcut hint hidden on touch | Full placeholder + ⌘K |
| Primary action | Floating **+** (56 px) above the bar | Header button | Header button |
| Panel | Full-screen sheet | 480 px side sheet over the page | 520 px side sheet |
| Tables | Low-priority columns dropped (`hide-m`); Lists become stacked rows | Tablet-only drops (`hide-t`): Value, Cost, Use; Weigh becomes icon-only | All columns |
| Touch | 44 px+ targets; menu items 12 px padding; `pointer: coarse` rules | Same `pointer: coarse` rules | Mouse sizes, keyboard hints |
| Scan pages | Designed for this: 64 px scan button, 68 px scale input, haptic on result | Centered 560 px column | Centered 560 px column |
| Dashboard | One column; attention buttons full-width | One column below 980 px, two above | Two columns |
| Printers | One card per row | 2 per row (portrait 1, landscape 2) | 3 per row |
---
## 5. New open questions
1. **Empty spool weight badge scope.** Unknown + estimated (as built), or unknown only? See §1.
2. **Near-duplicate list names.** Add a case-insensitive uniqueness check (a
schema change plus migration), or only warn in the UI?
3. **Holder scan session end.** `dev` keeps the active holder for the browser
session. Is an explicit **Done scanning** (as designed) wanted, and should it
also time out after a few minutes idle?
4. **Machine tokens, audit log, low-filament report.** Worth building screens
for all three now, or keep them API-only until something needs them?
5. **Label printing.** Always PDF download, or is there a specific label printer
you'd want a direct-print path for?
## 6. More implementation tasks (titles only, smallest first)
1. Rename the "Tare unknown" view to "Empty spool weight unverified" and include estimated weights
2. Add a Filament nav badge for unverified empty spool weights
3. Always run weigh sessions lightest first
4. Show the spool's location at the top of the weigh step
5. Add "Change spool" to attributed prints, stating the returned grams
6. Show the unreconciled-attribution refusal with its fix
7. Add a case-insensitive near-duplicate warning to Lists
8. Show "Used by N spools" with a link on every list value
9. Add label previews at 51 × 21 mm proportions
10. Add holder labels to the Labels screen
11. Add the "load … into …" palette command
12. Add the "attribute … to …" palette command
13. Add a More tab and sheet to the phone navigation
14. Add the compact tablet rail (700–1180 px)
15. Add a Manage holders panel with the removal rules
16. Add "Remove printer" with name confirmation and the loaded-holders refusal
17. Add a "Done scanning" control to holder scan sessions
18. Add the phone weigh-from-label layout
19. Add an audit log screen over `GET /api/admin/audit`
20. Add a low-filament report screen over the webhook endpoints
21. Add a machine and API token screen with show-once tokens