BUG: toggling the sidebar shifts the Calendar Day/Week date (up to a year) #181

Closed
opened 2026-09-26 14:28:12 +00:00 by kayg · 5 comments
Owner

Owner 2026-09-26 screenshots: Day view Mon 10 Aug 2026 → after closing/opening the sidebar it shows Sat 31 Jul 2027, then Fri 14 Aug 2026.
Diagnosis (Claude): packages/ui/src/components/calendar/TimeGrid.svelte measure(): on every ResizeObserver callback during the sidebar animation it recomputes the anchor day as nearestIndex(scroller.scrollLeft, colw), but scrollLeft was already rewritten by the previous resize frame (and smooth scrolling / scroll-snap can still be in flight, and settle()/recentreShift can run from scrollend in between), so each frame's rounding error feeds the next and the date drifts; onvisible/onsettle then write the drifted date into the URL/title.
Fix: keep the anchor as a day index/date state that changes only on user scroll settle or navigation, never recomputed from scrollLeft inside measure(); in measure() set scrollLeft = anchor * newColw with instant behaviour (scrollTo({left, behavior:'instant'}), temporarily disable scroll-snap), suppress onvisible/onsettle while a resize is in progress (programmatic flag until the resize settles), and never let recentreShift run mid-resize.
Tests: unit test for the anchor math across 30 successive width changes (no drift), and an e2e that toggles the sidebar 10 times (and resizes the window) in Day and Week views and asserts the URL date and title never change. Also check Month/Year views for the same class of bug.

Owner 2026-09-26 screenshots: Day view Mon 10 Aug 2026 → after closing/opening the sidebar it shows Sat 31 Jul 2027, then Fri 14 Aug 2026. Diagnosis (Claude): packages/ui/src/components/calendar/TimeGrid.svelte measure(): on every ResizeObserver callback during the sidebar animation it recomputes the anchor day as nearestIndex(scroller.scrollLeft, colw), but scrollLeft was already rewritten by the previous resize frame (and smooth scrolling / scroll-snap can still be in flight, and settle()/recentreShift can run from scrollend in between), so each frame's rounding error feeds the next and the date drifts; onvisible/onsettle then write the drifted date into the URL/title. Fix: keep the anchor as a day index/date state that changes only on user scroll settle or navigation, never recomputed from scrollLeft inside measure(); in measure() set scrollLeft = anchor * newColw with instant behaviour (scrollTo({left, behavior:'instant'}), temporarily disable scroll-snap), suppress onvisible/onsettle while a resize is in progress (programmatic flag until the resize settles), and never let recentreShift run mid-resize. Tests: unit test for the anchor math across 30 successive width changes (no drift), and an e2e that toggles the sidebar 10 times (and resizes the window) in Day and Week views and asserts the URL date and title never change. Also check Month/Year views for the same class of bug.
Author
Owner

Started work on branch job/grid-resize, based on dev at 6e1e565603. I am reviewing the resize/date flow and existing Calendar test harness before making the focused fix.

Started work on branch job/grid-resize, based on dev at 6e1e5656036eaeb060df3089043519aab2066376. I am reviewing the resize/date flow and existing Calendar test harness before making the focused fix.
Author
Owner

Source review confirmed the reported chain: TimeGrid.measure() derives the day index from scrollLeft on each width change, while scrollend can still call settle() and recenter the track. readScroll() reports the transient day through onvisible(), and settle() can send it to the page URL. The fix now keeps a separate settled/navigation index, reprojects it at the new width, and defers visible/settled callbacks and recentering until the resize quiet period ends.

Source review confirmed the reported chain: TimeGrid.measure() derives the day index from scrollLeft on each width change, while scrollend can still call settle() and recenter the track. readScroll() reports the transient day through onvisible(), and settle() can send it to the page URL. The fix now keeps a separate settled/navigation index, reprojects it at the new width, and defers visible/settled callbacks and recentering until the resize quiet period ends.
Author
Owner

The current clean base also blocks the requested real-server e2e setup: CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 TMPDIR=/target/tmp cargo build -p calternal-server fails in unmodified crates/plugins/files/src/index.rs:620 with E0308 (expected bool, found ()). This is outside this job's files, so I have not changed the Files plugin.

The current clean base also blocks the requested real-server e2e setup: CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 TMPDIR=<worktree>/target/tmp cargo build -p calternal-server fails in unmodified crates/plugins/files/src/index.rs:620 with E0308 (expected bool, found ()). This is outside this job's files, so I have not changed the Files plugin.
Author
Owner

Completed Forgejo #181 on job/grid-resize. I merged dev before the final gates. Head: 1f19b4ab0f65c60583c902c04de2231d69313e33.

What changed

  • TimeGrid.svelte now keeps the last settled or navigated day index as the resize anchor. ResizeObserver frames reproject that index at the new column width; resize-time scroll and settle callbacks no longer feed fractional scroll positions back into the date.
  • Added a unit test for repeated column-width changes and a production e2e that toggles the sidebar and viewport across Day, Week, Month, and Year.
  • The production e2e reproduced the old bug: after ten sidebar/window resize cycles, /calendar/day/2026-08-10 drifted to /calendar/day/2026-11-27. The fixed e2e reports: calendar resize e2e: all views kept their dates and titles.

Screenshots for review:

  • Before: /home/kayg/Developer/calternal-wt/grid-resize-review/before/day-before.png, /home/kayg/Developer/calternal-wt/grid-resize-review/before/day-after.png
  • After: /home/kayg/Developer/calternal-wt/grid-resize-review/after/day-before.png, /home/kayg/Developer/calternal-wt/grid-resize-review/after/day-after.png, /home/kayg/Developer/calternal-wt/grid-resize-review/after/week-before.png, /home/kayg/Developer/calternal-wt/grid-resize-review/after/week-after.png

Gates

  • cargo fmt --check: passed, exit 0, no output.
  • bun run check: svelte-check found 0 errors and 0 warnings.
  • Production build: ✓ built in 2m 24s; Wrote site to "build".
  • Calendar resize e2e: calendar resize e2e: all views kept their dates and titles.
  • bun run test --maxWorkers=1 --fileParallelism=false: 418 passed; one unrelated theme contrast test timed out at 5 seconds:
    Test Files  1 failed | 52 passed (53)
         Tests  1 failed | 418 passed (419)
    Error: Test timed out in 5000ms.
    
    That test passed alone with a 20-second timeout: Tests 1 passed | 54 skipped (55).
  • cargo clippy --all-targets -- -D warnings and cargo test stop at the existing compile error outside this job:
    error[E0308]: mismatched types
       --> crates/plugins/files/src/index.rs:620:19
    620 |         return Ok(());
        |                -- ^^ expected `bool`, found `()`
    error: could not compile `calternal-plugin-files` (lib) due to 1 previous error
    
  • Cleanup: Removed 15090 files, 5.9GiB total; removed apps/web/build and apps/web/.svelte-kit.

Decision

The design doc does not specify resize callback timing. I kept the last settled or navigated day as the anchor during resizing, deferred settle and date publication until 140 ms without a resize frame, then reconciled the visible date once.

Known gaps: the unrelated Files compile error blocks the full Rust gates. The full web suite had one load-sensitive 5-second timeout; the isolated test passed with a longer timeout.

Completed Forgejo #181 on `job/grid-resize`. I merged `dev` before the final gates. Head: `1f19b4ab0f65c60583c902c04de2231d69313e33`. **What changed** - `TimeGrid.svelte` now keeps the last settled or navigated day index as the resize anchor. ResizeObserver frames reproject that index at the new column width; resize-time scroll and settle callbacks no longer feed fractional scroll positions back into the date. - Added a unit test for repeated column-width changes and a production e2e that toggles the sidebar and viewport across Day, Week, Month, and Year. - The production e2e reproduced the old bug: after ten sidebar/window resize cycles, `/calendar/day/2026-08-10` drifted to `/calendar/day/2026-11-27`. The fixed e2e reports: `calendar resize e2e: all views kept their dates and titles`. Screenshots for review: - Before: `/home/kayg/Developer/calternal-wt/grid-resize-review/before/day-before.png`, `/home/kayg/Developer/calternal-wt/grid-resize-review/before/day-after.png` - After: `/home/kayg/Developer/calternal-wt/grid-resize-review/after/day-before.png`, `/home/kayg/Developer/calternal-wt/grid-resize-review/after/day-after.png`, `/home/kayg/Developer/calternal-wt/grid-resize-review/after/week-before.png`, `/home/kayg/Developer/calternal-wt/grid-resize-review/after/week-after.png` **Gates** - `cargo fmt --check`: passed, exit 0, no output. - `bun run check`: `svelte-check found 0 errors and 0 warnings`. - Production build: `✓ built in 2m 24s`; `Wrote site to "build"`. - Calendar resize e2e: `calendar resize e2e: all views kept their dates and titles`. - `bun run test --maxWorkers=1 --fileParallelism=false`: 418 passed; one unrelated theme contrast test timed out at 5 seconds: ``` Test Files 1 failed | 52 passed (53) Tests 1 failed | 418 passed (419) Error: Test timed out in 5000ms. ``` That test passed alone with a 20-second timeout: `Tests 1 passed | 54 skipped (55)`. - `cargo clippy --all-targets -- -D warnings` and `cargo test` stop at the existing compile error outside this job: ``` error[E0308]: mismatched types --> crates/plugins/files/src/index.rs:620:19 620 | return Ok(()); | -- ^^ expected `bool`, found `()` error: could not compile `calternal-plugin-files` (lib) due to 1 previous error ``` - Cleanup: `Removed 15090 files, 5.9GiB total`; removed `apps/web/build` and `apps/web/.svelte-kit`. **Decision** The design doc does not specify resize callback timing. I kept the last settled or navigated day as the anchor during resizing, deferred settle and date publication until 140 ms without a resize frame, then reconciled the visible date once. Known gaps: the unrelated Files compile error blocks the full Rust gates. The full web suite had one load-sensitive 5-second timeout; the isolated test passed with a longer timeout.
kayg referenced this issue from a commit 2026-09-26 16:41:51 +00:00
Author
Owner

Merged into dev at 61bf7070 (Claude reviewed the before/after screenshots: the date holds across sidebar toggles). Deploy status on #203.

Merged into dev at 61bf7070 (Claude reviewed the before/after screenshots: the date holds across sidebar toggles). Deploy status on #203.
kayg closed this issue 2026-09-26 16:42:00 +00:00
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#181
No description provided.