Files
onefinity-firmware/plans/2026-04-30_ux_redesign.md
Henrik Muehe 081209decf Plan: resolve open questions (hard cut, macros slice, override drawer, defer pin)
Replaces the 'Open questions' section with 'Resolved decisions' and
propagates the four decisions into the relevant phases:

- Hard cut: no config.ui.layout flag. Phase 6 now includes the
  removal of .nav-header, side-menu.css and the #tab1..#tab4 block
  with a git grep verification step.
- Macros: Control row binds to config.macros.slice(0, 8); Settings
  -> Macros owns the master list and reordering.
- Pin to Control: deferred, status strip stays at State / V&F /
  Spindle / Job for this iteration.
- Feed/spindle override: bottom drawer triggered by the Spindle
  KPI tile, reusing override_feed / override_speed.

Goals (s.1) and Phase 6 testing checklist updated to match.
2026-04-30 20:43:29 +02:00

170 lines
14 KiB
Markdown
Raw 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.
# UX Redesign — Implementation Plan
Reference mock: `docs/mocks/v09_full_ux.html`
Target hardware: 10.8" portable monitor, 1920×1080, capacitive touch, Chrome fullscreen.
## 1. Goals
The redesign keeps every existing feature but reorganizes the page into a single-screen control surface for finger-touch use:
- A slim 96 px header replaces the 140 px nav-header. Only logo + ONEFINITY wordmark + tab bar + system pill + READY badge + octagonal STOP.
- 4 top-level sections accessed via underline-ribbon tabs in the header:
1. **Control** — jog pad, DRO table, status strip, macro row.
2. **Program** — Auto run controls, file actions, G-code listing, 3D viewer.
3. **Console** — MDI, Messages, Indicators (sub-tabs).
4. **Settings** — paged settings (replaces the Pure left rail).
- Touch targets ≥ 64 px (jog tiles 72 px, axis action icons 72 px, macro buttons 84 px).
- All action chip-soup (WiFi/Camera/Rotary/IP/Version) collapses into one "All systems · view" pill that opens a popover. Burger menu removed (Settings tab supersedes it).
- V09 jog/macro palette: flat soft slate (#3f4b63), no drop shadow; yellow (#fde047) accent for active states (step seg, tab underline, macro number badge).
- Spindle override / feed override sliders live in a bottom-edge drawer triggered by tapping the Spindle KPI tile (no permanent screen real estate).
- Hard cut: no `config.ui.layout` flag; the new shell replaces the old in a single release.
## 2. Scope of code change
The build is Pug + Stylus + Browserify Vue (Vue 1.x). `index.pug` defines the chrome; `src/pug/templates/*.pug` defines each view; `src/js/*.js` mirrors them as Vue components routed by `currentView` from the URL hash.
Files we will touch:
- `src/pug/index.pug` — replace `#layout / #menu / #main / .nav-header` with the new header + tab bar + body. Drop the burger and the side-menu include.
- `src/pug/templates/control-view.pug` — restructure into the new Control panel (jog grid + DRO table + status strip + macro row). MDI/Messages/Indicators move out.
- New `src/pug/templates/program-view.pug` — Auto sub-panel content (action bar, file bar, gcode-viewer, path-viewer).
- New `src/pug/templates/console-view.pug` — MDI / Messages / Indicators sub-tabs hosting existing `console.pug` and `indicators.pug` partials.
- `src/js/app.js` — extend `parse_hash` so `#program`, `#console`, `#settings` resolve; expose tab state for the header to highlight.
- `src/js/control-view.js` — keep jog/DRO logic, drop the Auto/MDI/Messages/Indicators internal `tab` state and template hooks.
- New `src/js/program-view.js`, `src/js/console-view.js` — extracted Vue components.
- `src/stylus/style.styl` — add `.app-shell`, `.head`, `.tabs-host`, `.ktab`, panel styles, V09 jog tokens. Keep legacy classes alive until templates fully migrated.
- `src/static/css/side-menu.css` — stop including in `index.pug`.
- Settings: keep `settings-view.pug`, `admin-general-view.pug`, `admin-network-view.pug`, `motor-view.pug`, `tool-view.pug`, `io-view.pug`, etc., and surface them through a left-rail navigator inside the Settings panel rather than the sidebar.
- Settings → Macros owns the full macro list (1…N). Control's macro row is a slice of the first 8; reordering happens in Settings.
## 3. Routing model
We keep the existing URL hash routing because everything in `src/js/app.js#parse_hash` and the deep-linked menu items (`#motor:0`, `#admin-network`, etc.) depend on it.
| URL hash | Top tab | Notes |
|-------------------------|------------|-------------------------------------------------------|
| `#control` | Control | Default |
| `#program` / `#program:auto` | Program | Auto sub-view (only sub-view for now) |
| `#console` / `#console:mdi` | Console | MDI default, also `:messages` and `:indicators` |
| `#settings` | Settings | Settings home (Display & Units) |
| `#admin-general`, `#admin-network`, `#motor:N`, `#tool`, `#io`, `#help`, `#cheat-sheet` | Settings | Existing routes remain, surfaced in the Settings left rail |
The header tab bar maps URL prefix → active tab. A tiny helper `topTabFromHash(hash)` lives in `app.js` and is reused by the header template.
## 4. Step-by-step
### Phase 1 — Mock parity (12 days)
1. Add `docs/mocks/v09_full_ux.html` (done) so anyone can preview the target.
2. Move the V09 palette into Stylus tokens at the top of `style.styl`:
```styl
$jog-bg = #3f4b63
$jog-hover = #4a5777
$jog-dir = #5b6885
$jog-ghost = #8c97ad
$accent = #fde047
$accent-ink = #0f172a
```
3. Build the header in `index.pug`:
```pug
.app-shell
header.head
.brand-blk
.brand-logo
.brand-name ONEFINITY
nav.tabs-host(role="tablist")
a.ktab(:class="{active: topTab === 'control'}", href="#control")
.fa.fa-gamepad
| Control
a.ktab(:class="{active: topTab === 'program'}", href="#program") …
a.ktab(:class="{active: topTab === 'console'}", href="#console") …
a.ktab(:class="{active: topTab === 'settings'}", href="#settings") …
button.sys-btn(@click="toggle_sys_popover") …
span.state-badge(:class="state_class")
estop(@click="estop")
```
4. Style the header tabs as **underline ribbon** (V02): transparent fills, slate-gray text, dark text + 5 px yellow underline on active. CSS already proven in the mock.
5. Move the rotary toggle and pi-temp warning into the system pill popover.
### Phase 2 — Control panel (2 days)
1. Rewrite the outer markup of `control-view.pug` to a CSS grid:
```
.control-grid → 720px jog-card | 1fr right-col(dro-card + status-strip)
```
Drop the `<table>`-based outer layout (axes table stays — it's a real data table).
2. Replace the legacy `<button>` elements in the jog table with `.jbtn` markup that pulls colors from `$jog-*` tokens. Keep the `@click="jog_fn(...)"` bindings unchanged.
3. Build the new `.step-seg` with the existing `jog_incr` model. The four buttons stay wired to `jog_incr = 'fine' | 'small' | 'medium' | 'large'`.
4. Build `.dro-card` from the existing `table.axes` markup. Each row gets the new 7-column grid; axis cells just need `.dro-axis`, `.dro-pos`, `.dro-sec` classes.
5. Move the four KPI tiles (`State / Velocity-Feed / Spindle / Job`) into `.status-strip`. Existing `state.v`, `state.feed`, `state.s`, `state.line` bindings are unchanged.
6. Move `.macros-div` into a `.macro-row` 8-column grid. The row binds to `config.macros.slice(0, 8)`; macros 9…N are editable and runnable only from Settings → Macros (no drawer in Control). Reordering in Settings changes which macros appear in the visible 8.
7. Drop the legacy `.tabs / #tab1` block from `control-view.pug` entirely.
### Phase 3 — Program panel (1.5 days)
1. New file `src/pug/templates/program-view.pug` with `.program-card` and the action / file bars.
2. Move the Auto bar (RUN, STOP, UPLOAD FOLDER, UPLOAD FILE, DOWNLOAD FILE, DELETE) and the file-select strip (Create Folder, Delete Folder, folder picker, file picker, sort) out of `control-view.pug` into here. Use the V09 button styles (`.action-btn`, `.action-btn.run`, `.action-btn.danger`, `.file-btn`, `.file-select`).
3. Embed `path-viewer` and `gcode-viewer` in `.program-body { 1fr 600px }`. Both Vue components render unchanged.
4. New `src/js/program-view.js` exporting the same data model the existing `Auto` tab uses (`gcode_files`, `state.selected`, `start_pause`, etc.). The fastest path: move the relevant computed/methods into a mixin `gcode-program-mixin.js` consumed by both old and new components during the migration.
5. Wire `<component :is="currentView + '-view'">` in `index.pug` to pick up `program-view`.
### Phase 4 — Console panel (1 day)
1. New `src/pug/templates/console-view.pug` with the inner `.ptab-bar` (MDI / Messages / Indicators) and `data-sub` panels.
2. The MDI panel reuses the existing `<input v-model="mdi" @keyup.enter="submit_mdi">` plus the on-screen keypad (G0/G1/G2/G3/G28/G92/M3/M5 + axis letters + CLEAR/SEND).
3. The Messages panel pulls from the existing `popupMessages` array + a new `messages_log` state we will accumulate from `app.js`'s `error` and `popupMessages` channels (no protocol change).
4. The Indicators panel mounts the existing `<indicators :state="state" :template="template">` component.
5. Sub-tab state is local Vue state (`activeSub: 'mdi' | 'messages' | 'indicators'`) plus URL fragment after `:` so deep links keep working.
### Phase 5 — Settings panel (1 day)
1. New `src/pug/templates/settings-view.pug` with a left rail and a content slot.
2. The left rail is data-driven from a list of existing settings views: General, Network, Motion (settings-view), Spindle (tool-view), Safety (admin-general subset), Camera, Macros (settings-view subset), I/O, Motors, Help, About.
3. The content slot uses `<component :is="settingsSub + '-view'">` so each existing pug template renders unchanged (`admin-general-view.pug`, `admin-network-view.pug`, `motor-view.pug`, `tool-view.pug`, `io-view.pug`, `settings-view.pug`, `help-view.pug`, `cheat-sheet-view.pug`).
4. Existing routes (`#admin-network`, `#motor:0`, …) resolve to Settings + the matching left-rail item. We lose nothing.
5. Decommission the side menu in `index.pug` and stop including `side-menu.css`.
### Phase 6 — Polish & rollout (0.5 days)
1. Pulse-dot animation for the READY badge (CSS keyframes already in the mock).
2. System pill popover content: WiFi state + button, Camera state + retry, Rotary toggle, IP address, firmware version, "Open Settings".
3. Disabled states: jog buttons + macro buttons honor `is_ready` like before; gray them out instead of hiding.
4. Decimal-places setting from the existing `display_units` plumbing — wire to a new `precision` config the DRO reads.
5. Build the **Spindle override drawer**: clicking the `.stat-card` for Spindle toggles `.override-drawer.open` anchored to the bottom edge of the body. The drawer hosts the two existing `<input type="range">` controls for `feed_override` and `speed_override` plus `Reset` buttons. Bind to the existing `override_feed` / `override_speed` methods.
6. **Hard cut cleanup:** delete the legacy `.nav-header`, side-menu markup, and the inline `.tabs / #tab1#tab4` block from `control-view.pug`. Remove `src/static/css/side-menu.css` from `index.pug` includes. Sweep `style.styl` for orphan rules (`.nav-header`, `.brand`, `.menu-link`, `.pure-menu*` overrides, `.tabs > input` selectors) and delete them in the same commit so we don't ship dead CSS.
## 5. Migration risks & mitigations
| Risk | Mitigation |
|----------------------------------------------|---------------------------------------------------------------------------------------------|
| Existing deep links from PDFs / forum posts (`#admin-network`) break | Keep the same hashes; only their visual shell changes. `parse_hash` resolves them. |
| Vue 1.x doesn't support modern slot syntax we used in the mock | The mock is plain HTML for visual review; production code uses the existing Vue 1 patterns. No new Vue features required. |
| Touch monitor with HDMI vs USB-C may report different DPI | The new layout is fluid inside 1920 × 1080 only when fullscreen Chrome. Provide a CSS `@media (max-width: 1820px)` fallback that scales the macro row to 4 columns and stacks the right column under the jog. |
| Existing customers rely on muscle memory of the side menu | Settings tab opens directly to the same left-rail navigator. First-launch toast: "Side menu moved to Settings." |
| `path-viewer` / `gcode-viewer` are heavy three.js components | They live in the Program tab now; we lazy-mount with `v-if="currentView === 'program'"` so Control stays light. |
| MDI input could lose focus when the inner `.ptab` is switched | Keep the input mounted, just hide non-active subs with `display:none`. |
## 6. Testing checklist
- Chrome on the 10.8" 1920 × 1080 monitor, fullscreen — every panel fits without scrolling at 100 %.
- Chrome at 1366 × 768 — fallback layout works (Control collapses jog above DRO).
- Touch hit-tests: every interactive target ≥ 48 px on its shortest side, primary jog tiles ≥ 72 px.
- Existing flows still work end-to-end: home all axes, run a small program, MDI a `G0 X10`, switch to Imperial, upload a folder, delete a file.
- Hash routing: hand-type `#motor:1` and confirm Settings tab activates with Motor 1 selected.
- Spindle override drawer: tap Spindle KPI tile, sliders move feed/speed override, `Reset` returns both to 100 %, tile tap closes drawer.
- Macro row shows macros 18 only; reordering in Settings → Macros changes which 8 appear on Control.
- Pulse-dot animation respects `prefers-reduced-motion`.
- Hard-cut cleanup verified: `git grep` finds no references to the old `.nav-header`, `side-menu.css`, or the `#tab1#tab4` selectors after the rename.
## 7. Estimated effort
About 67 working days for one developer:
1. Mock parity & header — 1.5 days
2. Control panel (incl. macro slice + DRO grid) — 2 days
3. Program panel — 1.5 days
4. Console panel — 1 day
5. Settings shell — 1 day
6. Override drawer, polish, hard-cut cleanup, regression tests — 0.51 day
## 8. Resolved decisions
- **Rollout: hard cut.** No `config.ui.layout` feature flag, no parallel legacy shell. The new `index.pug` tree replaces the old one in a single release; the old `.nav-header`, side menu, and embedded `.tabs` block are deleted (not gated). One pre-release internal QA pass on real hardware before tagging.
- **Macros above 8: Settings owns the master list; Control surfaces the first 8 (configurable).** The Control macro row reads from `config.macros[0..7]`; everything beyond index 7 is editable / runnable only from Settings → Macros. Users can reorder which macros land in the visible 8 there.
- **"Pin to Control" indicator slot: defer.** Not in this redesign. Tracked as a follow-up; current status strip stays fixed at State / Velocity·Feed / Spindle / Job.
- **Feed & spindle override: drawer triggered by the Spindle KPI tile.** The Spindle card in the status strip becomes tappable. Tap opens a bottom-edge drawer (≈ 220 px tall) containing the two existing range inputs (`feed_override`, `speed_override`) at touch-friendly size with `Reset to 100 %` buttons. Closes by tapping the tile again or the drawer chevron. No protocol change; reuses the existing `override_feed` / `override_speed` handlers.