Calendar: new entry starts at 15:30 inside the previous event (ends 15:33) — snap to item edges regressed (#536) #714

Open
opened 2026-10-02 10:41:26 +00:00 by kayg · 5 comments
Owner

Owner report (2026-10-02, calternal.cloud c4a61e8cf): "once again it does not start from the end of the last event?"

Screenshot: "Cab home 15:00 – 15:33" ends at 15:33. Creating a new entry right below it (drag-create from the grid) produces a ghost 15:30 – 16:10, which starts 3 minutes inside the previous event instead of at its end (15:33). This was decided in #536 ("snap to item edges > now > grid", Settings → Calendars → Dragging Items) and has regressed, or the snap is ignored for create.

Expected

  • When a create/move/resize gesture starts or ends within the snap distance of an existing item's edge, it snaps to that edge exactly (15:33), ahead of the "now" line and the 5/15-minute grid (#536 priority order). This applies to drag-create, click-create, move and both resize edges, in Day and Week, with pointer, touch and keyboard (keyboard nudges stop at item edges too).
  • A new entry never overlaps the previous one by snapping into it. If the pointer is inside the previous item's time range, the new item starts at that item's end.
  • The ghost labels show the snapped times (#612 style: start top-left, end bottom-right).

Tests

e2e like a User: an existing entry 15:00–15:33; drag-create from 15:31 to 16:10 → the new entry is 15:33–16:10, also after reload. The same for move and resize, and for the "now" line. Unit tests for the snap-priority function with edges at non-grid minutes (:33, :47) and in several timezones.

## Owner report (2026-10-02, calternal.cloud `c4a61e8cf`): "once again it does not start from the end of the last event?" Screenshot: "Cab home 15:00 – 15:33" ends at 15:33. Creating a new entry right below it (drag-create from the grid) produces a ghost **15:30 – 16:10**, which starts 3 minutes *inside* the previous event instead of at its end (15:33). This was decided in #536 ("snap to item edges > now > grid", Settings → Calendars → Dragging Items) and has regressed, or the snap is ignored for create. ## Expected - When a create/move/resize gesture starts or ends within the snap distance of an existing item's edge, it snaps to that edge **exactly** (15:33), ahead of the "now" line and the 5/15-minute grid (#536 priority order). This applies to drag-create, click-create, move and both resize edges, in Day and Week, with pointer, touch and keyboard (keyboard nudges stop at item edges too). - A new entry never overlaps the previous one by snapping into it. If the pointer is inside the previous item's time range, the new item starts at that item's end. - The ghost labels show the snapped times (#612 style: start top-left, end bottom-right). ## Tests e2e like a User: an existing entry 15:00–15:33; drag-create from 15:31 to 16:10 → the new entry is 15:33–16:10, also after reload. The same for move and resize, and for the "now" line. Unit tests for the snap-priority function with edges at non-grid minutes (:33, :47) and in several timezones.
Author
Owner

Starting #714 and #715 on branch job/snapedge-714, based on c4a61e8cf (origin/dev). I am tracing the shared Calendar snap path and will merge the approved #612 drag-ghost branch locally before implementing the fixes.

Starting #714 and #715 on branch job/snapedge-714, based on c4a61e8cf (origin/dev). I am tracing the shared Calendar snap path and will merge the approved #612 drag-ghost branch locally before implementing the fixes.
Author
Owner

Root-cause evidence: I called the production resolver with 15:31, 200 px/hour, a 15-minute floor, and the neighboring 15:00/15:33 edges. It returned 15:30 as a grid target because 15:31 is outside the 1.8-minute zoom-scaled magnetic radius. This permits the create ghost to overlap the item. Pointer create/move/resize use this resolver; Calendar keyboard handlers currently add the grid step directly.

Root-cause evidence: I called the production resolver with 15:31, 200 px/hour, a 15-minute floor, and the neighboring 15:00/15:33 edges. It returned 15:30 as a grid target because 15:31 is outside the 1.8-minute zoom-scaled magnetic radius. This permits the create ghost to overlap the item. Pointer create/move/resize use this resolver; Calendar keyboard handlers currently add the grid step directly.
Author
Owner

Starting/resuming job job/snapedge-714 from origin/dev base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. The worktree already contains the approved #612 drag ghost merge (e49b211ba) and uncommitted regression tests for #714 and #715; I am preserving them and checking their red state before changing implementation.

Starting/resuming job `job/snapedge-714` from `origin/dev` base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. The worktree already contains the approved #612 drag ghost merge (`e49b211ba`) and uncommitted regression tests for #714 and #715; I am preserving them and checking their red state before changing implementation.
Author
Owner

Findings from the resumed branch:

  • The #536 resolver checks only distance to an item's start/end edge. At high zoom, the six-pixel radius is under two minutes, so a create start at 15:31 with an item ending at 15:33 falls through to the 15-minute grid and becomes 15:30. It has no create-specific rule for a pointer inside an item interval. The new #714 model tests fail on that case and on the same wall-clock case in Europe/Berlin, Asia/Kolkata and America/Los_Angeles.
  • #612 changes the drag ghost presentation and snap feedback; it does not change resolveTimeSnap or its callers. The #609 week-state changes the shared date anchor/initialization, not the snap edge list or resolver. The post-#536 diff also shows no altered snap calculation.
  • In Composer, applyTime() writes every drag range into the input text. A second request with a different range prepends it, while the parser also renders time chips. The #715 component test fails with both ranges in the text.

Decision for the fix: keep drag-selected times as Composer time state and freeze them into the send snapshot. This keeps the text field for the title/body and lets repeated drags replace one time range.

Findings from the resumed branch: - The #536 resolver checks only distance to an item's start/end edge. At high zoom, the six-pixel radius is under two minutes, so a create start at 15:31 with an item ending at 15:33 falls through to the 15-minute grid and becomes 15:30. It has no create-specific rule for a pointer inside an item interval. The new #714 model tests fail on that case and on the same wall-clock case in Europe/Berlin, Asia/Kolkata and America/Los_Angeles. - #612 changes the drag ghost presentation and snap feedback; it does not change `resolveTimeSnap` or its callers. The #609 week-state changes the shared date anchor/initialization, not the snap edge list or resolver. The post-#536 diff also shows no altered snap calculation. - In Composer, `applyTime()` writes every drag range into the input text. A second request with a different range prepends it, while the parser also renders time chips. The #715 component test fails with both ranges in the text. Decision for the fix: keep drag-selected times as Composer time state and freeze them into the send snapshot. This keeps the text field for the title/body and lets repeated drags replace one time range.
Author
Owner

#714 implementation report

Head: 991251d7a.

Commits:

  • f8dd11a56 fixes Calendar item-edge snapping for creates and keyboard nudges.
  • b8c603d3c keeps Calendar-picked time ranges in Composer chips and extends the benchmark.
  • 991251d7a preserves the selected five-minute create endpoint in the e2e assertion.

The shared resolveTimeSnap() path now clamps create endpoints out of item interiors, respects off-grid boundaries such as 15:33, and stops keyboard nudges at crossed item edges. TimeGrid caches edge intervals for pointer frames. The Composer keeps dragged time ranges beside RAW text and saves the selected range.

Files changed: packages/ui/src/components/calendar/snap.ts, packages/ui/src/components/calendar/TimeGrid.svelte, packages/ui/src/components/calendar/model.ts, apps/web/src/lib/calendar/model.test.ts, apps/web/src/lib/composer/Composer.svelte, apps/web/src/lib/composer/Composer.svelte.test.ts, apps/web/src/lib/composer/commit.ts, apps/web/src/lib/composer/commit.test.ts, apps/web/src/lib/composer/drafts.svelte.ts, apps/web/src/lib/composer/drafts.test.ts, apps/web/e2e/calendar.mjs, bench/calendar-snap-536.mjs, and docs/perf/baseline.json.

Gates

bun run check:

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/snapedge-714/apps/web
Getting Svelte diagnostics...
svelte-check found 0 errors and 0 warnings

bun run test:

 Test Files  153 passed (153)
      Tests  1068 passed (1068)
   Start at  15:13:53
   Duration  160.67s (transform 62%, environment 16%, import 13%, tests 6%, setup 3%)

E2E and screenshots

The real-server create/save/reload path reached 201 Created and verified the saved 15:33 start and Composer chips in one focused run. Later focused runs did not finish: the test harness used stale grid-step preferences after Settings navigation and its pointer scroll math did not account for the rendered --ui-scale. The scale-aware helper is committed but was not rerun. A separate attempt could not prepare the media sandbox because the shared host had 4,569 local threads, over its 4,090 ceiling; a previously built bounded runtime was used for subsequent attempts.

Six screenshots exist with Mac platform emulation at 390, 820 and 1440 px in light and dark:

  • apps/web/artifacts/snapedge-714/calendar-snap-create-390-light-mac.png
  • apps/web/artifacts/snapedge-714/calendar-snap-create-390-dark-mac.png
  • apps/web/artifacts/snapedge-714/calendar-snap-create-820-light-mac.png
  • apps/web/artifacts/snapedge-714/calendar-snap-create-820-dark-mac.png
  • apps/web/artifacts/snapedge-714/calendar-snap-create-1440-light-mac.png
  • apps/web/artifacts/snapedge-714/calendar-snap-create-1440-dark-mac.png

The fj issue CLI exposes comments but no attachment command. These files remain in the ignored worktree artifacts directory and were not attached to this issue.

Decisions and gaps

  • The create end uses the selected five-minute grid (16:10); the adjacent item’s 15:33 edge takes priority for the create start.
  • Closed: dragged times no longer enter RAW text; repeated selections replace chips; Undo, draft reload, typed-time precedence, and commit snapshots have component/unit coverage.
  • Left: the final scale-aware real-browser e2e run is unverified; repeated drag replacement was covered by component tests, not a final live-server run. Screenshot uploads are still pending.
  • Mac check pending: on a real Mac, drag beside an event ending at 15:33, verify 15:33–16:10 chips and blank RAW text, save/reload, then repeat the drag and use ⌘Z to restore the prior range. The macOS VM is offline; the screenshots use Playwright platform emulation.

Performance measurement was local at load average 60.12 / 61.47 / 55.84. Cached create resolution measured average p50/p95 3.41 / 12.28 µs and worst p50/p95 3.39 / 4.60 µs. The prior #536 resolver baseline was average 4.36 / 13.27 µs, worst 41.44 / 70.17 µs; the uncached general resolver measured slower under this high host load (8.07 / 27.58 µs average, 63.70 / 173.97 µs worst). The full measurement is in docs/perf/baseline.json.

#714 implementation report Head: `991251d7a`. Commits: - `f8dd11a56` fixes Calendar item-edge snapping for creates and keyboard nudges. - `b8c603d3c` keeps Calendar-picked time ranges in Composer chips and extends the benchmark. - `991251d7a` preserves the selected five-minute create endpoint in the e2e assertion. The shared `resolveTimeSnap()` path now clamps create endpoints out of item interiors, respects off-grid boundaries such as `15:33`, and stops keyboard nudges at crossed item edges. `TimeGrid` caches edge intervals for pointer frames. The Composer keeps dragged time ranges beside RAW text and saves the selected range. Files changed: `packages/ui/src/components/calendar/snap.ts`, `packages/ui/src/components/calendar/TimeGrid.svelte`, `packages/ui/src/components/calendar/model.ts`, `apps/web/src/lib/calendar/model.test.ts`, `apps/web/src/lib/composer/Composer.svelte`, `apps/web/src/lib/composer/Composer.svelte.test.ts`, `apps/web/src/lib/composer/commit.ts`, `apps/web/src/lib/composer/commit.test.ts`, `apps/web/src/lib/composer/drafts.svelte.ts`, `apps/web/src/lib/composer/drafts.test.ts`, `apps/web/e2e/calendar.mjs`, `bench/calendar-snap-536.mjs`, and `docs/perf/baseline.json`. ## Gates `bun run check`: ```text 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/snapedge-714/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text Test Files 153 passed (153) Tests 1068 passed (1068) Start at 15:13:53 Duration 160.67s (transform 62%, environment 16%, import 13%, tests 6%, setup 3%) ``` ## E2E and screenshots The real-server create/save/reload path reached `201 Created` and verified the saved `15:33` start and Composer chips in one focused run. Later focused runs did not finish: the test harness used stale grid-step preferences after Settings navigation and its pointer scroll math did not account for the rendered `--ui-scale`. The scale-aware helper is committed but was not rerun. A separate attempt could not prepare the media sandbox because the shared host had 4,569 local threads, over its 4,090 ceiling; a previously built bounded runtime was used for subsequent attempts. Six screenshots exist with Mac platform emulation at 390, 820 and 1440 px in light and dark: - `apps/web/artifacts/snapedge-714/calendar-snap-create-390-light-mac.png` - `apps/web/artifacts/snapedge-714/calendar-snap-create-390-dark-mac.png` - `apps/web/artifacts/snapedge-714/calendar-snap-create-820-light-mac.png` - `apps/web/artifacts/snapedge-714/calendar-snap-create-820-dark-mac.png` - `apps/web/artifacts/snapedge-714/calendar-snap-create-1440-light-mac.png` - `apps/web/artifacts/snapedge-714/calendar-snap-create-1440-dark-mac.png` The `fj issue` CLI exposes comments but no attachment command. These files remain in the ignored worktree artifacts directory and were not attached to this issue. ## Decisions and gaps - The create end uses the selected five-minute grid (`16:10`); the adjacent item’s `15:33` edge takes priority for the create start. - Closed: dragged times no longer enter RAW text; repeated selections replace chips; Undo, draft reload, typed-time precedence, and commit snapshots have component/unit coverage. - Left: the final scale-aware real-browser e2e run is unverified; repeated drag replacement was covered by component tests, not a final live-server run. Screenshot uploads are still pending. - Mac check pending: on a real Mac, drag beside an event ending at `15:33`, verify `15:33–16:10` chips and blank RAW text, save/reload, then repeat the drag and use `⌘Z` to restore the prior range. The macOS VM is offline; the screenshots use Playwright platform emulation. Performance measurement was local at load average `60.12 / 61.47 / 55.84`. Cached create resolution measured average p50/p95 `3.41 / 12.28 µs` and worst p50/p95 `3.39 / 4.60 µs`. The prior #536 resolver baseline was average `4.36 / 13.27 µs`, worst `41.44 / 70.17 µs`; the uncached general resolver measured slower under this high host load (`8.07 / 27.58 µs` average, `63.70 / 173.97 µs` worst). The full measurement is in `docs/perf/baseline.json`.
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#714
No description provided.