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

14 KiB
Raw Blame History

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:
    $jog-bg     = #3f4b63
    $jog-hover  = #4a5777
    $jog-dir    = #5b6885
    $jog-ghost  = #8c97ad
    $accent     = #fde047
    $accent-ink = #0f172a
    
  3. Build the header in index.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.