
timothyandClaude Opus 4.8
5c9f04fdec
feat(#253 PR2): optimistic-concurrency on schedule-items aggregate
Wire the frozen #253 ETag/If-Match/412 recipe onto ProgramSchedule /
schedule-items, keeping the PR#258 positional in-place reconcile intact.
Backend:
- ReplaceProgramScheduleItems command gains Option<int> ExpectedVersion;
ReplaceScheduleItemsRequest.ToCommand threads it.
- Handler: standalone CheckVersion Either AFTER validation (so 412 isn't
flattened to 422), unconditional Version++ before save, guarded save via
SaveChangesWithConcurrencyGuard, and 412 propagated without running the
post-save reload/enqueue.
- ProgramScheduleViewModel + Mapper carry Version.
- ScheduleController: GET /items emits ETag; PUT /items parses If-Match
(malformed -> 400), threads ExpectedVersion, re-queries for the new ETag,
and advertises 400/412.
- Sibling config-writers (Add/Delete item, Update schedule) bump Version.
Frontend:
- schedules.ts: getScheduleItemsWithMeta + replaceScheduleItems(ifMatch)
returning ResponseWithMeta.
- SchedulesScreen: etagRef threaded through the #242 dirty-guard (set from
load + every successful save); 412 opens a conflict ConfirmDialog whose
Reload discards the draft and re-runs loadItems.
Tests: handler concurrency suite (stale->412 no mutation + fill-group state
untouched, match/absent success+bump, no-op still bumps, racing save->412);
controller ETag/If-Match/412 cases; SchedulesScreen 412-conflict-dialog test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 18:36:25 +02:00
..
2026-07-11 15:52:31 +02:00
2026-07-06 22:07:36 +02:00
2026-07-02 18:36:56 +02:00
2026-07-02 18:36:56 +02:00
2026-07-11 16:51:59 +02:00
2026-07-11 16:51:59 +02:00
2026-07-11 00:15:41 +02:00
2026-07-11 00:15:41 +02:00
2026-07-06 22:07:36 +02:00
2026-07-06 22:07:36 +02:00
2026-07-11 16:51:59 +02:00
2026-07-11 16:51:59 +02:00
2026-07-09 22:35:43 +02:00
2026-07-09 22:35:43 +02:00
2026-07-04 21:14:44 +02:00
2026-07-07 19:22:49 +02:00
2026-07-07 19:22:49 +02:00
2026-07-07 19:51:11 +02:00
2026-07-07 19:51:11 +02:00
2026-07-07 14:33:01 +02:00
2026-07-07 14:33:01 +02:00
2026-07-07 14:33:01 +02:00
2026-07-07 14:33:01 +02:00
2026-07-05 23:06:08 +02:00
2026-07-07 22:56:48 +02:00
2026-07-07 22:56:48 +02:00
2026-07-11 16:11:16 +02:00
2026-07-11 00:15:41 +02:00
2026-07-11 00:15:41 +02:00
2026-07-10 23:28:09 +02:00
2026-07-11 12:54:48 +02:00
2026-07-07 21:42:18 +02:00
2026-07-07 21:42:18 +02:00
2026-07-11 00:10:20 +02:00
2026-07-11 00:10:20 +02:00
2026-07-07 15:39:54 +02:00
2026-07-07 15:39:54 +02:00
2026-07-07 22:56:48 +02:00
2026-07-07 22:56:48 +02:00
2026-07-07 15:39:54 +02:00
2026-07-07 15:39:54 +02:00
2026-07-11 16:11:16 +02:00
2026-07-11 16:11:16 +02:00
2026-07-08 19:58:59 +02:00
2026-07-08 19:58:59 +02:00
2026-07-09 07:52:14 +02:00
2026-07-09 07:52:14 +02:00
2026-07-10 00:00:05 +02:00
2026-07-10 00:03:18 +02:00
2026-07-07 22:11:04 +02:00
2026-07-07 22:10:57 +02:00
2026-07-09 22:30:28 +02:00
2026-07-09 22:30:28 +02:00
2026-07-08 19:58:59 +02:00
2026-07-08 19:58:59 +02:00
2026-07-11 18:36:25 +02:00
2026-07-10 00:03:18 +02:00
2026-07-10 00:03:18 +02:00
2026-07-07 09:22:50 +02:00
2026-07-07 09:22:51 +02:00
2026-07-07 18:37:59 +02:00
2026-07-07 18:37:59 +02:00
2026-07-07 15:09:30 +02:00
2026-07-07 15:09:30 +02:00
2026-07-09 00:03:26 +02:00
2026-07-09 00:03:26 +02:00
2026-07-02 08:23:19 +02:00
2026-07-07 14:33:01 +02:00
2026-07-07 14:33:01 +02:00