[PR #260] Redesign the web panel as a Svelte + Skeleton dashboard #264

Open
opened 2026-10-22 05:08:25 +00:00 by roman · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/safijari/openpuck/pull/260
Author: @adamrpostjr
Created: 8/30/2026
Status: 🔄 Open

Base: main ← Head: web-redesign


📝 Commits (10+)

  • a49ff12 Add Svelte + Skeleton scaffold for the web panel redesign
  • 8e423cd Wire up WebUSB transport and port the Feel section
  • 114fa1d Port the Mapping and Desktop sections
  • 756f4e5 Make the lizard editor read-first instead of a wall of selects
  • a45c792 Port in-browser firmware flashing and the release list
  • 6b5a455 Fix a layering violation and mark demo data in the UI
  • c1db039 Wire the Device menu actions, which were inert
  • 40837df Port backup and clone
  • a10d961 Port the diagnostics: capture, flight recorder, hang log, stability test
  • dd6dbc9 Make the beta gate on Firmware actually gate

📊 Changes

99 files changed (+19764 additions, -2610 deletions)

View changed files

➕ .github/workflows/panel.yml (+65 -0)
📝 AGENTS.md (+34 -0)
📝 docs/BUILD_AND_DEPLOY.md (+34 -15)
➕ docs/apple-touch-icon.png (+0 -0)
➕ docs/assets/Diagnostics-CNy-WTPw.js (+12 -0)
➕ docs/assets/Firmware-C8NOdfZi.js (+9 -0)
➕ docs/assets/Panel-DVJ_5r9G.js (+3 -0)
➕ docs/assets/Sniffer-BJJtISUE.js (+3 -0)
➕ docs/assets/index-BBXZdF5L.css (+2 -0)
➕ docs/assets/index-DQaQRYOM.js (+49 -0)
➕ docs/assets/refresh-cw-Cxz2buiS.js (+1 -0)
➕ docs/icon-192.png (+0 -0)
➕ docs/icon-512.png (+0 -0)
➕ docs/icon-maskable-512.png (+0 -0)
➕ docs/icon.svg (+10 -0)
📝 docs/index.html (+17 -2328)
➕ docs/legacy-sniffer.html (+270 -0)
➕ docs/legacy.html (+2329 -0)
➕ docs/manifest.webmanifest (+1 -0)
➕ docs/registerSW.js (+1 -0)

...and 79 more files

📄 Description

Closes #259.

Rebuilds the WebUSB panel as a Svelte + Skeleton dashboard. Every feature,
readout and line of explanatory copy from the current panel is carried over —
this is a reorganisation, not a trim.

The five things from #259

Logs in a floating, movable window ✅ Logs, Capture and the loop trail are draggable, resizable, non-modal panels
Sidebar: menu left, link info right ✅ Section rail on the left, live per-controller telemetry on the right
Better UX for button mapping ✅ Per-type tabs, and the desktop map now reads as sentences
Svelte, Skeleton, Tailwind, Lucide ✅ Svelte 5 + Skeleton 5 + Tailwind 4 + Lucide
Port the sniffer into the main interface ✅ A rail section; sniffer.html redirects so the old URL still works

Before / after

Same puck, same window, both connected.

Before — one 980px column, 3712px tall:

before

After — link status always visible, no scrolling to reach it:

after

What was actually wrong

Measured on the live page with a real puck attached, in a 1556×785 window:

  • The log was 940×34px, 3880px down a 3934px page — the last thing on it.
  • 5 screens of scrolling, all in one column.
  • 37% of the window was empty margin (max-width:980px); 62% on a 2560px display.
  • Link status was 816px tall and scrolled away while you changed anything.
  • Switch Pro motion was 97px for one dropdown; Rumble 97px for three.
  • A third of the page was permanent prose — 27 notes, ~7,460 chars against 23,780 chars of markup.
  • On a v17 puck, three cards rendered ~200px of "your firmware is too old".

The layout

Content split by how it is used, rather than interleaved:

  • Monitor — repolls every 600ms, so it never scrolls away (right sidebar).
  • Configure — seven rail sections replacing 15 stacked cards.
  • Diagnose — occasional or destructive, kept out of the way.

The old Link status card held two different jobs; the glanceable per-controller
stats moved to the sidebar and the deep telemetry to Diagnostics. All 27 notes
are kept verbatim, moved into ⓘ popovers.

modes

Modes are cards with capability badges instead of eleven identical buttons.

desktop

The desktop map reads as sentences — 37px rows instead of ~70px, and only the
row being edited renders controls. New: a row that would be dropped on save says
so, in place. Previously those vanished silently with a note in the log.

diagnostics

Live streams float; post-mortem dumps stay docked.

ReversePuck and the sniffer

The dongle reshapes the panel rather than showing puck controls that do nothing:

dongle

The sniffer is now a section, keeping its own connection since it is a separate board:

sniffer

Four bugs found while porting

Writing tests around the reverse-engineered code turned up real defects, all of
which exist in the current panel:

  1. Sniffer timestamps went negative past ~35 minutes. The frame time is
    acc[3] | acc[4]<<8 | acc[5]<<16 | acc[6]*16777216. The top byte is
    multiplied to dodge the sign bit << 24 would set, but | coerces to int32
    and undoes it — so any capture past 2^31 µs sorted before every earlier
    frame. Fixed, with a test at 0xff.
  2. Panel updates were gated on the wrong version in my first pass — it is
    status protocol v15, not v16. A puck on exactly v15 would have been locked
    out. Now caps.panelUpdate, tested either side of the boundary.
  3. The beta gate was decorative. The Firmware tab showed a "beta" label but
    never blocked; the warning you are meant to accept could be walked past.
  4. legacy.html became unreachable once the service worker installed, which
    defeated keeping it for comparison. Fixed with a fallback denylist.

Structure

40 files, largest 351 lines, against the current 2,329-line single file.

protocol/  →  usb/  →  state/  →  components, sections, panels  →  App

Verified acyclic with no upward imports. protocol/ is pure — no DOM, no
Svelte, no USB — and is the layer under test. applyBlob is the only function
that changed shape
: it decoded byte offsets and wrote to the DOM in one
pass, so the decode half became a pure parseBlob(). Every offset, length guard
and version gate is unchanged. The transport, UF2 parser, CRC32, update
protocol, lizard map and backup format are lifted as-is, comments included.

116 tests, all on the protocol layer — the first thing here testable without
a puck on the desk. CI runs format, types, tests and build, and rebuilds docs/
to catch a source change pushed without one.

What you should push back on

  • This repo gains a Node toolchain. Touching the UI now needs
    npm install, and docs/index.html becomes generated — git log shows you
    editing it directly as part of feature work, so this is a real change to your
    workflow, not just mine.
  • Build output is committed to docs/, so UI changes carry a bundle diff.
    That keeps Pages working with no settings change; an Actions deploy would be
    cleaner but needs repo access I do not have.
  • 106 kB gzipped first load, against a 148 kB single file today. Sections
    that are large and rarely opened load on demand.
  • docs/legacy.html keeps the current panel for one release so a suspicious
    reading can be compared side by side.

Testing status — please read

Reading from a puck is verified on real hardware. The transport, the status
blob decode, the 600ms poll and the lizard map dump (26 bindings off my own
puck) all work; the screenshots above are live data, not mocks.

Every write path is verified only by tests and fixtures. Mode switching,
config writes, the backup export→import round trip and firmware flashing have
not touched hardware. I did not want to exercise a flash path against someone
else's device without saying so first. If you would rather that happen before
this is considered, I will do it and report back.

Also worth a look: web/README.md is written for someone who has not used
Svelte or Skeleton, since that describes most of this repo's contributors.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/safijari/openpuck/pull/260 **Author:** [@adamrpostjr](https://github.com/adamrpostjr) **Created:** 8/30/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `web-redesign` --- ### 📝 Commits (10+) - [`a49ff12`](https://github.com/safijari/openpuck/commit/a49ff1271b11d3f6ef7038f42a7ca6b22df4260b) Add Svelte + Skeleton scaffold for the web panel redesign - [`8e423cd`](https://github.com/safijari/openpuck/commit/8e423cdcbfc1daad7829f8595ce04b2c60867c03) Wire up WebUSB transport and port the Feel section - [`114fa1d`](https://github.com/safijari/openpuck/commit/114fa1dbd7caf102d1285b5bacfabf0e1426502e) Port the Mapping and Desktop sections - [`756f4e5`](https://github.com/safijari/openpuck/commit/756f4e5abf48386f5d9c546d20f384650a771d78) Make the lizard editor read-first instead of a wall of selects - [`a45c792`](https://github.com/safijari/openpuck/commit/a45c7923f6b3bab5344d2b23fb3f1da8bb51fcd9) Port in-browser firmware flashing and the release list - [`6b5a455`](https://github.com/safijari/openpuck/commit/6b5a455a4de5529b5da2c6f34fbb44f77de48687) Fix a layering violation and mark demo data in the UI - [`c1db039`](https://github.com/safijari/openpuck/commit/c1db03942c2cefa8d8529d6da90f795ae458700d) Wire the Device menu actions, which were inert - [`40837df`](https://github.com/safijari/openpuck/commit/40837dfca2a706d6eca7f8768c7a37b7b6f71db8) Port backup and clone - [`a10d961`](https://github.com/safijari/openpuck/commit/a10d961115c22ed97851b8b11ac8b634d0891aa6) Port the diagnostics: capture, flight recorder, hang log, stability test - [`dd6dbc9`](https://github.com/safijari/openpuck/commit/dd6dbc97bb85bbcef10fe1a35fa0ab1949730edb) Make the beta gate on Firmware actually gate ### 📊 Changes **99 files changed** (+19764 additions, -2610 deletions) <details> <summary>View changed files</summary> ➕ `.github/workflows/panel.yml` (+65 -0) 📝 `AGENTS.md` (+34 -0) 📝 `docs/BUILD_AND_DEPLOY.md` (+34 -15) ➕ `docs/apple-touch-icon.png` (+0 -0) ➕ `docs/assets/Diagnostics-CNy-WTPw.js` (+12 -0) ➕ `docs/assets/Firmware-C8NOdfZi.js` (+9 -0) ➕ `docs/assets/Panel-DVJ_5r9G.js` (+3 -0) ➕ `docs/assets/Sniffer-BJJtISUE.js` (+3 -0) ➕ `docs/assets/index-BBXZdF5L.css` (+2 -0) ➕ `docs/assets/index-DQaQRYOM.js` (+49 -0) ➕ `docs/assets/refresh-cw-Cxz2buiS.js` (+1 -0) ➕ `docs/icon-192.png` (+0 -0) ➕ `docs/icon-512.png` (+0 -0) ➕ `docs/icon-maskable-512.png` (+0 -0) ➕ `docs/icon.svg` (+10 -0) 📝 `docs/index.html` (+17 -2328) ➕ `docs/legacy-sniffer.html` (+270 -0) ➕ `docs/legacy.html` (+2329 -0) ➕ `docs/manifest.webmanifest` (+1 -0) ➕ `docs/registerSW.js` (+1 -0) _...and 79 more files_ </details> ### 📄 Description Closes #259. Rebuilds the WebUSB panel as a Svelte + Skeleton dashboard. Every feature, readout and line of explanatory copy from the current panel is carried over — this is a reorganisation, not a trim. ## The five things from #259 | | | |---|---| | Logs in a floating, movable window | ✅ Logs, Capture and the loop trail are draggable, resizable, non-modal panels | | Sidebar: menu left, link info right | ✅ Section rail on the left, live per-controller telemetry on the right | | Better UX for button mapping | ✅ Per-type tabs, and the desktop map now reads as sentences | | Svelte, Skeleton, Tailwind, Lucide | ✅ Svelte 5 + Skeleton 5 + Tailwind 4 + Lucide | | Port the sniffer into the main interface | ✅ A rail section; `sniffer.html` redirects so the old URL still works | ## Before / after Same puck, same window, both connected. **Before** — one 980px column, 3712px tall: ![before](https://raw.githubusercontent.com/adamrpostjr/openpuck/pr-assets/01-before-legacy-panel.jpg) **After** — link status always visible, no scrolling to reach it: ![after](https://raw.githubusercontent.com/adamrpostjr/openpuck/pr-assets/02-after-overview.jpg) ### What was actually wrong Measured on the live page with a real puck attached, in a 1556×785 window: - **The log was 940×34px, 3880px down a 3934px page** — the last thing on it. - **5 screens of scrolling**, all in one column. - **37% of the window was empty margin** (`max-width:980px`); 62% on a 2560px display. - **Link status was 816px tall** and scrolled away while you changed anything. - Switch Pro motion was **97px for one dropdown**; Rumble 97px for three. - **A third of the page was permanent prose** — 27 notes, ~7,460 chars against 23,780 chars of markup. - On a v17 puck, three cards rendered ~200px of "your firmware is too old". ## The layout Content split by how it is used, rather than interleaved: - **Monitor** — repolls every 600ms, so it never scrolls away (right sidebar). - **Configure** — seven rail sections replacing 15 stacked cards. - **Diagnose** — occasional or destructive, kept out of the way. The old Link status card held two different jobs; the glanceable per-controller stats moved to the sidebar and the deep telemetry to Diagnostics. All 27 notes are kept **verbatim**, moved into `ⓘ` popovers. ![modes](https://raw.githubusercontent.com/adamrpostjr/openpuck/pr-assets/03-modes.jpg) Modes are cards with capability badges instead of eleven identical buttons. ![desktop](https://raw.githubusercontent.com/adamrpostjr/openpuck/pr-assets/04-desktop-bindings.jpg) The desktop map reads as sentences — 37px rows instead of ~70px, and only the row being edited renders controls. New: a row that would be dropped on save says so, in place. Previously those vanished silently with a note in the log. ![diagnostics](https://raw.githubusercontent.com/adamrpostjr/openpuck/pr-assets/05-diagnostics-logs.jpg) Live streams float; post-mortem dumps stay docked. <details><summary>ReversePuck and the sniffer</summary> The dongle reshapes the panel rather than showing puck controls that do nothing: ![dongle](https://raw.githubusercontent.com/adamrpostjr/openpuck/pr-assets/06-dongle.jpg) The sniffer is now a section, keeping its own connection since it is a separate board: ![sniffer](https://raw.githubusercontent.com/adamrpostjr/openpuck/pr-assets/07-sniffer.jpg) </details> ## Four bugs found while porting Writing tests around the reverse-engineered code turned up real defects, all of which exist in the current panel: 1. **Sniffer timestamps went negative past ~35 minutes.** The frame time is `acc[3] | acc[4]<<8 | acc[5]<<16 | acc[6]*16777216`. The top byte is multiplied to dodge the sign bit `<< 24` would set, but `|` coerces to int32 and undoes it — so any capture past 2^31 µs sorted before every earlier frame. Fixed, with a test at `0xff`. 2. **Panel updates were gated on the wrong version** in my first pass — it is status protocol v15, not v16. A puck on exactly v15 would have been locked out. Now `caps.panelUpdate`, tested either side of the boundary. 3. **The beta gate was decorative.** The Firmware tab showed a "beta" label but never blocked; the warning you are meant to accept could be walked past. 4. **`legacy.html` became unreachable** once the service worker installed, which defeated keeping it for comparison. Fixed with a fallback denylist. ## Structure 40 files, largest 351 lines, against the current 2,329-line single file. ``` protocol/ → usb/ → state/ → components, sections, panels → App ``` Verified acyclic with no upward imports. `protocol/` is pure — no DOM, no Svelte, no USB — and is the layer under test. **`applyBlob` is the only function that changed shape**: it decoded byte offsets *and* wrote to the DOM in one pass, so the decode half became a pure `parseBlob()`. Every offset, length guard and version gate is unchanged. The transport, UF2 parser, CRC32, update protocol, lizard map and backup format are lifted as-is, comments included. **116 tests**, all on the protocol layer — the first thing here testable without a puck on the desk. CI runs format, types, tests and build, and rebuilds `docs/` to catch a source change pushed without one. ## What you should push back on - **This repo gains a Node toolchain.** Touching the UI now needs `npm install`, and `docs/index.html` becomes generated — `git log` shows you editing it directly as part of feature work, so this is a real change to your workflow, not just mine. - **Build output is committed** to `docs/`, so UI changes carry a bundle diff. That keeps Pages working with no settings change; an Actions deploy would be cleaner but needs repo access I do not have. - **106 kB gzipped** first load, against a 148 kB single file today. Sections that are large and rarely opened load on demand. - `docs/legacy.html` keeps the current panel for one release so a suspicious reading can be compared side by side. ## Testing status — please read **Reading from a puck is verified on real hardware.** The transport, the status blob decode, the 600ms poll and the lizard map dump (26 bindings off my own puck) all work; the screenshots above are live data, not mocks. **Every write path is verified only by tests and fixtures.** Mode switching, config writes, the backup export→import round trip and firmware flashing have not touched hardware. I did not want to exercise a flash path against someone else's device without saying so first. If you would rather that happen before this is considered, I will do it and report back. Also worth a look: `web/README.md` is written for someone who has not used Svelte or Skeleton, since that describes most of this repo's contributors. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No labels
pull-request
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
safijari/openpuck#264
No description provided.