Release v26.04.29.3 — Event-store data integrity + EventSlideOver redesign #47

Merged
bvandeusen merged 0 commits from dev into main 2026-04-29 16:04:45 -04:00
bvandeusen commented 2026-04-29 16:04:35 -04:00 (Migrated from git.fabledsword.com)

Highlights

The 4/29 dentist-appointment investigation surfaced two distinct root causes, each fixed structurally — and an unrelated UX refactor we held off shipping in v26.04.29.2.

Event store: data integrity at every boundary

  • 4c58603 — Replace end_dt column with duration_minutes (Fable #160). Schema migration 0043. End-before-start is now structurally inexpressible (CHECK constraint duration_minutes >= 0). The corrupt prod row that started this whole investigation collapses cleanly to a point event during the migration backfill. Wire format unchanged — to_dict() emits end_dt as derived from start_dt + duration_minutes. Behavioral upgrade: moving an event's start now slides the effective end forward (preserving duration), instead of corrupting or hard-rejecting.
  • 84640a0update_event / delete_event refuse ambiguous queries (Fable #161). When query matches multiple events, the tool returns success: false with a candidates array of {id, title, start_dt, location}. The model picks one by passing event_id on the next call. This is the actual root cause of how event id=2 got mutated in the first place: pre-fix, both tools silently took matches[0]. For delete in particular, the tool description leans into "never guess between candidates."

The two are complementary: #160 makes invalid data inexpressible at storage; #161 makes the wrong event unmutatable at the tool layer.

Calendar UX

  • 2db23ceEventSlideOver redesigned as a centered modal. No Save / Cancel buttons; auto-save on close (X / Esc / backdrop click / Enter in form field). Trash icon in the header (edit mode only) opens an inline two-step confirm. Validity-aware: invalid form in edit mode → toast naming the missing field + discard; in create mode → silent discard. The right-edge slide-over was hiding the destructive ghost-style Delete button against a thin floor band; the centered modal eliminates the contrast pocket entirely.

Reverted in-flight

  • b7e7073 reverts 94b169f — the A+B hotfix (filter robustness + write-side end-vs-start check). Superseded by #160 which makes the same scenarios structurally impossible.

Test plan

  • 46 calendar/events tests pass; ruff clean across all touched files
  • Migration backfill rules covered: end_dt > start → minute count; end_dt <= start OR null → NULL
  • After deploy: verify event id=2 in prod surfaces correctly in the upcoming list (it should, with duration_minutes=NULL from the migration backfill)
  • Smoke test the disambiguation flow: create two events with the same title, ask the assistant to update "the appointment", confirm the candidates list comes back and the model picks correctly via event_id
  • Verify EventSlideOver modal renders centered with auto-save behavior on next deploy

Note on CI

The dev-branch CI on 84640a0 failed only on the Build & push image step (registry returned a generic unknown error after a successful build). Same transient infra flake we hit on 35fab6c earlier today; all code gates (typecheck, lint, tests, build) passed. The release-tag push will run a fresh build.

🤖 Generated with Claude Code

## Highlights The 4/29 dentist-appointment investigation surfaced two distinct root causes, each fixed structurally — and an unrelated UX refactor we held off shipping in v26.04.29.2. ### Event store: data integrity at every boundary - **`4c58603` — Replace `end_dt` column with `duration_minutes` (Fable #160).** Schema migration 0043. End-before-start is now structurally inexpressible (CHECK constraint `duration_minutes >= 0`). The corrupt prod row that started this whole investigation collapses cleanly to a point event during the migration backfill. Wire format unchanged — `to_dict()` emits `end_dt` as derived from `start_dt + duration_minutes`. Behavioral upgrade: moving an event's start now slides the effective end forward (preserving duration), instead of corrupting or hard-rejecting. - **`84640a0` — `update_event` / `delete_event` refuse ambiguous queries (Fable #161).** When `query` matches multiple events, the tool returns `success: false` with a `candidates` array of `{id, title, start_dt, location}`. The model picks one by passing `event_id` on the next call. This is the actual root cause of how event id=2 got mutated in the first place: pre-fix, both tools silently took `matches[0]`. For delete in particular, the tool description leans into "never guess between candidates." The two are complementary: #160 makes invalid data inexpressible at storage; #161 makes the wrong event unmutatable at the tool layer. ### Calendar UX - **`2db23ce` — `EventSlideOver` redesigned as a centered modal.** No Save / Cancel buttons; auto-save on close (X / Esc / backdrop click / Enter in form field). Trash icon in the header (edit mode only) opens an inline two-step confirm. Validity-aware: invalid form in edit mode → toast naming the missing field + discard; in create mode → silent discard. The right-edge slide-over was hiding the destructive ghost-style Delete button against a thin floor band; the centered modal eliminates the contrast pocket entirely. ### Reverted in-flight - **`b7e7073`** reverts **`94b169f`** — the A+B hotfix (filter robustness + write-side end-vs-start check). Superseded by #160 which makes the same scenarios structurally impossible. ## Test plan - [x] 46 calendar/events tests pass; ruff clean across all touched files - [x] Migration backfill rules covered: `end_dt > start` → minute count; `end_dt <= start` OR null → NULL - [ ] After deploy: verify event id=2 in prod surfaces correctly in the upcoming list (it should, with `duration_minutes=NULL` from the migration backfill) - [ ] Smoke test the disambiguation flow: create two events with the same title, ask the assistant to update "the appointment", confirm the candidates list comes back and the model picks correctly via `event_id` - [ ] Verify `EventSlideOver` modal renders centered with auto-save behavior on next deploy ## Note on CI The dev-branch CI on `84640a0` failed only on the `Build & push image` step (registry returned a generic `unknown` error after a successful build). Same transient infra flake we hit on `35fab6c` earlier today; all code gates (typecheck, lint, tests, build) passed. The release-tag push will run a fresh build. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledScribe#47