fix(note-arrays): size send paths to real dimensions + harden tests [gate for #1185]#1296
Conversation
Note-array boards crashed with HTTP 500 on every send path that resolved
board geometry via get_dimensions(), which only knows flagship/note and
raises ValueError on "note_array". Reproduced at runtime: POST
/templates/render/live to a note-array board 500s with
"Unknown device type: note_array. ... use resolve_dimensions()".
Fix all send sites to use resolve_dimensions(device_type, notes_wide,
notes_tall):
- /templates/render/live (live editor output)
- /pages/{id}/send (send a page)
- /settings/active-page (set active page + send)
- /displays/{type}/send (send a plugin display)
- /send-message (also de-hardcodes the flagship 22x6 grid)
Test hardening (the existing send tests mocked get_dimensions away, so
they could never have caught this):
- Add a real regression test that sends a live template to a note-array
board WITHOUT mocking the resolver, asserting no 500 and a true 3x60 grid.
- Repoint the 7 send-endpoint tests that patched src.api_server.get_dimensions
to patch resolve_dimensions (get_dimensions is no longer used there).
- Make the two note-array Cloud URL tests hermetic: assert against the
client's configured URL so they pass in the documented `docker exec ...
pytest` dev flow (VESTABOARD_CLOUD_API_URL -> mock), not only in CI.
Also:
- devices.py: guard BoardInstance.from_dict against a JSON-null
note_array_token ((... or "").strip() crashed on None.strip()).
- ci.yml: run the integration-tests/ mock-cloud contract suite, which was
never collected (pyproject testpaths excludes integration-tests/).
Verified in the dev container on origin/next: full Python suite 4102
passed / 11 skipped (was 4099 + 2 failing), integration 25 passed, web
vitest 1166 passed, and a live send to a note-array board now returns 200
and lands a 3x60 grid on the bundled mock Cloud board.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ardInstance TS type
- board-size-indicator.tsx rendered note-array preset names from the raw
English NOTE_ARRAY_PRESETS[].label, leaking English into both the visible
indicator and its screen-reader aria-label for all 13 non-English locales.
Resolve the label via the displaySettings namespace (`presets.${id}`) --
the same localized keys the Settings board-type selector already uses, so
the English output is identical and existing unit tests / E2E are unchanged.
- api.ts: add `schedule_enabled` and `port` to interface BoardInstance so the
TS contract matches what GET /settings/board actually emits (src/devices.py).
Not a data-loss bug -- the Settings UI spreads the received board object on
edit, preserving the runtime fields -- but the contract was incomplete.
Verified: i18n key parity clean across all 14 locales; full web vitest 1166
passed; prettier + eslint clean on changed files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Multi-board scope clarification (resolves flagged item #1): the per-board driving limitations I noted are tracked as separate open epic issues, not gaps in #1185's claims:
#1185 ships the multi-board foundation (per-board settings + schema migration, per-board schedule storage/filtering by |
There was a problem hiding this comment.
No a11y concerns — this PR is a net improvement.
The only web UI change is in web/src/components/board-size-indicator.tsx. It replaces the hardcoded English preset labels that were previously leaking from NOTE_ARRAY_PRESETS[].label into the ariaLabelWithLayout aria-label string with properly localized strings via tSettings(presets.${presetId}).
Verified:
- Preset IDs align with i18n keys. The five IDs in
NOTE_ARRAY_PRESETS(2_wide,4_wide,2_tall,4_tall,2x2_grid) match exactly the keys underdisplaySettings.presetsin the PR head'sen.json. No missing or misspelled keys. boardSizeIndicatorkeys are intact.ariaLabel,ariaLabelWithLayout, andcustomall exist inen.json. TheariaLabelWithLayoutstring ("{rows} rows by {cols} columns, {layout}") produces a coherent screen-reader announcement oncelayoutis filled with the localized preset name.- No hardcoded English strings introduced. Both the visible preset label and the aria-label text are now derived from next-intl.
- All 14 locale files already carry
displaySettings.presets— no locale drift introduced. - Semantics unchanged. The
role="img"+aria-labelpattern on the indicator wrapper was correct before and remains correct; the decorative·separator sits inside the img role and is hidden from AT via the aria-label override.
This is a straightforward i18n fix that also improves the screen-reader experience for non-English users.
Why this PR
This is the human-in-the-loop gate for #1185 (Note Arrays). It targets
next, so merging it updates #1185. Goal: when this merges, #1185 is actually 100% correct for arrays + multi-board, with tests you can trust.I rebuilt the
next(04c4f08b) dev stack from scratch (with the new mock Cloud board on:19200) and verified the feature at runtime — not just by reading tests. That surfaced a ship-blocker the existing tests hid.🔴 Blocker fixed: note-array boards 500 on every send path
get_dimensions()only knowsflagship/noteand raisesValueErroronnote_array. Five send paths called it. Reproduced live:A user cannot send to a note-array board via the live editor,
/pages/{id}/send,/settings/active-page, or/displays/{type}/send. The send-endpoint testspatch("…get_dimensions")and hardcoderows=6, cols=22, so they could never catch it.Fix: all five send sites use
resolve_dimensions(device_type, notes_wide, notes_tall). After the fix the same live send returns 200 and lands a 3×60 grid on the mock.What's in this PR (all verified green)
src/api_server.pyfrom_dictagainst a JSON-nullnote_array_token(None.strip()crash)src/devices.pytests/test_api_extended.pyget_dimensions→resolve_dimensionstests/test_api_coverage.pydocker exec … pytest, not only CI)tests/test_board_client.pyintegration-tests/mock-cloud contract suite in CI (was never collected).github/workflows/ci.ymlweb/src/components/board-size-indicator.tsxschedule_enabled+porttoBoardInstance(server emits them; TS type was incomplete)web/src/lib/api.tsResults: Python
4102 passed / 11 skipped(was4099 + 2 failing), integration25 passed, web vitest1166 passed, prettier/eslint clean.Verification performed against the live
nextbuildX-Vestaboard-Token, body exactly{"characters": grid}with transitions stripped, 3×60 lands,readround-trips, 15s throttle blocks then allows, per-board token isolation holds.board_idfiltering + board-switch isolation), setup wizard, device-mismatch.BoardInstancefields fixed here.✅ Investigated and cleared (were flagged, verified NOT bugs)
schedule.enabled": not a bug.is_schedule_enabled()falls back to the global mirror when there are no boards, and the migration stamps per-boardschedule_enabledwhen boards exist (and never deletes the global value). No upgrade data loss.schedule_enabled/port": not data loss. The Settings UI spreads the received board object ({...b, ...updates}), so runtime fields survive even though the TS type omitted them (now added for honesty).src/main.py:733) and the dashboard preview ignores the sidebar selection (active-page-display.tsx); alsoPUT /settings/active-pageis primary-board-scoped (noboard_id). Looks like later per-board-epic scope, not claimed-broken here.ruff>=0.9.0) → nondeterministic CI format-check. ruff 0.15 reformats 64 src/ files of pre-existing code. Can failruff format --checkfor any new PR tonext. Recommend pinning ruff + a one-time reformat (separate PR).None, so users get a misleading "blank or unreachable" message for an unsupported size.scaled-board-displayfit-mode scaling untested; seam gap not asserted;page-grid-selectorrenders note-array thumbnails at 1×1; plugin note-array cache tests assert onlyfetch_count.🤖 Generated with Claude Code