Files
onefinity-firmware/plans/2026-04-30_ux_redesign.md
Henrik Muehe ef4658aaf6 Plan: V09 full UX redesign mock + implementation plan
- docs/mocks/v09_full_ux.html — high-fidelity 1920x1080 mock
  showing the proposed Control / Program / Console / Settings tab
  layout with the V09 flat slate jog/macro palette and an underline
  ribbon header tab style.
- plans/2026-04-30_ux_redesign.md — phased implementation plan to
  port index.pug + control-view.pug to the new shell while keeping
  hash routing and existing settings/admin views intact.
2026-04-30 20:00:03 +02:00

13 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).

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.

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. Cap visible buttons to 8 — overflow lives in Settings → Macros (or an "All macros" sheet for later).
  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. Add a feature flag config.ui.layout = "v09" | "legacy" in config-template.json. If legacy, keep the old index.pug tree for one release. Default to v09 after a beta cycle.

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.
  • Pulse-dot animation respects prefers-reduced-motion.
  • Audit style.styl for orphan rules from removed templates and delete them.

7. Estimated effort

About 67 working days for one developer:

  1. Mock parity & header — 1.5 days
  2. Control panel — 2 days
  3. Program panel — 1.5 days
  4. Console panel — 1 day
  5. Settings shell — 1 day
  6. Polish, feature flag, regression tests — 0.51 day

8. Open questions

  • Do we ship the redesign behind config.ui.layout or a single hard cut? Recommendation: feature flag for one beta release, then default-on.
  • Macros above 8: drawer in Control or move overflow into Settings → Macros? Recommendation: Settings owns the master list; Control surfaces the first 8 (configurable).
  • Do we want a "Pin to Control" affordance (e.g. let the user pin a chosen indicator into the status strip)? Defer to a later iteration.
  • Spindle override / feed override sliders are not in the V09 mock. Decide whether they live in the status strip or as a quick-action drawer pulled from the bottom edge. Recommendation: drawer triggered by the spindle KPI tile.