Calendar drag: card snaps back and the move takes ~5 s to settle (production 7a) #1015

Open
opened 2026-10-03 15:21:47 +00:00 by kayg · 15 comments
Owner

Owner report (2026-10-03, production 7a c82b9aca1, reproducible)

"It took 5 seconds for the deployed build to drag an event to another time and have it sync. I drag, the card seems expanded. The card gets back to its original size and then the toast appears ' ...' and then the card expands to the right size."

So on drop the card first shows the new geometry, then SNAPS BACK to the old one, a toast with "…" appears late, and only after ~5 s does the card take the new size/time. Production server was idle (load 0.2, no slow statements logged), so suspect the client path: the optimistic placement being dropped/overwritten before the write is acknowledged (e.g. a refetch or SSE/change-feed update with the old state wins), the write waiting on something (If-Match/412 retry, parse round-trip, debounce, journal write + change feed wakeup), and the pending toast shown late.

Required behaviour (DESIGN instant-interactions rules, §59 on 7b / owner rule "loading never waits for motion")

  • On drop the card keeps its new position and size immediately (optimistic) and never snaps back unless the write fails; then it animates back with an error toast and Undo.
  • The write completes in well under 1 s on an idle server; no toast for a normal successful move except the Undo toast; a pending indicator only if the write takes > ~400 ms.
  • A stale refetch or live update older than the pending write must not overwrite it (revision ordering).

Tasks

  1. Reproduce on a local production build and on staging (7a) with timing marks: pointerup → request sent → response → UI settled; capture a Performance trace and a frame filmstrip. Find each delay and the snap-back cause (cite file:line).
  2. Fix all of them (Event moves and resizes, Log entries, Tasks with times; Day, Week, Month drag).
  3. Regression e2e as a User: drag an Event and a Log entry to a new time; assert no snap-back frame, new time persisted after reload, total settle < 1 s on a local server; and a forced-failure case that reverts with an error toast.
    Per-branch policy; screenshots of the filmstrip. Report with the measured before/after timings.
## Owner report (2026-10-03, production 7a c82b9aca1, reproducible) "It took 5 seconds for the deployed build to drag an event to another time and have it sync. I drag, the card seems expanded. The card gets back to its original size and then the toast appears ' ...' and then the card expands to the right size." So on drop the card first shows the new geometry, then SNAPS BACK to the old one, a toast with "…" appears late, and only after ~5 s does the card take the new size/time. Production server was idle (load 0.2, no slow statements logged), so suspect the client path: the optimistic placement being dropped/overwritten before the write is acknowledged (e.g. a refetch or SSE/change-feed update with the old state wins), the write waiting on something (If-Match/412 retry, parse round-trip, debounce, journal write + change feed wakeup), and the pending toast shown late. ## Required behaviour (DESIGN instant-interactions rules, §59 on 7b / owner rule "loading never waits for motion") - On drop the card keeps its new position and size immediately (optimistic) and never snaps back unless the write fails; then it animates back with an error toast and Undo. - The write completes in well under 1 s on an idle server; no toast for a normal successful move except the Undo toast; a pending indicator only if the write takes > ~400 ms. - A stale refetch or live update older than the pending write must not overwrite it (revision ordering). ## Tasks 1. Reproduce on a local production build and on staging (7a) with timing marks: pointerup → request sent → response → UI settled; capture a Performance trace and a frame filmstrip. Find each delay and the snap-back cause (cite file:line). 2. Fix all of them (Event moves and resizes, Log entries, Tasks with times; Day, Week, Month drag). 3. Regression e2e as a User: drag an Event and a Log entry to a new time; assert no snap-back frame, new time persisted after reload, total settle < 1 s on a local server; and a forced-failure case that reverts with an error toast. Per-branch policy; screenshots of the filmstrip. Report with the measured before/after timings.
Author
Owner

Started on job/drag-1015 at base cfee85c6b1 (origin/dev). Reading the Calendar write and projection paths, then measuring a production SPA with the shared local server. No push or deploy.

Started on job/drag-1015 at base cfee85c6b11537968aaa0685d1ed1c3ac68a8c3e (origin/dev). Reading the Calendar write and projection paths, then measuring a production SPA with the shared local server. No push or deploy.
Author
Owner

Local production SPA measurement reproduced snap-back: Event 2 wrong frames (request 40 ms, response 99 ms, final old frame 131.5 ms); timed Task 1 wrong frame (request 21 ms, response 146 ms); Log 0 wrong frames (request 79 ms, response 282 ms). The 5 s production delay is not reproduced locally. Cause: TimeGrid.svelte onPlanPointerUp clears its placement preview, while the route movePlan only persists/refetches and never applies new geometry. Log request-start versions also permit reads begun during a write to overwrite it. MonthGrid has no existing drag implementation. Implementing a per-item acknowledgement/projection guard, Event/Task optimistic moves and Undo, and shared Month move callbacks. Staging measurement belongs to the merge round under the verification policy; no deployed data has been changed.

Local production SPA measurement reproduced snap-back: Event 2 wrong frames (request 40 ms, response 99 ms, final old frame 131.5 ms); timed Task 1 wrong frame (request 21 ms, response 146 ms); Log 0 wrong frames (request 79 ms, response 282 ms). The 5 s production delay is not reproduced locally. Cause: TimeGrid.svelte onPlanPointerUp clears its placement preview, while the route movePlan only persists/refetches and never applies new geometry. Log request-start versions also permit reads begun during a write to overwrite it. MonthGrid has no existing drag implementation. Implementing a per-item acknowledgement/projection guard, Event/Task optimistic moves and Undo, and shared Month move callbacks. Staging measurement belongs to the merge round under the verification policy; no deployed data has been changed.
Author
Owner

Committed d58c110cd: per-item optimistic placement and matching-projection release; writes serialize per stable item; Event/Task moves now offer Undo; saving feedback starts only after 400 ms. Focused checks: svelte-check found 0 errors and 0 warnings; 3 Vitest files passed, 47 tests passed. First after measurements: Event response/settle 665 ms, Log 653 ms, Task 332 ms; zero snap-back frames in all three. Resize/failure regression and six-width/theme macOS screenshot sets are running.

Committed d58c110cd: per-item optimistic placement and matching-projection release; writes serialize per stable item; Event/Task moves now offer Undo; saving feedback starts only after 400 ms. Focused checks: svelte-check found 0 errors and 0 warnings; 3 Vitest files passed, 47 tests passed. First after measurements: Event response/settle 665 ms, Log 653 ms, Task 332 ms; zero snap-back frames in all three. Resize/failure regression and six-width/theme macOS screenshot sets are running.
Author
Owner

Perf VM measurement held /root/perf.lock. Load 0.10/0.31/0.30. Grouped placement reconciliation: average p50/p95 0.0075/0.0618 ms; worst (35 days × 10,000 rows, 100 pending placements) p50/p95 1.125/2.759 ms, 20-read burst 19.522 ms, measured CPU 15.136 ms, RSS 145,428,480 → 155,652,096 bytes. Initial implementation worst p95 was 1,202.984 ms and is now fixed. Existing docs/perf/baseline.json has no placement-guard metric. Origin/dev merged once (daily-log #724); final per-branch gates running.

Perf VM measurement held /root/perf.lock. Load 0.10/0.31/0.30. Grouped placement reconciliation: average p50/p95 0.0075/0.0618 ms; worst (35 days × 10,000 rows, 100 pending placements) p50/p95 1.125/2.759 ms, 20-read burst 19.522 ms, measured CPU 15.136 ms, RSS 145,428,480 → 155,652,096 bytes. Initial implementation worst p95 was 1,202.984 ms and is now fixed. Existing docs/perf/baseline.json has no placement-guard metric. Origin/dev merged once (daily-log #724); final per-branch gates running.
Author
Owner

Month regression found another real interaction gap: the shared hover timer could open a Task preview during a drag and cover its destination. Month now calls the existing Calendar gesture hook to cancel hover intent, and the shared pointer hover handlers ignore held buttons. Event, Log and Task Month moves now reach the new day and persist after reload. Added rollback geometry motion using shared duration tokens and reduced-motion CSS. Baseline screenshots/filmstrip and perf JSON are attached to this issue; final screenshots are being captured.

Month regression found another real interaction gap: the shared hover timer could open a Task preview during a drag and cover its destination. Month now calls the existing Calendar gesture hook to cancel hover intent, and the shared pointer hover handlers ignore held buttons. Event, Log and Task Month moves now reach the new day and persist after reload. Added rollback geometry motion using shared duration tokens and reduced-motion CSS. Baseline screenshots/filmstrip and perf JSON are attached to this issue; final screenshots are being captured.
Author
Owner

UX review also found that selection/open previews did not follow moved Event occurrence identities or cross-day positions. They now follow the same placement as the loaded grid, including rollback and refreshed base identities. Shared-host latency check recorded a SLOW Task write at 1,410 ms with zero snap-back frames; default idle regression remains strict at <1 s, with an explicit shared-host mode that records SLOW misses. No server/API code changed. Final focused touch, keyboard, Undo and selection checks are running.

UX review also found that selection/open previews did not follow moved Event occurrence identities or cross-day positions. They now follow the same placement as the loaded grid, including rollback and refreshed base identities. Shared-host latency check recorded a SLOW Task write at 1,410 ms with zero snap-back frames; default idle regression remains strict at <1 s, with an explicit shared-host mode that records SLOW misses. No server/API code changed. Final focused touch, keyboard, Undo and selection checks are running.
Author
Owner

Real touch regression found a further Month race: pointerdown/move/up arrived correctly, but the long-press menu still covered the drop point while its exit motion ran. The writer was never called. Month now finds the underlying day cell in the hit stack; input no longer waits for the menu animation. This uses hit testing for drag behavior only and adds no per-row layout measurements.

Real touch regression found a further Month race: pointerdown/move/up arrived correctly, but the long-press menu still covered the drop point while its exit motion ran. The writer was never called. Month now finds the underlying day cell in the hit stack; input no longer waits for the menu animation. This uses hit testing for drag behavior only and adds no per-row layout measurements.
Author
Owner

Real-server touch finding: Month's long-press drop PATCH returned 200 with date: 2026-08-12, but the same pointer release also navigated to July through the existing page swipe action. Movable Month rows now declare the existing data-gesture-owner contract, which pageSwipeTarget already excludes. The focused Month regression verifies that declaration and the underlying-day drop under an exiting menu.

Gates:

svelte-check found 0 errors and 0 warnings
 Test Files  1 passed (1)
      Tests  4 passed (4)

Commit: the branch's latest fix(calendar): keep Month touch drags out of page swipes. Completing the real-server touch/keyboard/Undo and screenshot evidence next.

Real-server touch finding: Month's long-press drop PATCH returned 200 with `date: 2026-08-12`, but the same pointer release also navigated to July through the existing page swipe action. Movable Month rows now declare the existing `data-gesture-owner` contract, which `pageSwipeTarget` already excludes. The focused Month regression verifies that declaration and the underlying-day drop under an exiting menu. Gates: ``` svelte-check found 0 errors and 0 warnings Test Files 1 passed (1) Tests 4 passed (4) ``` Commit: the branch's latest `fix(calendar): keep Month touch drags out of page swipes`. Completing the real-server touch/keyboard/Undo and screenshot evidence next.
Author
Owner

Implemented #1015 on job/drag-1015. No push, deploy or issue closure.

Built

Event and timed Task moves/resizes now update loaded geometry before the first await. Logs, Events and Tasks keep their dropped placement through pending writes and stale Calendar Index reads. Writes to one stable item run in order. An acknowledgement releases its overlay only after all affected days show the saved geometry; independent chunk reads are supported. Normal moves show the Undo toast; Saving… appears only after 400 ms. A refused write restores only that item with shared rollback motion and an error toast. Reduced motion remains active.

Month now uses the existing move writers for pointer drag, touch long press and keyboard day/week arrows. A closing long-press menu cannot cover the drop target. Movable rows use the existing gesture-owner contract so the same touch release cannot also navigate between months. Selection, keyboard focus and open previews follow moved item identities.

Cause

packages/ui/src/components/calendar/TimeGrid.svelte:1079 clears the temporary drag preview at pointerup. The old route's movePlan did not publish optimistic Event/Task geometry before awaiting the writer and range refresh. That exposed the old row until the write/read completed. The original Log request-start version check also allowed reads started during a write to return an old Index placement.

The fix is in +page.svelte:927 (immediate placement), edits.ts:354 (pending/acknowledged overlay) and data.ts:1030 (shared local-day projectors). No artificial write debounce, animation await or server delay was found in the reproduced local path. The production owner's five-second delay was not reproduced locally.

UX gaps closed

  • Day/Week Events and timed Tasks retain their dropped size and position.
  • Stale reads cannot replace pending Log placement.
  • Month had no move handlers; pointer, touch and keyboard moves now use the same writers.
  • Touch drops under an exiting menu remain valid, and do not trigger page navigation.
  • Selection, focus and previews follow the moved identity.
  • Successful moves expose Undo; real-server refused writes restore the saved placement and show an error toast.

Files

apps/web/src/lib/calendar/{edits.ts,data.ts,edits.test.ts,MonthGrid.svelte.test.ts}; apps/web/src/routes/calendar/[view]/[date]/+page.svelte; packages/ui/src/components/calendar/MonthGrid.svelte; apps/web/e2e/calendar-drag-1015.mjs; bench/calendar-drag-1015.mjs.

Decisions

The API exposes no ordered revision for Calendar Index placement. The client therefore uses stable item identity, write tokens, per-item write ordering and matching geometry from every affected day. Metadata does not determine placement acknowledgement. Month keeps the clock interval and uses unmodified arrows for one day/one week. Existing read-only, recurring/carry-in Event and date-only Task restrictions stay in force. Failure restores the saved placement; Undo is for completed changes.

Known gaps / UX gaps left

Production 7a/staging timing and real macOS interop remain for the merge round under the latest verification policy. The before run used the starting frontend with an available alternate prebuilt backend; its absolute latency is exploratory, not a controlled backend speed comparison. All final checks use this branch's backend and production client build. The five-second production server path still needs staging measurement. Existing small Month rows retain the documented day-opening touch-target equivalent; no new target sizes were introduced.

For the merge round

  • cd apps/web && bun run test --maxWorkers=2: full combined web regressions.
  • cd apps/web && bun run test:e2e:calendar && bun run test:e2e:calendar-crossday: existing Calendar interactions and cross-day placement.
  • CALTERNAL_SERVER_BIN=<combined-server> TZ=UTC bun apps/web/e2e/calendar-drag-1015.mjs: strict below-one-second local writes, no snap-back, reload persistence, refused-write rollback, Month touch/keyboard/Undo and all screenshot widths/themes.
  • bash tests/adversarial/run.sh: the merge round's one time-boxed API/authz/XUser/robustness round.
  • Deploy/check on staging, then repeat pointerup → request → response → settled timing on 7a and the combined build; verify on the locked macOS VM. This job made no deployment.

Local production-build measurements

All times are milliseconds from pointerup. This is a shared build host; staging is not measured in this job.

Case Before request / response / last wrong frame Before wrong frames After request / response / settle After wrong frames
Day Drag Event move 41 / 102 / 145.2 2 154 / 349 / 349.0 0
Day Drag Log move 39 / 268 / 268.0 0 133 / 365 / 365.0 0
Day Drag Task move 49 / 324 / 399.3 2 63 / 343 / 343.0 0
Day resize: Drag Log — — 94 / 398 / 398.0 0
Day resize: Drag Task — — 106 / 379 / 379.0 0
Day resize: Drag Event — — 123 / 313 / 313.0 0
Week move: Drag Event — — 176 / 412 / 412.0 0
Week move: Drag Log — — 113 / 360 / 360.0 0
Week move: Drag Task — — 38 / 468 / 468.0 0

All nine successful move/resize writes passed the strict below-one-second assertion. The three forced 503 cases restored their prior geometry and showed the correct error toast. Each successful Day/Week case verifies reload persistence. Month verifies real pointer moves/reload, touch, keyboard focus and selection, and Undo. The screenshots cover Day, Week and Month at 390, 820 and 1440 px, in paper and midnight, with the macOS platform emulated. The final set contains 18 screenshots and 45 drop filmstrip frames from 12 pointerup marks. The CDP trace and timing JSON are attached separately; private Playwright archives with session headers are removed.

Placement hot-path profile

Measured on perf-test under flock /root/perf.lock, load average 0 / 0.06 / 0.09. No compilation ran there. docs/perf/baseline.json has no placement-guard baseline. The first guard implementation measured worst-case p95 1,202.984 ms; indexing each day/family removed the repeated per-item scans before the fix was completed.

Profile p50 / p95 ms CPU ms RSS before / after bytes 20-read burst ms
Average: 35 days × 50 rows, one pending move 0.013 / 0.072 4.755 25452544 / 28839936 0.130
Worst: 35 days × 10,000 rows, 100 pending moves 0.612 / 1.819 9.567 145297408 / 155389952 11.176
Worst: acknowledged, awaiting Index projection 3.313 / 4.885 82.933 266522624 / 320311296 31.537

RSS includes fixture allocation and preceding profile cases; it is not an isolated incremental peak. Performance is evidence, not a merge gate.

Attached evidence

Gate output (verbatim)

cargo fmt --check: exit 0, no output. No Rust source was authored by this job; per-crate clippy/test do not apply under the current verification policy. The required local server build completed successfully because the shared CURRENT file was absent.

bun run check:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/drag-1015/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

Focused Vitest results (the suite, the added cross-chunk case, the final Month file and the final write-queue case):

 Test Files  4 passed (4)
      Tests  55 passed (55)
   Start at  17:57:38
   Duration  53.12s (transform 79%, import 8%, tests 6%, environment 6%, setup 2%)
 Test Files  1 passed (1)
      Tests  1 passed | 17 skipped (18)
   Start at  17:59:58
   Duration  31.70s (transform 92%, import 8%)
 Test Files  1 passed (1)
      Tests  4 passed (4)
   Start at  19:02:09
   Duration  21.90s (transform 77%, environment 9%, import 6%, tests 5%, setup 3%)
 Test Files  1 passed (1)
      Tests  1 passed | 17 skipped (18)
   Start at  19:13:29
   Duration  32.10s (transform 91%, import 9%)

Production web build:

✓ built in 56.37s
✓ built in 121ms
✓ built in 1m 52s
  Wrote site to "build"

Full focused real-server regression:

calendar drag #1015: frame geometry, persisted time, rollback, Month touch/keyboard and Undo passed

The final Undo safeguard uses the same stable-item queue. Its focused real-server Month rerun confirms touch, keyboard focus/selection and Undo after acknowledgement. The regression now awaits each Month item’s own write response before reload, rather than another item’s earlier Undo toast.

Final state

Head: ec4aa3ca0cc23f0ab8cd3a3ddd3db9efb0aea592. origin/dev was fetched and merged once before final gates (11cf048e9). Working tree is clean. No push or deployment. Generated web build output and private Playwright archives were removed. Cargo cleanup output:

     Removed 7242 files, 4.6GiB total

Final focused Month/Undo output:

Month touch placement confirmed
Month keyboard placement and focus confirmed
Month Undo placement confirmed
calendar drag #1015: frame geometry, persisted time, rollback, Month touch/keyboard and Undo passed

Final-head screenshot set (18 macOS-platform images): head-macos-screenshots.zip. The earlier full-run trace/filmstrip remains the measured evidence; the final change orders Undo writers and makes the test await the current item’s acknowledgement.

Implemented #1015 on `job/drag-1015`. No push, deploy or issue closure. ## Built Event and timed Task moves/resizes now update loaded geometry before the first await. Logs, Events and Tasks keep their dropped placement through pending writes and stale Calendar Index reads. Writes to one stable item run in order. An acknowledgement releases its overlay only after all affected days show the saved geometry; independent chunk reads are supported. Normal moves show the Undo toast; Saving… appears only after 400 ms. A refused write restores only that item with shared rollback motion and an error toast. Reduced motion remains active. Month now uses the existing move writers for pointer drag, touch long press and keyboard day/week arrows. A closing long-press menu cannot cover the drop target. Movable rows use the existing gesture-owner contract so the same touch release cannot also navigate between months. Selection, keyboard focus and open previews follow moved item identities. ## Cause `packages/ui/src/components/calendar/TimeGrid.svelte:1079` clears the temporary drag preview at pointerup. The old route's `movePlan` did not publish optimistic Event/Task geometry before awaiting the writer and range refresh. That exposed the old row until the write/read completed. The original Log request-start version check also allowed reads started during a write to return an old Index placement. The fix is in `+page.svelte:927` (immediate placement), `edits.ts:354` (pending/acknowledged overlay) and `data.ts:1030` (shared local-day projectors). No artificial write debounce, animation await or server delay was found in the reproduced local path. The production owner's five-second delay was not reproduced locally. ## UX gaps closed - Day/Week Events and timed Tasks retain their dropped size and position. - Stale reads cannot replace pending Log placement. - Month had no move handlers; pointer, touch and keyboard moves now use the same writers. - Touch drops under an exiting menu remain valid, and do not trigger page navigation. - Selection, focus and previews follow the moved identity. - Successful moves expose Undo; real-server refused writes restore the saved placement and show an error toast. ## Files `apps/web/src/lib/calendar/{edits.ts,data.ts,edits.test.ts,MonthGrid.svelte.test.ts}`; `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`; `packages/ui/src/components/calendar/MonthGrid.svelte`; `apps/web/e2e/calendar-drag-1015.mjs`; `bench/calendar-drag-1015.mjs`. ## Decisions The API exposes no ordered revision for Calendar Index placement. The client therefore uses stable item identity, write tokens, per-item write ordering and matching geometry from every affected day. Metadata does not determine placement acknowledgement. Month keeps the clock interval and uses unmodified arrows for one day/one week. Existing read-only, recurring/carry-in Event and date-only Task restrictions stay in force. Failure restores the saved placement; Undo is for completed changes. ## Known gaps / UX gaps left Production 7a/staging timing and real macOS interop remain for the merge round under the latest verification policy. The before run used the starting frontend with an available alternate prebuilt backend; its absolute latency is exploratory, not a controlled backend speed comparison. All final checks use this branch's backend and production client build. The five-second production server path still needs staging measurement. Existing small Month rows retain the documented day-opening touch-target equivalent; no new target sizes were introduced. ## For the merge round - `cd apps/web && bun run test --maxWorkers=2`: full combined web regressions. - `cd apps/web && bun run test:e2e:calendar && bun run test:e2e:calendar-crossday`: existing Calendar interactions and cross-day placement. - `CALTERNAL_SERVER_BIN=<combined-server> TZ=UTC bun apps/web/e2e/calendar-drag-1015.mjs`: strict below-one-second local writes, no snap-back, reload persistence, refused-write rollback, Month touch/keyboard/Undo and all screenshot widths/themes. - `bash tests/adversarial/run.sh`: the merge round's one time-boxed API/authz/XUser/robustness round. - Deploy/check on staging, then repeat pointerup → request → response → settled timing on 7a and the combined build; verify on the locked macOS VM. This job made no deployment. ## Local production-build measurements All times are milliseconds from pointerup. This is a shared build host; staging is not measured in this job. | Case | Before request / response / last wrong frame | Before wrong frames | After request / response / settle | After wrong frames | |---|---:|---:|---:|---:| | Day Drag Event move | 41 / 102 / 145.2 | 2 | 154 / 349 / 349.0 | 0 | | Day Drag Log move | 39 / 268 / 268.0 | 0 | 133 / 365 / 365.0 | 0 | | Day Drag Task move | 49 / 324 / 399.3 | 2 | 63 / 343 / 343.0 | 0 | | Day resize: Drag Log | — | — | 94 / 398 / 398.0 | 0 | | Day resize: Drag Task | — | — | 106 / 379 / 379.0 | 0 | | Day resize: Drag Event | — | — | 123 / 313 / 313.0 | 0 | | Week move: Drag Event | — | — | 176 / 412 / 412.0 | 0 | | Week move: Drag Log | — | — | 113 / 360 / 360.0 | 0 | | Week move: Drag Task | — | — | 38 / 468 / 468.0 | 0 | All nine successful move/resize writes passed the strict below-one-second assertion. The three forced 503 cases restored their prior geometry and showed the correct error toast. Each successful Day/Week case verifies reload persistence. Month verifies real pointer moves/reload, touch, keyboard focus and selection, and Undo. The screenshots cover Day, Week and Month at 390, 820 and 1440 px, in paper and midnight, with the macOS platform emulated. The final set contains 18 screenshots and 45 drop filmstrip frames from 12 pointerup marks. The CDP trace and timing JSON are attached separately; private Playwright archives with session headers are removed. ## Placement hot-path profile Measured on perf-test under `flock /root/perf.lock`, load average `0 / 0.06 / 0.09`. No compilation ran there. `docs/perf/baseline.json` has no placement-guard baseline. The first guard implementation measured worst-case p95 1,202.984 ms; indexing each day/family removed the repeated per-item scans before the fix was completed. | Profile | p50 / p95 ms | CPU ms | RSS before / after bytes | 20-read burst ms | |---|---:|---:|---:|---:| | Average: 35 days × 50 rows, one pending move | 0.013 / 0.072 | 4.755 | 25452544 / 28839936 | 0.130 | | Worst: 35 days × 10,000 rows, 100 pending moves | 0.612 / 1.819 | 9.567 | 145297408 / 155389952 | 11.176 | | Worst: acknowledged, awaiting Index projection | 3.313 / 4.885 | 82.933 | 266522624 / 320311296 | 31.537 | RSS includes fixture allocation and preceding profile cases; it is not an isolated incremental peak. Performance is evidence, not a merge gate. ## Attached evidence - [screenshots-and-filmstrip.zip](https://git.kayg.org/attachments/11b4d430-547b-4cca-9957-4a231b01362d) - [Drag Event-move-drop.png](https://git.kayg.org/attachments/78b05b15-c5a1-4eb0-bbda-3b0586adefb7) - [perf-final.json](https://git.kayg.org/attachments/5febd632-3679-4e61-bcd7-3afb7b338f29) - [after-screenshots-and-filmstrip.zip](https://git.kayg.org/attachments/6e686831-ecd4-4842-a279-26985a40022c) - [after-performance-trace.zip](https://git.kayg.org/attachments/d5e6e656-4e48-464b-8c3c-e7d3f80c3d14) - [perf-ack.json](https://git.kayg.org/attachments/2b78a234-c893-46a4-9631-f8c90ddf55da) ## Gate output (verbatim) `cargo fmt --check`: exit 0, no output. No Rust source was authored by this job; per-crate clippy/test do not apply under the current verification policy. The required local server build completed successfully because the shared CURRENT file was absent. `bun run check`: ``` $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/drag-1015/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Focused Vitest results (the suite, the added cross-chunk case, the final Month file and the final write-queue case): ``` Test Files 4 passed (4) Tests 55 passed (55) Start at 17:57:38 Duration 53.12s (transform 79%, import 8%, tests 6%, environment 6%, setup 2%) ``` ``` Test Files 1 passed (1) Tests 1 passed | 17 skipped (18) Start at 17:59:58 Duration 31.70s (transform 92%, import 8%) ``` ``` Test Files 1 passed (1) Tests 4 passed (4) Start at 19:02:09 Duration 21.90s (transform 77%, environment 9%, import 6%, tests 5%, setup 3%) ``` ``` Test Files 1 passed (1) Tests 1 passed | 17 skipped (18) Start at 19:13:29 Duration 32.10s (transform 91%, import 9%) ``` Production web build: ``` ✓ built in 56.37s ✓ built in 121ms ✓ built in 1m 52s Wrote site to "build" ``` Full focused real-server regression: ``` calendar drag #1015: frame geometry, persisted time, rollback, Month touch/keyboard and Undo passed ``` The final Undo safeguard uses the same stable-item queue. Its focused real-server Month rerun confirms touch, keyboard focus/selection and Undo after acknowledgement. The regression now awaits each Month item’s own write response before reload, rather than another item’s earlier Undo toast. ## Final state Head: `ec4aa3ca0cc23f0ab8cd3a3ddd3db9efb0aea592`. `origin/dev` was fetched and merged once before final gates (`11cf048e9`). Working tree is clean. No push or deployment. Generated web build output and private Playwright archives were removed. Cargo cleanup output: ``` Removed 7242 files, 4.6GiB total ``` Final focused Month/Undo output: ``` Month touch placement confirmed Month keyboard placement and focus confirmed Month Undo placement confirmed calendar drag #1015: frame geometry, persisted time, rollback, Month touch/keyboard and Undo passed ``` Final-head screenshot set (18 macOS-platform images): [head-macos-screenshots.zip](https://git.kayg.org/attachments/2f9ae974-f7e3-4348-a129-a040fb7f71c4). The earlier full-run trace/filmstrip remains the measured evidence; the final change orders Undo writers and makes the test await the current item’s acknowledgement.
Author
Owner

Started at base . I have read CLAUDE.md, CONTEXT.md and the Calendar design sections. I am merging the completed #1015 branch, checking #1018's READY FOR MERGE status, then I will run the requested gates and staging checks. No production changes.

Started at base . I have read CLAUDE.md, CONTEXT.md and the Calendar design sections. I am merging the completed #1015 branch, checking #1018's READY FOR MERGE status, then I will run the requested gates and staging checks. No production changes.
Author
Owner

Started on branch job/round-drag at base fdd5b364c3. I have read CLAUDE.md, CONTEXT.md and the Calendar design sections. I am merging the completed #1015 branch, checking #1018 READY FOR MERGE status, then I will run the requested gates and staging checks. No production changes.

Started on branch job/round-drag at base fdd5b364c3d8f746c53bc4c511afee15bf07999b. I have read CLAUDE.md, CONTEXT.md and the Calendar design sections. I am merging the completed #1015 branch, checking #1018 READY FOR MERGE status, then I will run the requested gates and staging checks. No production changes.
Author
Owner

Local real-server focused run on merged head 9b72abb58: Event, Log and Task Day/Week moves each settled below 1 s with zero snap-back frames; all three forced 503 rollbacks ran; Month touch placement passed. The following Month keyboard path failed the unchanged assertion "selection follows the moved Log" at apps/web/e2e/calendar-drag-1015.mjs:170. I am tracing it now.

Local real-server focused run on merged head 9b72abb58: Event, Log and Task Day/Week moves each settled below 1 s with zero snap-back frames; all three forced 503 rollbacks ran; Month touch placement passed. The following Month keyboard path failed the unchanged assertion "selection follows the moved Log" at apps/web/e2e/calendar-drag-1015.mjs:170. I am tracing it now.
Author
Owner

With CALTERNAL_E2E_ASSET_OVERRIDE=1, the correct production SPA passes the Day/Week Event, Log and Task moves under 200 ms with zero snap-back frames, and all forced 503 rollback cases run. Month touch placement passes, but the full run then fails the unchanged Month assertion selection follows the moved Log at calendar-drag-1015.mjs:170. The month-only path passes this selection check. This points to state carried from the preceding Day/Week interactions; I am tracing that case.

With CALTERNAL_E2E_ASSET_OVERRIDE=1, the correct production SPA passes the Day/Week Event, Log and Task moves under 200 ms with zero snap-back frames, and all forced 503 rollback cases run. Month touch placement passes, but the full run then fails the unchanged Month assertion selection follows the moved Log at calendar-drag-1015.mjs:170. The month-only path passes this selection check. This points to state carried from the preceding Day/Week interactions; I am tracing that case.
Author
Owner

Finding: the full production-SPA e2e reproduces the Month selection failure only after Day/Week setup and Month touch. The new pointerdown starts after longPressMenu has set firedAt; its 800 ms capture handler then calls stopImmediatePropagation on the next deliberate row click. Evidence: the click target is the moved Log row, no menu remains, the click suppression timer has reset, but the Svelte row handler does not run. The Month-only path passes. I am fixing the stale trailing-click guard in apps/web/src/lib/actions/longPressMenu.ts and adding a regression.

Finding: the full production-SPA e2e reproduces the Month selection failure only after Day/Week setup and Month touch. The new pointerdown starts after longPressMenu has set firedAt; its 800 ms capture handler then calls stopImmediatePropagation on the next deliberate row click. Evidence: the click target is the moved Log row, no menu remains, the click suppression timer has reset, but the Svelte row handler does not run. The Month-only path passes. I am fixing the stale trailing-click guard in apps/web/src/lib/actions/longPressMenu.ts and adding a regression.
Author
Owner

#1015 finished on job/round-drag.

Head: c2584756e3203ef71fb6b7b0109117ee8890ba38.

Built and fixed

The merged drag branch adds optimistic Day/Week Calendar placement, no snap-back during successful writes, stale-read protection, Undo, and Month moves. I fixed Month selection being swallowed by a stale touch long-press click guard. A new pointer press clears that guard; detail === 0 keyboard and assistive-technology clicks pass through. The regression test covers the trailing click and the next deliberate click.

Files: apps/web/src/lib/actions/longPressMenu.ts, apps/web/src/lib/gestures.svelte.test.ts, apps/web/src/lib/calendar/{data.ts,edits.ts,edits.test.ts,MonthGrid.svelte.test.ts}, apps/web/src/routes/calendar/[view]/[date]/+page.svelte, packages/ui/src/components/calendar/MonthGrid.svelte, apps/web/e2e/calendar-drag-1015.mjs, and bench/calendar-drag-1015.mjs.

Gates

cargo fmt --check: exit 0, no output. No Rust files changed, so Rust clippy/test gates do not apply.

bun run check:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/round-drag/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

Focused Vitest command: bunx vitest run src/lib/gestures.svelte.test.ts src/lib/calendar/edits.test.ts src/lib/calendar/data.test.ts src/lib/calendar/MonthGrid.svelte.test.ts src/lib/calendar/TimeGrid.svelte.test.ts --maxWorkers=2

 Test Files  5 passed (5)
      Tests  66 passed (66)
   Start at  21:44:12
   Duration  13.82s (transform 74%, import 9%, environment 9%, tests 5%, setup 2%)

Production web build:

✓ built in 10.82s
✓ built in 18ms
✓ built in 23.35s
  Wrote site to "build"
  ✔ done

Focused Calendar drag e2e used the production SPA asset override:

calendar drag #1015: frame geometry, persisted time, rollback, Month touch/keyboard and Undo passed

Local interaction timings

Measured by that e2e on the local debug server with production web assets. Successful moves had zero snap-back frames. Drop-to-settled: Day Event 148 ms, Day Log 133 ms, Week Event 149 ms, Week Log 138 ms. Month touch and keyboard placement, persistence after reload and Undo passed, but the script does not record Month drop timing. The 503 rollback cases intentionally show rollback frames.

The macOS screenshot matrix is in ignored artifacts/drag-1015/after: Day/Week/Month × 390/820/1440 px × paper/midnight. I could not attach these files with the installed fj issue CLI (it has no attachment upload command); the Git HTTPS credential helper also had no credential. The images remain local and were not added to Git.

Deferred for merge round

The non-merge-round verification rule defers full bun run test, the full Calendar e2e suite, release-image builds and staging deploys. No image was built, so there is no image tag. The bench profile was added but not measured: flock -n /root/perf.lock reported the perf VM lock busy, and I did not wait or measure locally.

Run in the merge round:

  • From apps/web: bun run test, bun run test:e2e:calendar, bun e2e/calendar-drag-1015.mjs, and bun e2e/calendar-preview-421.mjs. These prove the full web tests, Calendar suite, placement behavior, and preview regression on the merged head.
  • Fetch this head into /mnt/hdd/targets/staging-build/src, then run REV=c2584756e3203ef71fb6b7b0109117ee8890ba38 /mnt/hdd/targets/staging-build/build-rev.sh. That script builds the release image and invokes /home/kayg/.local/state/codex-jobs/calternal/deploy-staging.sh with its staging tag. On staging, drag an Event and a Log entry in Week and Month, then verify each settles in under one second, shows zero snap-back frames, and persists after reload.
  • When /root/perf.lock is free, run bench/calendar-drag-1015.mjs on the perf VM under flock /root/perf.lock; record the load average and compare p50/p95, CPU, RSS, and burst results with docs/perf/baseline.json.

UX gaps

Closed: Day/Week Event and Log moves, Month touch and keyboard moves, persistence after reload, Undo, refused-write rollback, and the deliberate click after long press. Keyboard and assistive clicks no longer get eaten by a stale pointer guard.

Left: staging timings and Month timing telemetry; full Calendar and web test suites; the locked perf profile run; issue attachments for the screenshot matrix.

Decisions

DESIGN §39 did not define the Month drop writer details. Month moves use the same optimistic Calendar writer as Day/Week and preserve the item's local clock interval. DESIGN §34 did not define stale long-press suppression across gestures. I scope it to its originating gesture by clearing it on the next pointer press, while allowing detail === 0 activation.

DRAG ROUND READY FOR PRODUCTION: no

#1015 finished on `job/round-drag`. Head: `c2584756e3203ef71fb6b7b0109117ee8890ba38`. ## Built and fixed The merged drag branch adds optimistic Day/Week Calendar placement, no snap-back during successful writes, stale-read protection, Undo, and Month moves. I fixed Month selection being swallowed by a stale touch long-press click guard. A new pointer press clears that guard; `detail === 0` keyboard and assistive-technology clicks pass through. The regression test covers the trailing click and the next deliberate click. Files: `apps/web/src/lib/actions/longPressMenu.ts`, `apps/web/src/lib/gestures.svelte.test.ts`, `apps/web/src/lib/calendar/{data.ts,edits.ts,edits.test.ts,MonthGrid.svelte.test.ts}`, `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`, `packages/ui/src/components/calendar/MonthGrid.svelte`, `apps/web/e2e/calendar-drag-1015.mjs`, and `bench/calendar-drag-1015.mjs`. ## Gates `cargo fmt --check`: exit 0, no output. No Rust files changed, so Rust clippy/test gates do not apply. `bun run check`: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/round-drag/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` Focused Vitest command: `bunx vitest run src/lib/gestures.svelte.test.ts src/lib/calendar/edits.test.ts src/lib/calendar/data.test.ts src/lib/calendar/MonthGrid.svelte.test.ts src/lib/calendar/TimeGrid.svelte.test.ts --maxWorkers=2` ```text Test Files 5 passed (5) Tests 66 passed (66) Start at 21:44:12 Duration 13.82s (transform 74%, import 9%, environment 9%, tests 5%, setup 2%) ``` Production web build: ```text ✓ built in 10.82s ✓ built in 18ms ✓ built in 23.35s Wrote site to "build" ✔ done ``` Focused Calendar drag e2e used the production SPA asset override: ```text calendar drag #1015: frame geometry, persisted time, rollback, Month touch/keyboard and Undo passed ``` ## Local interaction timings Measured by that e2e on the local debug server with production web assets. Successful moves had zero snap-back frames. Drop-to-settled: Day Event 148 ms, Day Log 133 ms, Week Event 149 ms, Week Log 138 ms. Month touch and keyboard placement, persistence after reload and Undo passed, but the script does not record Month drop timing. The 503 rollback cases intentionally show rollback frames. The macOS screenshot matrix is in ignored `artifacts/drag-1015/after`: Day/Week/Month × 390/820/1440 px × paper/midnight. I could not attach these files with the installed `fj issue` CLI (it has no attachment upload command); the Git HTTPS credential helper also had no credential. The images remain local and were not added to Git. ## Deferred for merge round The non-merge-round verification rule defers full `bun run test`, the full Calendar e2e suite, release-image builds and staging deploys. No image was built, so there is no image tag. The bench profile was added but not measured: `flock -n /root/perf.lock` reported the perf VM lock busy, and I did not wait or measure locally. Run in the merge round: - From `apps/web`: `bun run test`, `bun run test:e2e:calendar`, `bun e2e/calendar-drag-1015.mjs`, and `bun e2e/calendar-preview-421.mjs`. These prove the full web tests, Calendar suite, placement behavior, and preview regression on the merged head. - Fetch this head into `/mnt/hdd/targets/staging-build/src`, then run `REV=c2584756e3203ef71fb6b7b0109117ee8890ba38 /mnt/hdd/targets/staging-build/build-rev.sh`. That script builds the release image and invokes `/home/kayg/.local/state/codex-jobs/calternal/deploy-staging.sh` with its staging tag. On staging, drag an Event and a Log entry in Week and Month, then verify each settles in under one second, shows zero snap-back frames, and persists after reload. - When `/root/perf.lock` is free, run `bench/calendar-drag-1015.mjs` on the perf VM under `flock /root/perf.lock`; record the load average and compare p50/p95, CPU, RSS, and burst results with `docs/perf/baseline.json`. ## UX gaps Closed: Day/Week Event and Log moves, Month touch and keyboard moves, persistence after reload, Undo, refused-write rollback, and the deliberate click after long press. Keyboard and assistive clicks no longer get eaten by a stale pointer guard. Left: staging timings and Month timing telemetry; full Calendar and web test suites; the locked perf profile run; issue attachments for the screenshot matrix. ## Decisions DESIGN §39 did not define the Month drop writer details. Month moves use the same optimistic Calendar writer as Day/Week and preserve the item's local clock interval. DESIGN §34 did not define stale long-press suppression across gestures. I scope it to its originating gesture by clearing it on the next pointer press, while allowing `detail === 0` activation. DRAG ROUND READY FOR PRODUCTION: no
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#1015
No description provided.