Originally created by: athanclark
Stage 3 of [#537]: the desktop's SQLite store and och-convert hold ongoing durations. This PR is stacked on [#540], which is stacked on [#539]. I'll retarget it as those merge.
durations CHECK becomes "at most one" for an end. A start still needs exactly one, and an ongoing start is refused.durations table requires an end time or moment. The first ongoing duration saved into one copies the table into the new schema (allow_ongoing). Files that never hold an ongoing duration are left alone, and earlier app versions cannot open a file that does.end_json = {"ongoing":true}. A new partial index, duration_intervals_ongoing, finds those rows.validateDocument.The desktop store has no clock of its own, so the app passes now with each query.
durations, overlapping or keyset) read duration_intervals with ongoing rows resolved in SQL (resolved_intervals). Their filters, sort orders and offsets are therefore exact.now. The app merges them with frameWithOngoing.frameWithOngoing too, replacing the merge [#540] added. The server and the desktop share one merge, which:durations table. All 35 native tests pass, and cargo fmt and clippy are clean.och-convert: round-trips an ongoing end, and refuses one without a calendar.ongoing-durations-postgres, durations-postgres, summaries-postgres and views-postgres all pass locally.npm test: passes apart from contents.test.mjs, which fails locally with git ENOBUFS.I haven't run the desktop app itself.
🤖 Generated with Claude Code
Ticket changed by: athanclark