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>
248 lines
14 KiB
Markdown
248 lines
14 KiB
Markdown
# Iteration 2: navigation, data display and interaction
|
||
|
||
2026-09-18. Builds on iteration 1 (`../screens/`, left unchanged for comparison).
|
||
Same Design System v3 tokens; the new components are in
|
||
`source/components.css`. Open **`screens/index.html`** first: it's the flow map,
|
||
with eight guided flows that deep-link into each state.
|
||
|
||
---
|
||
|
||
## 1. What was wrong with iteration 1
|
||
|
||
| # | Where | Problem | Why it matters | Iteration 2 fix |
|
||
|---|---|---|---|---|
|
||
| 1 | Nav, Dashboard | "Prints" and "All prints →" linked to `#`. Dead ends. | You lose trust in a nav item the first time it does nothing. | A real **Prints** page (history + attention queue, as on `dev`) |
|
||
| 2 | Filament → Edit | Edit navigated to a separate page | You lose your place, filter and scroll every time | **Detail panel** opens over the list; Esc/back returns you to where you were |
|
||
| 3 | Dashboard, Printers | A spool in "Running low" or an AMS slot wasn't clickable | The most natural next click went nowhere | Every spool, print and holder opens the same panel, on any page |
|
||
| 4 | Dashboard | Reported numbers but never said what to do | The kill criterion is **daily use**, so the home screen has to hand you today's jobs | **"Needs you"** queue, each item one click from its fix |
|
||
| 5 | Everywhere | Almost every value was an uppercase chip (status, provenance, warnings) | Nothing stands out when everything is loud, so a legend box was needed | Chip/label hierarchy (§5.1); provenance became quiet text with a tooltip |
|
||
| 6 | Filament table | Tare, source, gross, value, location and two actions in every row, 2–3 lines each | Too dense to scan 105 rows | **Stock / Weights** column sets; the full breakdown moves to the panel |
|
||
| 7 | All pages | No freshness, loading, empty or error state | The app polls every 10 s; stale data shown as live is the worst failure | "Live · updated 3s ago" in the header, an offline banner with the data's age, skeletons, empty states |
|
||
| 8 | Filament | "P1S · AMS 1 · **slot 2**" | **Invented.** The schema stores the holder, not the tray position | Holders show fill ("3 / 4") and contents; tray position listed under Future |
|
||
| 9 | Mobile | Nav squeezed into icon rows; no reachable primary action | Weighing happens at the shelf, on a phone | Bottom tab bar with badges, a floating **+** button, the panel as a full-screen sheet |
|
||
| 10 | Every page | A large proposal banner on top of every screen | Pushed content down; it read as part of the app | A small "Mockup" pill bottom-left, with the legend and demo controls inside |
|
||
| 11 | Printers | One card per section in three stacked sections | Two-thirds of the width empty; the lost printer was mid-page | Urgency order (lost → printing → idle) in one row, plus a "Jump to" strip |
|
||
| 12 | Printers ↔ Dashboard | "Open printer" landed at the top of the page | You had to find the right card yourself | `04-printers.html#printer=3` scrolls to that card and highlights it |
|
||
|
||
---
|
||
|
||
## 2. Navigation model
|
||
|
||
```
|
||
┌ Rail (desktop) / tab bar (phone) ─────────────────────────────┐
|
||
│ Dashboard · Filament · Prints [3] · Printers [1] │ ← badges = work waiting
|
||
│ Manage: Lists · Labels [4] · Access (admin) │ ← kept on dev, not redesigned
|
||
└───────────────────────────────────────────────────────────────┘
|
||
Top bar on every page: [ Search… ⌘K ] ● Live · updated 3s ago [◐]
|
||
```
|
||
|
||
- **Four destinations, ranked by how often they're used.** Manage items are
|
||
second-tier and grouped separately. Access is labeled "admin".
|
||
- **Badges only for work waiting**: Prints = prints missing a spool or weight
|
||
(amber); Printers = disconnected printers (red). No badge for totals, so a
|
||
badge always means "go here".
|
||
- **One page header pattern**: title, a one-line summary with the key numbers,
|
||
**one** primary button on the right, everything else under "More ▾".
|
||
- **Deep links for every state**, so browser Back, bookmarks and sharing all
|
||
work:
|
||
|
||
| URL | Opens |
|
||
|---|---|
|
||
| `02-filament.html?f=low&q=black` | Filament, Low view, filtered |
|
||
| `…#spool=47` · `#spool=47&tab=weigh` · `&tab=edit&state=conflict` | Spool panel, on a tab, optionally in a demo state |
|
||
| `03-prints.html?tab=needs` · `#print=210` | Prints tab / print panel |
|
||
| `04-printers.html#printer=3` · `#load=p3-lite` | Scroll to printer / load a holder |
|
||
| `?palette=weigh%20black` | Palette open with a query (demo hook) |
|
||
|
||
---
|
||
|
||
## 3. Core flows (most frequent first)
|
||
|
||
| Flow | Steps in iteration 2 | Iteration 1 |
|
||
|---|---|---|
|
||
| **Morning check** | Dashboard → "Needs you" → Review 3 → **Use** suggestion ×2, type 1 weight | Not possible; no queue |
|
||
| **Attribute a print** | Prints › Needs attention: suggestion shown inline → **Use**. Or batch-select → one spool | Not designed |
|
||
| **Weigh a spool** | Row **⚖ Weigh**, or palette `weigh copper` → type reading → see before → after → Save | Open the form, find the weigh box, save the whole form |
|
||
| **Weigh several** | Filament › Low → **Start weighing** → **Save & weigh next** (j/k also steps) | One at a time, reopening the form each time |
|
||
| **Load a spool** | Printers → holder **+ Load** → filter stored spools → pick → Load | Not designed |
|
||
| **Move / unload** | Panel › Move: storage or any holder; full holders disabled with the reason | Only by typing a location |
|
||
| **Add a spool** | **+ Add spool** (header, FAB, palette) → panel with 5-part name preview | Separate page |
|
||
| **Handle a 409** | Panel › Edit: side-by-side you-opened / now / your-edit → reapply or discard | Same content, but on a separate page |
|
||
| **Tare unknown** | Tare unknown view → Weigh tab explains, offers spool type or weigh-empty | Only a hint under the form field |
|
||
| **Find anything** | `/` or `⌘K` → spools, prints, printers and actions in one list | Filament search only |
|
||
|
||
---
|
||
|
||
## 4. Interaction patterns
|
||
|
||
**Panel (drawer) vs page.** Use a page for a *collection* and a panel for *one
|
||
thing*. The panel sits on the right (520 px) and becomes a full-screen sheet
|
||
under 860 px. Tabs: Overview · Weigh · Move · Edit for spools; a single view for
|
||
prints, with the fix at the top when something is missing.
|
||
- Opens with the heading focused (or the tab's main input), traps Tab, and
|
||
returns focus to the row that opened it.
|
||
- **Esc**, the ✕, clicking the backdrop or browser **Back** closes it.
|
||
- **↑ / ↓ buttons and j / k** step through the list it was opened from. The
|
||
footer's **Save & weigh next** does the same while weighing.
|
||
- **Unsaved changes**: closing Edit with changes shows an inline "Discard
|
||
unsaved changes?" bar, not a browser `confirm()`.
|
||
|
||
**Command palette.** `/` or `⌘K` from anywhere. Empty, it shows actions (Add
|
||
spool, Weigh a spool…, Prints needing attention, Switch theme) plus Go to.
|
||
Typing matches color, brand, `#id`, location, job name, gcode path or printer.
|
||
The `weigh ` prefix turns the list into "Weigh which spool?". ↑↓ ↵ Esc all work.
|
||
It uses the ARIA combobox + listbox pattern.
|
||
|
||
**Row actions.** A whole row is clickable and focusable (↵ opens it). The most
|
||
common action (**⚖ Weigh**) is visible on the row; the rest sit in a ⋯ menu,
|
||
which is always visible so touch and keyboard users can reach it. Destructive
|
||
items are last, after a separator, in red.
|
||
|
||
**Confirmation.** Only for destructive actions, and it says the consequence:
|
||
"Its print history stays… This can't be undone." Everything else saves
|
||
directly and confirms with a **toast** that names what changed ("Weighed Copper
|
||
· Sunlu: 97 g → 58 g"), with a **View** link. There's no fake Undo, because the
|
||
API has none.
|
||
|
||
**Batch.** Batch attribution follows `dev`'s AttributionQueue (select → attribute
|
||
to one spool). The weigh session is the batch pattern for weighing.
|
||
|
||
**Keyboard map.** `/` `⌘K` palette · `↵` open row · `Esc` close · `j` `k` next
|
||
and previous in the panel · `Tab` stays inside the panel while it's open.
|
||
|
||
---
|
||
|
||
## 5. Data display rules
|
||
|
||
### 5.1 Three visual tiers, used for nothing else
|
||
|
||
| Tier | Looks like | Used for | Examples |
|
||
|---|---|---|---|
|
||
| **State** | Colored dot/shape + bold word | What something *is doing* | ● Running · ● Finished · ◆ Unknown · ■ Failed · ○ Idle · ◆ Reconnecting |
|
||
| **Needs action** | Amber/red text with `!` or `⚠`, or an amber meter | Something *you* should do | `! no spool` · `⚠ 97 g` · `? tare unknown` |
|
||
| **Provenance** | Small gray icon + lowercase word, tooltip explains | *Where a number came from* | `✎ typed` · `▦ sliced` · `⎘ recalled` · `⚖ weighed bare` · `◇ community` · `≈ estimated` |
|
||
|
||
Each state has its own **shape** (dot, diamond, square, ring), so status never
|
||
relies on color alone. Chips (pills) remain only for view counts, filters and
|
||
the "Future, not built" marker.
|
||
|
||
### 5.2 Numbers
|
||
|
||
- Grams: `1,000 g` with thousands separators, always a space before the unit.
|
||
Totals over 1 kg: `95.72 kg`. Money: `$1,721.18`.
|
||
- Right-aligned tabular numbers in tables; the unit is never dropped.
|
||
- **Net is the default.** "Remaining" always means net filament. Gross appears
|
||
only with its tare beside it (`262 g tare + 812 g = 1,074 g`), so the three
|
||
can't be confused.
|
||
- **Unknown is written out** (`unknown`, `no duration`, `—` only for "not
|
||
applicable yet"). A missing tare is never 0.
|
||
- Durations: `3h 12m`. An upper bound is `≤ 3h 12m` in amber, with the gap
|
||
count beneath.
|
||
|
||
### 5.3 Time
|
||
|
||
Today and yesterday are relative words plus a clock time (`Today 08:37`,
|
||
`Yesterday 21:48`); older dates are `16 Sep 10:20`. Freshness is relative
|
||
(`updated 3s ago`, `last contact 6m ago`), because the question it answers is
|
||
"can I trust this?".
|
||
|
||
### 5.4 Meters
|
||
|
||
The remaining meter carries a **tick at the 200 g line**, so "how close to low"
|
||
reads without a number. The fill turns amber below it.
|
||
|
||
### 5.5 Progressive disclosure
|
||
|
||
| Where | Shows | Moves to the panel |
|
||
|---|---|---|
|
||
| Filament row | Spool, remaining meter, location *or* tare/gross (column set), value | Full tare/gross breakdown, source, spool type, label, version, prints from this spool |
|
||
| Print row | Job, state, duration, grams + source, spool, cost | Job key, why the source matters, per-color table, use type, archive |
|
||
| Printer card | State, now/last job, camera, holder contents | Connection details (collapsed) |
|
||
|
||
### 5.6 System states on every list
|
||
|
||
Loading → skeletons of the real layout (never a spinner over an empty page).
|
||
Empty → what's missing and one action ("Clear filters"). Stale → header dot
|
||
turns amber, and a banner gives the data's age and says saving is paused.
|
||
All-done → "✓ Nothing needs you right now."
|
||
|
||
---
|
||
|
||
## 6. Accessibility of the new pieces
|
||
|
||
- Panel: `role="dialog"`, `aria-modal`, labeled by its heading, focus trap, focus
|
||
returned on close.
|
||
- Palette: `role="combobox"` + `listbox` with `aria-activedescendant`.
|
||
- Tabs and segmented controls use `aria-selected`/`aria-pressed` **and** show a
|
||
raised fill plus bold text, not shadow alone.
|
||
- Sortable headers use `aria-sort` with a visible ▲/▼/↕.
|
||
- Meters use `role="meter"` with min, max and now.
|
||
- Toasts sit in an `aria-live="polite"` region. The offline banner is
|
||
`role="alert"`.
|
||
- Skip link, and focus rings on every interactive element (including rows).
|
||
- Motion (drawer slide, highlight flash, skeleton shimmer) turns off under
|
||
`prefers-reduced-motion`.
|
||
- All contrast pairs are unchanged from v3 (54/54 pass). No new colors were
|
||
introduced.
|
||
|
||
---
|
||
|
||
## 7. Things I fixed for honesty
|
||
|
||
- **Tray positions removed** (see §1 #8). The Move flow chooses a *holder*, and
|
||
full holders are disabled with the reason.
|
||
- **"Loaded filament: recorded by Extrudex, not read from the printer"** on
|
||
every printer card.
|
||
- **API gaps are labeled where they bite**: spool location history
|
||
(`spool_location_event` has no read endpoint) and weight source on
|
||
`/api/prints`.
|
||
- The weigh flow shows before → after and refuses, with the reason, a reading
|
||
below tare or above a full spool (the same two checks `dev` does).
|
||
|
||
## 8. Not redesigned in this iteration
|
||
|
||
Lists (taxonomy), Labels, Access administration, the Add/Edit **printer** form,
|
||
and the phone scan session (`/s/:id`, `/c/:id`). They keep their `dev` screens.
|
||
The nav links to them show a toast saying so.
|
||
|
||
## 9. New open questions
|
||
|
||
1. **Suggested spool acceptance.** On `dev` a suggestion is "not confirmed or
|
||
charged" until accepted. Iteration 2 puts **Use** one click away. Is one
|
||
click enough confirmation for a deduction, or should it ask?
|
||
1a. On click is fine as long as it's accurate. It can always be manually updated if it was an error.
|
||
2. **Weigh session order.** It follows the current list order (least remaining
|
||
first). Would shelf order be better? That needs location to be structured,
|
||
not free text.
|
||
2a. Weight would be best, but the location should also be visible, that way I can see the rolls that are lowest and use them first.
|
||
3. **Default landing page.** Dashboard, or straight to Prints › Needs attention
|
||
when it's non-empty?
|
||
3a. Dashboard
|
||
4. **Badge scope.** Should Filament get a badge for "tare unknown", or does that
|
||
become nagging, since most tares resolve from a spool type?
|
||
4a. Yes, this is good. It identifies an assumed weight and could be off.
|
||
5. **Palette actions.** Worth adding "Load ‹spool› into ‹holder›" and "Attribute
|
||
‹print› to ‹spool›" as typed commands?
|
||
5a. Makes sense to me.
|
||
|
||
## 10. Extra implementation tasks from iteration 2 (titles only, smallest first)
|
||
|
||
1. Add a freshness indicator driven by the last successful poll
|
||
2. Show an offline banner with the data's age when polling fails
|
||
3. Add a 200 g tick to the remaining meter
|
||
4. Replace provenance chips with icon + text labels and tooltips
|
||
5. Make spool rows and holder contents open a detail panel
|
||
6. Deep-link panel state in the URL (`#spool=`, `#print=`, `&tab=`)
|
||
7. Add Stock / Weights column sets to Filament
|
||
8. Add saved views with counts to Filament (All, Low, Loaded, In storage, Tare unknown)
|
||
9. Add a Weigh tab with a live before/after preview
|
||
10. Add a weigh session (Save & weigh next)
|
||
11. Add a Move tab that picks a holder, disabling full ones
|
||
12. Add "+ Load" on printer holders with a stored-spool picker
|
||
13. Add a Needs attention tab to Prints with inline suggestion acceptance
|
||
14. Add nav badges for prints needing attention and disconnected printers
|
||
15. Add a "Needs you" queue to the Dashboard
|
||
16. Add a ⌘K command palette over spools, prints, printers and actions
|
||
17. Add a mobile bottom tab bar and a floating Add button
|
||
18. Order printer cards by urgency and support `#printer=` scroll-to
|