frontend-development #1

Merged
fragmented merged 13 commits from frontend-development into main 2026-05-13 07:59:35 +00:00
Owner
No description provided.
Captures the design for replacing compile-time keymaps and LED
behavior with a profile model edited via a local desktop
configurator. Covers the firmware data model, boot dispatcher,
multi-key keyboard report, palette LED mode (static + shift),
codegen contract for generated_config.h, frontend layout
descriptor schema with the populated v5 hardware mapping, SVG
preview UX, and the desktop application's responsibilities
(bundled firmware source, first-run toolchain provisioning,
USB-triggered BOOTSEL, UF2 flashing).
Hardware:
- v5 Pico mapping: 9 switches (BT-A/B/C/D, FX-L/R, EX, dead, START)
  with 32 WS2812B LEDs on a single chain. SW_GPIO_SIZE, LED_GPIO_SIZE,
  WS2812B_LED_SIZE updated in src/controller_config.h.
- Boot dispatch (boot_dispatch.{c,h}): startup-eligible trigger
  switches select a non-default profile or apply an LED override
  at boot time. NO_TRIGGER_SW sentinel + profileTriggers /
  ledOverrides tables drive the boot-time choice.

Encoders:
- encoder_dispatch.{c,h}: pure logic that converts quadrature edges
  into HID axis events. Ring buffer in the ISR, dispatch in the HID
  tick. encoder_override.{c,h}: traveling-spot LED effect with
  ENC_OVERRIDE_HOLD_US (500 ms) fade after last edge.
- HID-descriptor-aware: Joystick profiles can use JoyX/JoyY,
  Keyboard profiles can use MouseX/MouseY/Scroll. None disables.
- Host-testable: firmware-tests/ runs the same encoder_dispatch
  module against a non-RP2040 target.

LED rework:
- WS2812B chain with per-button pressed-color overrides
  (palette.buttonPressed) and per-encoder color overrides on the
  perimeter LED ranges.
- LED modes: Palette / Cycle / Turbocharge / Off / LowBrightness.
  Per-profile ledMode + boot-time LED overrides composing in a
  defined order (palette -> pressed -> mode effect -> encoder ->
  dim -> off).
- Palette modes: Static + Shift (32-step rotation every
  shiftStepMs).

Keyboard report:
- keyboard_report.{c,h}: NKRO-aware report builder; modifier
  routing for HID_KEY_CONTROL_LEFT etc.

Generated config:
- src/generated_config.h is emitted by the configurator's codegen.
- profile_t, profile_trigger_t, led_override_t structs declared in
  controller_config.h; arrays sized to MAX_PROFILES / MAX_LED_OVERRIDES
  with sentinel padding rows.

Tests:
- firmware-tests/ uses cmake to build encoder_dispatch + palette_math
  modules against the host toolchain and asserts on HID-report shapes.

Documentation:
- documentation/agentic-context/firmware-hardware-checklist.md
- implementation-plans/ entries for encoder remapping, LED rework
  detail, LED model rework, LED debug test, firmware refactor,
  firmware hardware verification, firmware-bootsel-usb-deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Svelte 5 + TypeScript + Vite frontend for editing per-profile
keymaps, palettes, encoder modes, and LED overrides. Renders the
controller layout, emits the firmware's generated_config.h, and
persists state to localStorage.

UI components:
- App.svelte: topbar (Import / Export / Build & Flash / Reset),
  view tabs (Profiles / Boot Triggers / Generated Config), modal
  host (keymap, profile-meta, encoder, build-flash, confirm/alert,
  JSON import/export).
- Schematic.svelte: SVG layout renderer; inline LED color picker;
  button + encoder edit entry points.
- KeymapEditor / EncoderEditor / ProfileManager / TriggersView:
  scoped editors.
- Dialog.svelte + JsonDialog.svelte: in-app modals replacing
  browser primitives (browser confirm/alert/file pickers fail
  inside embedded WebViews).
- BuildView.svelte: build-flash flow state machine (checking ->
  missing-tools | building -> awaiting-bootsel -> flashing ->
  done | error).

Domain model:
- types.ts: ProfileSet / Profile / Palette / ProfileTrigger /
  LedOverride / Rgb + LedMode / EncoderMode / HidDescriptor /
  PaletteMode enums + factories (createRgbArray,
  DEFAULT_BUTTON_PRESSED, makeEmptyProfile, makeEmptyPalette).
- hidKeys.ts: single declarative KEYCODES table driving both
  KEYCODE_TO_MACRO and KEYCODE_TO_LABEL.
- layout.ts: validateLayout (exhaustive structural check on
  buttons / encoders / ledZones / leds).
- colorUtils.ts: hexToRgb / rgbToHex / rgbIsBlank sentinel.

State + persistence:
- profileStore.svelte.ts: Svelte 5 reactive store with localStorage
  persistence + forward-migration of older persisted shapes
  (missing encoderModes, wrong-length arrays, all-black
  buttonPressed).
- defaultProfileSet.ts: mirrors firmware's committed default
  (joystick Default + keyboard variant + BT-A trigger).

Validation + import:
- validation.ts: isProfileSet / isProfile shape guards +
  validateProfileSet / validateProfile content rules (sentinel
  termination, encoder-mode/descriptor compatibility, trigger
  uniqueness, RGB range).
- importService.ts: parseImport(text, layout) returns a
  discriminated ImportResult (profile-set | profile | error).
  Single entry point for textarea paste, file pick, and legacy
  migration.

Codegen:
- codegen.ts: emitGeneratedConfig(set, layout) produces
  generated_config.h with sized arrays + sentinel padding. Caps
  each profile's keymap to layout.buttons.length so older persisted
  profiles can't overflow the firmware's keymap array.
- labels.ts: labelForKeycode / labelForKeymapEntry /
  labelForJoystickButton.

Dialog channels:
- dialog.ts: createDialogChannel factory drives confirm / alert /
  exportJson / importJson; one handler registration per channel.

Profile mutations:
- profileMutations.ts: appendProfile / deleteProfile / assignRole
  as pure mutators called inside store.update. AssignedRole
  discriminated union owns the trigger-assignment vocabulary.

Tests:
- *.test.ts files for types, validation, codegen, profileStore,
  importService, profileMutations, layout, defaultProfileSet,
  colorUtils, hidKeys, labels, dialog. Outcome-focused; storage
  is a W3C-shaped fake; migration tests use JSON literals.

Module index at configurator/src/lib/README.md.

Design + plans:
- documentation/agentic-context/agent-designs/
  2026-05-10-configurable-profiles-design.md drove the profile
  model.
- implementation-plans/ entries for frontend-editing,
  frontend-skeleton, frontend-ux-fixes, and the post-Tauri
  configurator-hardening-cleanup pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rust crate at configurator/src-tauri/ wraps the Vite-built Svelte
bundle in a native Tauri 2 window. Owns the menu bar, native file
dialogs, build & flash command surface, and disk persistence.

Rust commands (src-tauri/src/commands/):
- firmware.rs: check_toolchain (probes cmake + arm-none-eabi-gcc,
  parses version tokens), get_repo_path (walks the executable's
  parent chain to find build.sh + configurator/ — handles dev,
  packaged .app, and root-level cases), build_firmware (writes
  generated_config.h, spawns build.sh firmware via
  tokio::process::Command, streams stdout/stderr line-by-line via
  the build-log Tauri event, strips ANSI SGR codes so the WebView's
  <pre> shows clean text).
- flash.rs: detect_controller scans per-OS BOOTSEL mount candidates
  (cfg-based — /Volumes on macOS, /run/media + /media + HOME on
  Linux, drive letters on Windows) and requires INFO_UF2.TXT marker
  to confirm a real Pico mount; flash_uf2 copies the UF2 with a
  re-stat guard against stale mount paths.
- config.rs: save_config / load_config round-trip the ProfileSet
  through ~/.svdx-pico/config.json.
- All commands return typed thiserror enums (FirmwareError /
  FlashError / ConfigError) that serialize as Display strings to
  the JS side.

Menu (src-tauri/src/menu.rs):
- MenuBuilder + SubmenuBuilder construct App / Edit / Window
  submenus with Tauri's predefined items (about, hide,
  hide_others, show_all, quit, undo, redo, cut, copy, paste,
  select_all, minimize, maximize, fullscreen).
- macOS maps the predefined items to AppKit selectors via the
  responder chain so Cmd+Q/V/C/X/A flow to the focused WebView
  input.
- Linux / Windows render the same items as an in-window menu bar.

Plugin registration (lib.rs):
- tauri_plugin_dialog + tauri_plugin_fs for the JS-side helpers.
- tauri_plugin_log in debug builds.

JS bridge:
- native.ts: thin wrapper over @tauri-apps/api/core invoke +
  @tauri-apps/plugin-dialog + @tauri-apps/plugin-fs. Same public
  surface as before (checkToolchain, buildFirmware, detectController,
  flashUf2, saveConfig, loadConfig, pickJsonFile, saveJsonFile)
  so the Svelte side is untouched.
- isAvailable() probes window.__TAURI_INTERNALS__.
- buildFirmware listens for build-log events and unsubscribes in
  a finally block so the listener cleans up even on rejection.
- buildFlashController.ts: prepareBuildFlash(set, layout) calls
  the get_repo_path Rust command + emitGeneratedConfig and returns
  a typed bundle for BuildView to consume.
- main.ts: calls Neutralino.init equivalent — Tauri's IPC is
  implicit, so main.ts is just the Svelte mount.

Capabilities:
- src-tauri/capabilities/default.json grants core:default +
  core:event listen/unlisten + dialog open/save + fs read-text/
  write-text with $HOME/.svdx-pico/** + $DESKTOP / $DOCUMENT /
  $DOWNLOAD / $TEMP scopes. Tight enough that the app can't
  reach ~/.ssh or ~/.aws, broad enough that user-selected files
  via the dialog plugin still work.

Tests (native.test.ts, buildFlashController.test.ts):
- vi.mock for @tauri-apps/api/core invoke + @tauri-apps/api/event
  listen + @tauri-apps/plugin-dialog + @tauri-apps/plugin-fs.
  Outcome-only assertions; the listener lifecycle is verified
  via the mock unlisten function getting called even on rejection.

Plan:
- documentation/agentic-context/implementation-plans/
  2026-05-12-tauri-migration.md captures the 5-phase migration
  design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Repo: docs + Forgejo CI + build.sh orchestrator
Some checks failed
Build artifacts / Desktop bundle (linux) (push) Has been cancelled
Build artifacts / Desktop bundle (windows) (push) Has been cancelled
Build artifacts / Desktop bundle (macos) (push) Has been cancelled
Build artifacts / Firmware UF2 (push) Has been cancelled
check / Firmware host tests (push) Has been cancelled
check / Frontend (Svelte + TS) (push) Has been cancelled
check / Firmware host tests (pull_request) Has been cancelled
check / Frontend (Svelte + TS) (pull_request) Has been cancelled
04b5f45b4f
Build orchestrator:
- build.sh at repo root: detects OS, auto-installs cmake / ninja /
  arm-none-eabi-gcc / Node / pnpm if missing (brew on macOS,
  apt/pacman/dnf on Linux), then routes to firmware-only,
  desktop-only, or both via subcommands.
- Subcommands: firmware, app (pnpm tauri:build + open the .app
  detached), app-dev (pnpm tauri:dev), clean (wipe build/ +
  src-tauri/target/), all.
- ensure_rust precheck — exits with a friendly rustup install hint
  if cargo isn't on PATH.

Documentation tree:
- documentation/README.md — master TOC hub.
- documentation/architecture/ — overview, IPC bridge, build-flash
  flow. Includes pastel-dark Mermaid diagrams: system-context
  flowchart, build-and-flash sequence diagram, BuildView state
  machine.
- documentation/models/ — profile (classDiagram), layout JSON
  schema, encoder-mode × HID-descriptor compatibility matrix.
- documentation/configuration.md — generated_config.h +
  ~/.svdx-pico/config.json schemas.
- documentation/development/ — setup, running, testing.
- documentation/firmware/ — Pocket SDVX Pico v5 hardware map, LED
  model render order, encoder dispatch pipeline.
- agentic-context/implementation-plans/: ci-distribution +
  ci-smoke-test (CI design) + audit-docs-ci-squash (this turn's
  plan).
- Root README links the doc tree + calls out the rustup
  prerequisite for the desktop app.

Configurator package config:
- configurator/package.json, pnpm-lock.yaml, vite.config.ts,
  svelte.config.js, tsconfig.{app,node,}.json, .gitignore.
- Deps: @tauri-apps/api + plugin-dialog + plugin-fs runtime;
  @tauri-apps/cli + svelte + vite + vitest + svelte-check + tsx
  + @testing-library/svelte + jsdom dev.
- Scripts: dev / build / preview / check / test / test:run /
  test:firmware / tauri / tauri:dev / tauri:build / clean.

Forgejo Actions (.forgejo/workflows/):
- build.yaml: firmware job on Ubuntu (apt-installs the toolchain,
  builds the UF2, uploads as artifact); desktop matrix over
  macos-latest / ubuntu-latest / windows-latest (each runs
  pnpm tauri:build with cargo target cache, uploads the per-OS
  bundle as an artifact). Triggers on push to main / sprint
  branches + on annotated tags.
- check.yml: pre-existing lint / type-check workflow on push +
  PR + manual dispatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
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
tools/sdvx-pico!1
No description provided.