Skip to content

Feat/web client api parity#44

Open
joschaschmiedt wants to merge 16 commits into
mainfrom
feat/web-client-api-parity
Open

Feat/web client api parity#44
joschaschmiedt wants to merge 16 commits into
mainfrom
feat/web-client-api-parity

Conversation

@joschaschmiedt

Copy link
Copy Markdown
Contributor

No description provided.

joschaschmiedt and others added 16 commits June 30, 2026 10:12
…t, generic setters)

Complete TODO #1: the web client now matches the Python client / proto surface.

Stimuli: add the remaining generic setters — setName, setDrawMode (ShapeDrawMode
string-union → proto enum), setOutlineColor, setOutlineWidth, and draw-order
(bringToFront / sendToBack / swapDrawOrder; server returns NotSupported for now,
see #43).

conn.vtl: add list(), reusing a toVtlLineView mapper extracted from snapshot.ts.

conn.animations: new client mirroring vstimd.animations — create (all 7 types),
arm / disarm / delete / list / query, with *Frames|*Ms conversion via a cached
server frame rate. VtlLine addressing reuses vtl.ts's vtlLineHandle.

conn.config: new client — list / load / save / retrieve / upload.

All proto types stay private; the public surface is hand-written domain types and
string-union enums, matching the existing grating/vtl pattern. e2e coverage added
for each (draw-order asserts the documented NotSupported gap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
The animation type tag appeared in three forms: the server proto `type_name`
(PascalCase, from list), the proto oneof field (camelCase in TS / snake_case in
Python, from query), and the serde config-file enum tag (PascalCase variant
name). Since the tag is persisted to config files, divergence between what one
client writes and another reads is a real round-trip hazard.

Make the Rust enum variant name the single canonical tag and pass it through
everywhere:

- proto: add `type_name` to QueryAnimationResponse so query carries the same
  string as list (no client-side derivation from the oneof).
- server: populate it from `Animation::type_name()`, and add a guard test
  asserting `type_name()` equals the serde externally-tagged variant key — so it
  can never drift from what's written to / read from config files.
- web + python clients: `query()` now passes the server `type_name` through
  verbatim, matching `list()`. The web client drops the camelCase mapping table;
  `AnimationTypeName` mirrors the server's PascalCase set for autocomplete only.

All three components and the config file now agree on one tag (e.g.
"FlashForNFrames", "MoveAlongPath2D"). Verified: Rust guard test, web e2e (13),
python e2e-null (121).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
…n (config v2)

Stimuli and animations now serialize the same way: internally-tagged with a
`type` discriminator, instead of three different shapes (shapes were
double-nested `{"Shape":{"Rect":{…}}}`, grating/text single-level, animations
flat externally-tagged). A schema-driven UI and the planned JSON Schema both want
one uniform tagged-union form.

Server:
- Flatten the `Stimulus` enum: `Shape(ShapeStimulus)` → flat
  `Rect|Ellipse|Circle|Grating|Text`; remove `ShapeStimulus`. Shape-specific
  dispatch (tessellation, accessors) moves up to `Stimulus` via `is_shape()` /
  `shape_appearance()`.
- Factor the shared shape params (flags/transform/appearance) into `ShapeCommon`,
  `#[serde(flatten)]`-ed into each shape struct — DRY in Rust, flat in JSON, and a
  reusable component for the JSON Schema.
- `#[serde(tag = "type")]` on both `Stimulus` and `Animation`.
- Bump CONFIG_VERSION 1 → 2 (clean break; no migration). Check the version before
  the full parse so older files fail with a clear version error, not a serde error.

Tests:
- New v2 reference fixture; config_compat asserts v2 loads and v1 is rejected.
- config_roundtrip proves the internally-tagged + flatten form round-trips.
- python config e2e expects version 2.

Verified: full Rust test suite, web e2e (13), python e2e-null (121).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
…Copy clone)

Pre-existing clippy findings surfaced after the config refactor:
- grating tests: replace 3.14 phase values (flagged as approx PI) with 1.5.
- vtl_state::vblank_mask: collapse nested if via Option::filter.
- test setup: struct-update for SceneConfig, drop clone on Copy Color.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
First step-2 UI panel: a Trigger Lines panel reading snapshot.vtlLines, showing
each line's name/bank:bit, direction, and live level (lit indicator), with a
toggle button for input lines (conn.vtl.toggleInput). Wired into App beside the
stimuli panel. Playwright smoke test covers register → render → toggle low→high.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
Second step-2 panel. Animations are not in the SceneSnapshot stream, so the panel
polls conn.animations.list() every 500ms (and refreshes after each action) and
shows name / canonical type / state, with arm·disarm·delete buttons gated on
state. Wired into App. Playwright covers create → list → arm (state leaves idle);
beforeEach now also clears animations for isolation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
…delete-all)

Third step-2 panel. Adds the missing system client methods setAllEnabled and
setDeferredMode (parity with the Python client), then a System panel with a
background colour picker (from snapshot.serverInfo.background), show/hide-all,
deferred-batch begin/apply/cancel, and a guarded delete-all. Photodiode is
omitted — it has no runtime command yet (config/animation-only). Playwright
covers Hide all → stimulus checkbox unchecks via the snapshot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
benches/tess.rs referenced the pre-refactor stimulus API (DiscStimulus,
ShapeStimulus, tessellate_stimulus) and no longer compiles. Remove it and its
Cargo.toml stanza; it needs a rewrite against the current API.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
The VTL list (and thus the snapshot) now enumerates every bit in the configured
banks, not just named lines, so any line can be observed/triggered for debugging
(inputs simulate the hardware bridge; outputs override the animation-driven level).

Web VtlPanel is rewritten as a clickable per-bank binary view: each bank renders
its 64 bits as 1/0 cells (high = green), MSB-first in bytes; clicking toggles that
line via toggleInput/toggleOutput. Named bits are underlined with a name tooltip.
Playwright updated to toggle a named bit in the grid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
The bank view now defaults to binary. Adds a "Manual fire" control to the VTL
group: pick In/Out, bank, and bit, then drive it — inputs fire a rising/falling
edge (set_input_bit + set_input_rise/fall, simulating the hardware bridge),
outputs set the level via set_staged_bit (overriding the animation-driven value).
This makes any line triggerable for debugging without needing a registered name.

vtl_group no longer holds a long-lived owner borrow (state is read inline and the
names are cloned) so the output writes can take &mut VtlState.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
The create-animation dialog could only set the type-reactive trigger
(EnableOnEdge/Couple); there was no way to gate a Flash/Flicker/Move on a VTL
edge or pulse a line on completion, so trigger-driven animations couldn't be
wired up. Add optional "Start on VTL edge" (bank/bit + rising/falling →
config.start_trigger) and "Pulse VTL line on completion" (bank/bit →
FinalAction::FINAL_ACTION_TRIGGER_LINE + final_action_trigger_line). Bank/bit
entry so any line works, named or not — matching the VTL panel change.

Engine support is already covered by the animation integration tests
(flash_with_start_trigger_stays_armed_until_edge, final_action_trigger_line_*).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011t4AuaVFXikaAAFt7oCZ1F
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant