Calendar Week: three overlapping entries squeeze a card into a ~40px lane with a clipped time at max zoom #969

Open
opened 2026-10-03 04:54:40 +00:00 by kayg · 5 comments
Owner

Problem

In Week view at 1440 px and the highest zoom (hour height 160), three entries that overlap at 09:00 split one day column into narrow lanes. One card gets a lane of about 40 px. Its time chip is clipped to a single "0", and its title and attachment deck do not show. Evidence: artifacts/agenda-decks-822/week-1440-light-dense.png from the agenda-decks job (#822), fixture "Dense deck review" 09:00–10:30, "Other lane" 09:15–09:45, "Short deck" 09:30–09:31.

The lane packing comes from the existing overlap layout (packages/ui/src/components/calendar/layout.ts, GridColumn.svelte, drawnLaneCount, cramped). The agenda-decks job did not change it.

Expected

  • A card never gets a lane too narrow for its clock and one title line. Below a minimum lane width (about one title word plus padding), fold the extra entries into the existing "+N" lane (#384), as the narrow phone Week already does.
  • A cramped card hides its time row rather than clipping it to a fragment. It keeps the title and a Micro attachment count.
  • The rule depends on measured column width and lane count only (no text measurement). It holds at 1-, 3- and 7-day views, at 390/820/1440, and at every zoom.

Tests

Layout checks in apps/web/e2e/agenda-decks-822.mjs: at every width and zoom, each drawn card is at least the minimum lane width and no time chip is clipped. Screenshots with macOS emulation, light and dark.

## Problem In Week view at 1440 px and the highest zoom (hour height 160), three entries that overlap at 09:00 split one day column into narrow lanes. One card gets a lane of about 40 px. Its time chip is clipped to a single "0", and its title and attachment deck do not show. Evidence: `artifacts/agenda-decks-822/week-1440-light-dense.png` from the agenda-decks job (#822), fixture "Dense deck review" 09:00–10:30, "Other lane" 09:15–09:45, "Short deck" 09:30–09:31. The lane packing comes from the existing overlap layout (`packages/ui/src/components/calendar/layout.ts`, `GridColumn.svelte`, `drawnLaneCount`, `cramped`). The agenda-decks job did not change it. ## Expected - A card never gets a lane too narrow for its clock and one title line. Below a minimum lane width (about one title word plus padding), fold the extra entries into the existing "+N" lane (#384), as the narrow phone Week already does. - A cramped card hides its time row rather than clipping it to a fragment. It keeps the title and a Micro attachment count. - The rule depends on measured column width and lane count only (no text measurement). It holds at 1-, 3- and 7-day views, at 390/820/1440, and at every zoom. ## Tests Layout checks in `apps/web/e2e/agenda-decks-822.mjs`: at every width and zoom, each drawn card is at least the minimum lane width and no time chip is clipped. Screenshots with macOS emulation, light and dark.
Author
Owner

Starting weeklane-969 on branch job/weeklane-969. Base SHA: d4e7188810 (origin/dev at start). I read CLAUDE.md, CONTEXT.md and Calendar grid decisions in docs/DESIGN.md §§29–30, 33–34, 38–39. I will keep the change in the existing overlap layout and its regression coverage.

Starting weeklane-969 on branch job/weeklane-969. Base SHA: d4e7188810a89fb0e8e6b162279917f7e23989f9 (origin/dev at start). I read CLAUDE.md, CONTEXT.md and Calendar grid decisions in docs/DESIGN.md §§29–30, 33–34, 38–39. I will keep the change in the existing overlap layout and its regression coverage.
Author
Owner

Correction: The original comment omitted the CSS selector .b-content in its final sentence. See the corrected finding in the next comment.

Correction: The original comment omitted the CSS selector `.b-content` in its final sentence. See the corrected finding in the next comment.
Author
Owner

Finding: TimeGrid gives every Week column a 36 px lane floor, and GridColumn computes normal capacity as floor(columnWidth / floor). Three entries can therefore stay visible below a title-line width. The cramped rule also uses that uncapped estimate; planned cards then hide all of their content, including the title. I added CSS-derived lane-capacity helpers and focused geometry tests first; these use the existing 8 px insets, 2 px gap and 34 px +N reservation.

Finding: TimeGrid gives every Week column a 36 px lane floor, and GridColumn computes normal capacity as floor(columnWidth / floor). Three entries can therefore stay visible below a title-line width. The cramped rule also uses that uncapped estimate; planned cards then hide all of their content, including the title. I added CSS-derived lane-capacity helpers and focused geometry tests first; these use the existing 8 px insets, 2 px gap and 34 px +N reservation.
Author
Owner

UX finding: At 390 px and 820 px, the +N pill can be the only visible way to open an overlap cluster. Its painted height was 18 px, and the button had no enlarged touch target. I added the shared touch-hit area without changing the pill. The production browser regression checks a 44 × 44 px hit area on coarse pointers, opens the full cluster by touch and Enter, and closes it with Escape.

UX finding: At 390 px and 820 px, the +N pill can be the only visible way to open an overlap cluster. Its painted height was 18 px, and the button had no enlarged touch target. I added the shared `touch-hit` area without changing the pill. The production browser regression checks a 44 × 44 px hit area on coarse pointers, opens the full cluster by touch and Enter, and closes it with Escape.
Author
Owner

Finished #969

Built

  • Week overlap lanes now keep a 64 px minimum at the shared UI scale. A column that cannot fit a lane puts the whole cluster behind +N.
  • Cards below 96 px hide the clock. Cramped cards keep their title; Logs also keep a Micro paperclip attachment count.
  • +N uses the shared invisible touch target. The production regression opens its complete cluster by touch/pointer and Enter, and closes it with Escape.
  • Width and clock decisions use CSS geometry only. They do not measure text or depend on zoom.

Files

  • packages/ui/src/components/calendar/layout.ts
  • packages/ui/src/components/calendar/GridColumn.svelte
  • packages/ui/src/components/calendar/TimeGrid.svelte
  • apps/web/src/lib/calendar/model.test.ts
  • apps/web/e2e/agenda-decks-822.mjs
  • apps/web/package.json
  • docs/DESIGN.md

UX gaps closed

  • Small overlap cards no longer lose their title, clock text cannot clip, and attached Logs retain their count.
  • The +N action has a 44 × 44 px coarse-pointer hit area and the full cluster remains accessible by touch, pointer, keyboard and Escape.

UX gaps left / known behavior

  • When no lane can meet the 64 px floor, the grid shows only +N. The action opens the complete cluster; this is the intended zero-capacity behavior.

Decisions not already set by DESIGN

  • Set the Week lane floor to 64 px and the clock cutoff to 96 px at UI scale 1.
  • The three-column geometry case in the unit test checks math only; it does not add a three-day Calendar view.

Verification

  • git fetch origin && git merge origin/dev: Already up to date.
  • cargo fmt --check: exit 0, no output.
  • bun run check output:
$ 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/weeklane-969/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings
  • bunx vitest run src/lib/calendar/model.test.ts --maxWorkers=2 output:
 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/weeklane-969/apps/web

 Test Files  1 passed (1)
      Tests  31 passed (31)
   Start at  15:59:33
   Duration  12.70s (transform 91%, import 9%, tests 1%)
  • bun run test:e2e:agenda-decks-822: passed. It checked Week zooms 24/44/84/120/160 and Day at 160 at 390/820/1440, light/dark, with macOS platform emulation. All 18 screenshots are attached below.
  • Production build: ✓ built in 42.48s.
  • Rust clippy/tests were not run because this job changed no Rust code. The local calternal-server build for e2e finished: Finished dev profile [unoptimized + debuginfo] target(s) in 69m 05s.
  • Cleanup: cargo clean reported Removed 7237 files, 4.6GiB total; apps/web/build and .svelte-kit/output were deleted.

Screenshots (macOS emulated)

Week full captures:

Week 3× overlap crops:

Day captures:

Head SHA: 8a7e665bfa20aea11e812dfd7dfe0335997c2ccb

**Finished #969** **Built** - Week overlap lanes now keep a 64 px minimum at the shared UI scale. A column that cannot fit a lane puts the whole cluster behind +N. - Cards below 96 px hide the clock. Cramped cards keep their title; Logs also keep a Micro paperclip attachment count. - +N uses the shared invisible touch target. The production regression opens its complete cluster by touch/pointer and Enter, and closes it with Escape. - Width and clock decisions use CSS geometry only. They do not measure text or depend on zoom. **Files** - `packages/ui/src/components/calendar/layout.ts` - `packages/ui/src/components/calendar/GridColumn.svelte` - `packages/ui/src/components/calendar/TimeGrid.svelte` - `apps/web/src/lib/calendar/model.test.ts` - `apps/web/e2e/agenda-decks-822.mjs` - `apps/web/package.json` - `docs/DESIGN.md` **UX gaps closed** - Small overlap cards no longer lose their title, clock text cannot clip, and attached Logs retain their count. - The +N action has a 44 × 44 px coarse-pointer hit area and the full cluster remains accessible by touch, pointer, keyboard and Escape. **UX gaps left / known behavior** - When no lane can meet the 64 px floor, the grid shows only +N. The action opens the complete cluster; this is the intended zero-capacity behavior. **Decisions not already set by DESIGN** - Set the Week lane floor to 64 px and the clock cutoff to 96 px at UI scale 1. - The three-column geometry case in the unit test checks math only; it does not add a three-day Calendar view. **Verification** - `git fetch origin && git merge origin/dev`: `Already up to date.` - `cargo fmt --check`: exit 0, no output. - `bun run check` output: ```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/weeklane-969/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` - `bunx vitest run src/lib/calendar/model.test.ts --maxWorkers=2` output: ```text RUN v5.0.1 /home/kayg/Developer/calternal-wt/weeklane-969/apps/web Test Files 1 passed (1) Tests 31 passed (31) Start at 15:59:33 Duration 12.70s (transform 91%, import 9%, tests 1%) ``` - `bun run test:e2e:agenda-decks-822`: passed. It checked Week zooms 24/44/84/120/160 and Day at 160 at 390/820/1440, light/dark, with macOS platform emulation. All 18 screenshots are attached below. - Production build: `✓ built in 42.48s`. - Rust clippy/tests were not run because this job changed no Rust code. The local `calternal-server` build for e2e finished: `Finished dev profile [unoptimized + debuginfo] target(s) in 69m 05s`. - Cleanup: `cargo clean` reported `Removed 7237 files, 4.6GiB total`; `apps/web/build` and `.svelte-kit/output` were deleted. **Screenshots (macOS emulated)** Week full captures: - [390 light](https://git.kayg.org/attachments/d0234eb6-6d7c-4f4c-bd3b-7ef99f1bbbbe) · [390 dark](https://git.kayg.org/attachments/33008e45-ce86-4a71-b591-fa8c9f22541d) - [820 light](https://git.kayg.org/attachments/115a9e8a-eba0-4ec4-8911-d2d758363330) · [820 dark](https://git.kayg.org/attachments/fa1f6524-ba26-44ff-9095-ad6f562b4a0f) - [1440 light](https://git.kayg.org/attachments/cf5643f0-1d3c-4d2b-bfb1-f922d1c95426) · [1440 dark](https://git.kayg.org/attachments/d25631fd-08e8-4169-b02b-f039e42cd168) Week 3× overlap crops: - [390 light](https://git.kayg.org/attachments/b620fd36-e21e-426f-a460-0bd357adf6e1) · [390 dark](https://git.kayg.org/attachments/381d19d7-3ac7-4022-b26d-7e5005bd212b) - [820 light](https://git.kayg.org/attachments/87eead74-7ed6-4b72-aa63-a6629a292136) · [820 dark](https://git.kayg.org/attachments/c9af1cb5-7c52-4ada-84e9-a16ca904a02a) - [1440 light](https://git.kayg.org/attachments/527903d2-4f38-4822-be28-7b19d87e6eb2) · [1440 dark](https://git.kayg.org/attachments/f15d94eb-ab25-493d-a6b9-06c17d0c7b86) Day captures: - [390 light](https://git.kayg.org/attachments/5ac86d68-5d31-4753-9a6a-2d48324f1f86) · [390 dark](https://git.kayg.org/attachments/b1c1c2fe-4de8-41c7-ac4b-abcb66a50781) - [820 light](https://git.kayg.org/attachments/46a2724c-d932-4561-8dfe-5a785925e4f4) · [820 dark](https://git.kayg.org/attachments/17eaf05d-a8be-4e28-8638-dcb60963a333) - [1440 light](https://git.kayg.org/attachments/c71b5db4-41cb-441d-bfaa-1d5bed0e585b) · [1440 dark](https://git.kayg.org/attachments/50270abd-a9c3-4774-8300-f99863e222b2) **Head SHA:** `8a7e665bfa20aea11e812dfd7dfe0335997c2ccb`
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#969
No description provided.