Three pieces close out the editor's main gaps:
F1 — backend includes raw fields in programs/get response:
_program_to_fields() serialises a Program record into the same
field dict the editor form consumes. Round-trips through
programs/write are now lossless (fetch → edit → write produces
byte-identical wire output if no fields changed). The old TODO
in _fetchProgramFields was about exactly this — the frontend
was seeding from sensible defaults rather than real values
because the wire didn't carry raw fields. Now it does.
Verified by a new round-trip test: read slot 42, write the same
fields back, assert the encoded wire bytes are identical.
F2 — EVENT program editor:
EVENT records pack a 16-bit event_id into (month<<8 | day).
Editing requires decoding that ID into one of four categories:
* "button" — USER_MACRO_BUTTON, low byte = button index
* "zone" — ZONE_STATE_CHANGE, packed zone + state-change kind
* "unit" — UNIT_STATE_CHANGE, packed unit + on/off
* "fixed" — hand-rolled IDs (phone events, AC power) from
EVENT_AC_POWER_OFF / EVENT_PHONE_RINGING / etc.
TS helpers decodeEventId / encodeEventId / packEventIdIntoFields
mirror the Python helpers in program_engine.py.
UI: category dropdown switches the sub-fields (button picker,
zone+state pair, unit+on/off, fixed-event picker). Each change
re-encodes back to month/day. Existing programs with unrecognised
IDs fall into a "raw" category that shows the literal hex —
user can switch category to redefine.
F3 — YEARLY program editor:
YEARLY records have month + day + hour + minute, no days-bitmask.
The editor now switches on prog_type to pick the right trigger
section: month dropdown (named months), day number input,
hour/minute number inputs.
Editor render path refactored: _renderTriggerSection(draft)
dispatches to _renderTimedTrigger / _renderEventTrigger /
_renderYearlyTrigger by prog_type. _renderActionSection is
shared across all three (command picker + object picker + level%).
Action editing works identically regardless of trigger.
Edit button visibility extended from "TIMED only" to any
program_type in EDITABLE_PROG_TYPES (TIMED / EVENT / YEARLY).
REMARK and clausal chains remain read-only.
Full suite: 648 passed, 1 skipped (up from 647, F1 round-trip test).
Frontend bundle: 56 KB minified (up from 47 KB with EVENT + YEARLY
forms and event-id helpers).