The panel stream so far carried only quest-completed. This adds the
three remaining documented transitions (§3) so the panel UI can react
to reversals, full days, and nightly misses through the same ONE
publisher:
- core/events.py gains build_quest_missed_event, mirroring the
sweep's hand-built payload exactly (shared _instance_payload shape),
so the API plane publishes the same shape the integration sweep fires.
- api/transitions.py adds the reusable publish helpers the later
uncomplete route (da0226b3) and the API-side missed sweep will call;
payloads are always built by the core builders, never hand-built.
- The panel complete route now evaluates the day-complete rule on an
actual transition, off the completed event's payload and the route's
single clock read; the core builder still decides, so a zero-quest
day and a non-today instance never fire it and a no-op re-complete
publishes nothing.
- Tests drive all three transitions through the real SSE stream
(server-loop scheduling for the not-yet-routed helpers) and assert
every documented payload field.
NestQuestCoordinator.async_refresh holds a per-entry lock so
overlapping service-path refreshes never interleave; the queued pass
always publishes after the in-flight one.
complete_quest and uncomplete_quest force a coordinator refresh when
the call appended, so entity states land the moment the service call
returns; scheduled internals are unaffected.
complete/uncomplete return a CompletionResult str subclass carrying
appended (decided with the write under the lock), so duplicate
concurrent calls fire one transition event; day-complete only
evaluates for an instance due today.
quest_completed and quest_uncompleted fire on actual transitions with
documented payloads; child_day_complete fires when a completion clears
the child's day; quest_missed is defined as Feature 11's contract.
Payloads documented in the README for automation authors.