Date and time formats: one formatter, Settings choice, default = system locale; fix the calendar preview header #179

Closed
opened 2026-09-26 14:18:32 +00:00 by kayg · 25 comments
Owner

Owner 2026-09-26 (screenshots: calendar preview says 'Note · saved · Saturday, September 26' wrapping onto two lines, and dates appear in different formats across the app; reference: macOS Settings → Language & Region, which lists date formats as examples like 19/08/26, 8/19/26, 19.08.2026, 2026-08-19 and number format).

  1. One formatting module for the whole web app (dates, times, date+time, relative 'Edited yesterday', weekday names, month names, durations). Delete every ad-hoc Intl.DateTimeFormat / toLocale* / hand-built date string in apps/web and packages/ui and route them through it (reuse gate; a unit test or lint that fails on new direct uses).
  2. Settings → Calendar gets: Date format (default 'System ()' = the browser/OS locale; then a list shown as live examples of today's date like macOS: 26/09/26, 9/26/26, 26/9/26, 9/26/2026, 26/09/2026, 26.09.2026, 26-09-2026, 2026/9/26, 2026.09.26, 2026-09-26), Time format (System, 24-hour, 12-hour), First day of week (System, Sunday, Monday, Saturday). A live preview line at the top like macOS. Stored per user in the existing user settings (reuse the settings file and route; DESIGN §41 one route). The CLI's human output uses the same preference.
  3. Long dates (headers, previews) use the user's order and month names from their locale; short dates use the chosen format.
  4. Calendar preview popover fix (same screenshot): the kind/state/date line must not wrap (shorter form: 'Note · Sat 26 Sep' in the user's format, truncate with ellipsis if still too long), and the title plus time appear once, not twice (the header shows title and time; the attachment row below repeats them: drop the duplicate or show the note's first line instead).
    Screenshots of Settings → Calendar and several date surfaces (Calendar headers, Agenda, previews, Files 'Modified', search 'Edited') in two locales (en-IN, en-US), light and dark, for Claude's review.
Owner 2026-09-26 (screenshots: calendar preview says 'Note · saved · Saturday, September 26' wrapping onto two lines, and dates appear in different formats across the app; reference: macOS Settings → Language & Region, which lists date formats as examples like 19/08/26, 8/19/26, 19.08.2026, 2026-08-19 and number format). 1. One formatting module for the whole web app (dates, times, date+time, relative 'Edited yesterday', weekday names, month names, durations). Delete every ad-hoc Intl.DateTimeFormat / toLocale* / hand-built date string in apps/web and packages/ui and route them through it (reuse gate; a unit test or lint that fails on new direct uses). 2. Settings → Calendar gets: Date format (default 'System (<example>)' = the browser/OS locale; then a list shown as live examples of today's date like macOS: 26/09/26, 9/26/26, 26/9/26, 9/26/2026, 26/09/2026, 26.09.2026, 26-09-2026, 2026/9/26, 2026.09.26, 2026-09-26), Time format (System, 24-hour, 12-hour), First day of week (System, Sunday, Monday, Saturday). A live preview line at the top like macOS. Stored per user in the existing user settings (reuse the settings file and route; DESIGN §41 one route). The CLI's human output uses the same preference. 3. Long dates (headers, previews) use the user's order and month names from their locale; short dates use the chosen format. 4. Calendar preview popover fix (same screenshot): the kind/state/date line must not wrap (shorter form: 'Note · Sat 26 Sep' in the user's format, truncate with ellipsis if still too long), and the title plus time appear once, not twice (the header shows title and time; the attachment row below repeats them: drop the duplicate or show the note's first line instead). Screenshots of Settings → Calendar and several date surfaces (Calendar headers, Agenda, previews, Files 'Modified', search 'Edited') in two locales (en-IN, en-US), light and dark, for Claude's review.
Author
Owner

Starting #179 on branch job/date-formats, based on dev at 6e1e5656036eaeb060df3089043519aab2066376. I have read CLAUDE.md, CONTEXT.md, and the Calendar, search, deep-link, and chrome decisions in docs/DESIGN.md. I will implement the shared formatter, the existing user settings path, CLI human-output preference, call-site migration, and the preview fix in separate commits. No design decision in the issue is being changed.

Starting #179 on branch `job/date-formats`, based on `dev` at `6e1e5656036eaeb060df3089043519aab2066376`. I have read `CLAUDE.md`, `CONTEXT.md`, and the Calendar, search, deep-link, and chrome decisions in `docs/DESIGN.md`. I will implement the shared formatter, the existing user settings path, CLI human-output preference, call-site migration, and the preview fix in separate commits. No design decision in the issue is being changed.
Author
Owner

Finding: the reuse audit counted 29 production source files under apps/web/src and packages/ui/src with direct Intl.DateTimeFormat or toLocale* formatting. packages/ui/src/time.ts already provides a cached shared formatter, and /api/v1/calendar/preferences already stores per-User Calendar settings. I am extending those existing units and migrating the existing call sites; the new source-scan test records the reuse gate.

Finding: the reuse audit counted 29 production source files under `apps/web/src` and `packages/ui/src` with direct `Intl.DateTimeFormat` or `toLocale*` formatting. `packages/ui/src/time.ts` already provides a cached shared formatter, and `/api/v1/calendar/preferences` already stores per-User Calendar settings. I am extending those existing units and migrating the existing call sites; the new source-scan test records the reuse gate.
Author
Owner

Finding: the shared formatter normalized malformed local date 2026-02-30 to 2026-03-02. Added a regression case and changed the formatter to reject dates that fail a local-part round trip; bun x vitest run --maxWorkers=1 --fileParallelism=false src/lib/time.test.ts src/lib/date-formatting.test.ts now passes (2 files, 9 tests).

Finding: the shared formatter normalized malformed local date `2026-02-30` to `2026-03-02`. Added a regression case and changed the formatter to reject dates that fail a local-part round trip; `bun x vitest run --maxWorkers=1 --fileParallelism=false src/lib/time.test.ts src/lib/date-formatting.test.ts` now passes (2 files, 9 tests).
Author
Owner

Finding during cargo test -p calternal-plugin-calendar format_preferences_round_trip_and_validate: compilation stops first in crates/plugins/files/src/index.rs:620, where record_once returns Result<bool, Failure> but the unchanged fast path returns Ok(()) (E0308). This file is outside the date-formats call-site scope; I will check the required incoming dev merge for a fix before final gates.

Finding during `cargo test -p calternal-plugin-calendar format_preferences_round_trip_and_validate`: compilation stops first in `crates/plugins/files/src/index.rs:620`, where `record_once` returns `Result<bool, Failure>` but the unchanged fast path returns `Ok(())` (E0308). This file is outside the date-formats call-site scope; I will check the required incoming `dev` merge for a fix before final gates.
Author
Owner

Finding / scope decision: calternal-cli has no human date renderer today. Normal mode prints the same API value as pretty JSON (serde_json::to_string_pretty); --json prints the compact machine form. I will keep those canonical API date/path values intact instead of adding a second Rust formatter that would diverge from the shared web module.

Finding / scope decision: `calternal-cli` has no human date renderer today. Normal mode prints the same API value as pretty JSON (`serde_json::to_string_pretty`); `--json` prints the compact machine form. I will keep those canonical API date/path values intact instead of adding a second Rust formatter that would diverge from the shared web module.
Author
Owner

Preview decision: the en-IN runtime formats September's short localized month as Sept (the targeted formatter test observed Sat 26 Sept). The non-wrapping preview keeps that locale form rather than trimming it to Sep; custom date order still controls the day/month order.

Preview decision: the en-IN runtime formats September's short localized month as `Sept` (the targeted formatter test observed `Sat 26 Sept`). The non-wrapping preview keeps that locale form rather than trimming it to `Sep`; custom date order still controls the day/month order.
Author
Owner

Regression evidence: the Composer test expected fixed 24-hour chips while the shared formatter emitted 9:00 AM under the test runner's en-US System locale. I made the component test set 24_hour explicitly and restore System afterward; product behavior remains locale driven. Verified: Test Files 1 passed (1) and Tests 21 passed (21).

Regression evidence: the Composer test expected fixed 24-hour chips while the shared formatter emitted `9:00 AM` under the test runner's en-US System locale. I made the component test set `24_hour` explicitly and restore System afterward; product behavior remains locale driven. Verified: `Test Files 1 passed (1)` and `Tests 21 passed (21)`.
Author
Owner

After merging the current dev (e976bff6), crates/plugins/files/src/index.rs:620 still returns Ok(()) from a Result<bool, Failure> function. This keeps the targeted calendar Cargo test and server OpenAPI generation from compiling. The correction is present on local job/agenda at 9fd9d184, but is not in dev yet; I have left that out-of-scope file unchanged.

After merging the current `dev` (`e976bff6`), `crates/plugins/files/src/index.rs:620` still returns `Ok(())` from a `Result<bool, Failure>` function. This keeps the targeted calendar Cargo test and server OpenAPI generation from compiling. The correction is present on local `job/agenda` at `9fd9d184`, but is not in `dev` yet; I have left that out-of-scope file unchanged.
Author
Owner

The API build blocker also prevents cargo run -p calternal-server -- openapi. To keep the shared TypeScript client aligned with the committed Rust schema, I added the three enum schemas and CalendarPreferences fields to contracts/openapi.json, then regenerated packages/api-client/src/generated.ts with the pinned openapi-typescript@7.13.0. I will compare this contract with server output if the agenda fix reaches dev before final gates.

The API build blocker also prevents `cargo run -p calternal-server -- openapi`. To keep the shared TypeScript client aligned with the committed Rust schema, I added the three enum schemas and CalendarPreferences fields to `contracts/openapi.json`, then regenerated `packages/api-client/src/generated.ts` with the pinned `openapi-typescript@7.13.0`. I will compare this contract with server output if the agenda fix reaches `dev` before final gates.
Author
Owner

Finding during the required dev merge: the formatter reuse gate initially scanned only .ts and .svelte, so a TSX fixture containing new Intl.DateTimeFormat(...) passed undetected. I added a fixture test; it failed with expected [] and showed the gap. The same run found direct formatter calls in the newly merged apps/web/src/lib/analytics/store.svelte.ts timezone lookup and apps/web/src/lib/components/analytics/HabitsCard.svelte weekday/hour labels. I am moving those through the shared UI formatter and expanding the gate to TSX.

Finding during the required `dev` merge: the formatter reuse gate initially scanned only `.ts` and `.svelte`, so a TSX fixture containing `new Intl.DateTimeFormat(...)` passed undetected. I added a fixture test; it failed with `expected []` and showed the gap. The same run found direct formatter calls in the newly merged `apps/web/src/lib/analytics/store.svelte.ts` timezone lookup and `apps/web/src/lib/components/analytics/HabitsCard.svelte` weekday/hour labels. I am moving those through the shared UI formatter and expanding the gate to TSX.
Author
Owner

Merge finding: the first focused web test stopped at package parsing because the merged packages/ui/src/index.ts exported formatHours twice (once in the shared formatter exports and once in the Calendar model exports). I kept the single shared export. The same merge resolution keeps the new Calendar preference fields inside user_settings::update, preserving dev's checked retry behavior while persisting date format, time format and week start.

Merge finding: the first focused web test stopped at package parsing because the merged `packages/ui/src/index.ts` exported `formatHours` twice (once in the shared formatter exports and once in the Calendar model exports). I kept the single shared export. The same merge resolution keeps the new Calendar preference fields inside `user_settings::update`, preserving `dev`'s checked retry behavior while persisting date format, time format and week start.
Author
Owner

Finding: after the current dev merge, cargo fmt --check reports tab indentation in the Calendar preference update at crates/plugins/calendar/src/items.rs around lines 714–768. No other Rust format diffs were reported. I am formatting this block before continuing the gates.

Finding: after the current `dev` merge, `cargo fmt --check` reports tab indentation in the Calendar preference update at `crates/plugins/calendar/src/items.rs` around lines 714–768. No other Rust format diffs were reported. I am formatting this block before continuing the gates.
Author
Owner

Workspace gate finding: cargo test stopped in calternal-collab at continuous_typing_is_saved_within_the_maximum_wait (crates/calternal-collab/tests/hostile_clients.rs:394) with no save during 5 s of typing. The test has a 5-second save deadline. Multiple worktrees were compiling and testing on the shared host at the same time. The collab implementation and test files are unchanged on this branch. I reran only this test with the required Cargo environment; it passed: 1 passed; 0 failed; 10 filtered out; finished in 5.09s. This appears load-sensitive; I made no product change for it and will continue the workspace gate.

Workspace gate finding: `cargo test` stopped in `calternal-collab` at `continuous_typing_is_saved_within_the_maximum_wait` (`crates/calternal-collab/tests/hostile_clients.rs:394`) with `no save during 5 s of typing`. The test has a 5-second save deadline. Multiple worktrees were compiling and testing on the shared host at the same time. The collab implementation and test files are unchanged on this branch. I reran only this test with the required Cargo environment; it passed: `1 passed; 0 failed; 10 filtered out; finished in 5.09s`. This appears load-sensitive; I made no product change for it and will continue the workspace gate.
Author
Owner

Web gate finding: the first bun run check found one stale call in apps/web/src/lib/components/analytics/BklitAnalytics.tsx:736: trendMonthFormat had become a function routed through the shared date formatter, but the year-trend label still called .format(parsed). Changed it to call trendMonthFormat(parsed). Verification: bun run check now reports svelte-check found 0 errors and 0 warnings; the focused analytics suite reports Test Files 1 passed (1) and Tests 13 passed (13). Fix commit: 77904d25.

Web gate finding: the first `bun run check` found one stale call in `apps/web/src/lib/components/analytics/BklitAnalytics.tsx:736`: `trendMonthFormat` had become a function routed through the shared date formatter, but the year-trend label still called `.format(parsed)`. Changed it to call `trendMonthFormat(parsed)`. Verification: `bun run check` now reports `svelte-check found 0 errors and 0 warnings`; the focused analytics suite reports `Test Files 1 passed (1)` and `Tests 13 passed (13)`. Fix commit: `77904d25`.
Author
Owner

Adversarial round finding: the authorization matrix exercised 229 OpenAPI operations for four identities (916 requests). Standard-user POST /api/v1/auth/sessions/revoke-all returned -1 from the probe, which uses a 20-second HTTP timeout. The local server was still alive at the end of the round. Other worktrees were running adversarial/API load at the same time, so the cause is unconfirmed. I added the transport exception text to tests/adversarial/authz_matrix.py (885cc011) for diagnosis. This is filed as an unresolved route response under load; it did not show a 5xx response or server crash.

Adversarial round finding: the authorization matrix exercised 229 OpenAPI operations for four identities (916 requests). Standard-user `POST /api/v1/auth/sessions/revoke-all` returned `-1` from the probe, which uses a 20-second HTTP timeout. The local server was still alive at the end of the round. Other worktrees were running adversarial/API load at the same time, so the cause is unconfirmed. I added the transport exception text to `tests/adversarial/authz_matrix.py` (`885cc011`) for diagnosis. This is filed as an unresolved route response under load; it did not show a 5xx response or server crash.
Author
Owner

Forgejo #179 final report

Branch: job/date-formats
Head: 885cc011a2bb26937e6e3f9c6f036322a1fd3395

Built

  • Added shared locale aware date and time formatting in packages/ui, including explicit date, clock, and week-start preferences.
  • Persisted Calendar preferences through GET/PUT /api/v1/calendar/preferences; updated the settings UI, OpenAPI contract, and generated API client.
  • Migrated Calendar, analytics, files, Notes, Photos, search, notifications, AI, and settings date labels to shared formatters. Fixed the localized yearly trend label call caught by svelte-check.
  • Extended adversarial coverage for invalid date, clock, and week-start values and mixed concurrent preference writes.
  • Added 24 production screenshots in artifacts/date-formats-review/ for the visual reviewer.

Key files

packages/ui/src/time.ts, packages/ui/src/formatPreferences.svelte.ts, packages/ui/src/weekStart.ts, apps/web/src/lib/calendar/prefs.ts, apps/web/src/routes/settings/calendars/CalendarsSection.svelte, apps/web/src/lib/components/analytics/BklitAnalytics.tsx, crates/plugins/calendar/src/items.rs, contracts/openapi.json, packages/api-client/src/generated.ts, tests/adversarial/attack.py, tests/adversarial/attack2.py, tests/adversarial/authz_matrix.py, and artifacts/date-formats-review/ (90 changed paths total relative to dev).

Commits

32aa5028 merged current dev; ddab912c, f16b6e57, 0100654b, 77904d25, 825f3350, and 885cc011 contain the job changes.

Gates

cargo fmt --check exited 0 with no output.

cargo clippy --all-targets -- -D warnings:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 8m 06s

Full cargo test rerun used RUST_TEST_THREADS=1 and exited 0. It emitted 69 test result: lines, all with 0 failed. Representative output:

Finished `test` profile [unoptimized + debuginfo] target(s) in 1m 16s
test result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.33s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 25.12s
test result: ok. 101 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 161.29s
test result: ok. 90 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 135.26s

The first full test attempt hit continuous_typing_is_saved_within_the_maximum_wait with no save during 5 s of typing while other worktrees were testing. The isolated test then passed (1 passed; 0 failed; 10 filtered out; finished in 5.09s), and the serialized full rerun passed the collab suite (11 passed; 0 failed).

bun run check:

svelte-check found 0 errors and 0 warnings

bun run test -- --maxWorkers=1 --no-file-parallelism:

Test Files  71 passed (71)
      Tests  554 passed (554)
   Start at  09:05:15
   Duration  109.27s (transform 37%, import 31%, environment 16%, tests 12%, setup 2%, worker 1%)

The production adversarial runner rebuilt the SPA and server after the analytics fix. The date preference abuse cases passed. The round also reported one authz-matrix transport failure (POST /api/v1/auth/sessions/revoke-all as standard returned -1; the probe timeout is 20 seconds), an Appearance SLOW p95 of 2.649s, and a thumbnail worker delay beyond 30 seconds. The server remained alive, hostile-byte isolation reported 0 findings, journal-race returned 40 successful creates and 46 entries with no timeouts, and the restart probe reported 0 findings. A follow-up probe fix now skips the dependent thumbnail 404 checks when the 256 px thumbnail is still pending; the full adversarial round was not repeated. The outer tee pipeline masked the runner’s nonzero result, but the probe output recorded the authz failure and the runner sets its status to nonzero for that result. The authz transport finding is filed above; its cause was unconfirmed under concurrent worktree load.

Known gaps and decisions

  • The CLI has no separate human date renderer. Normal CLI output remains canonical API JSON so machine-readable dates and paths stay unchanged; no second Rust formatter was added.
  • system format follows the Installation’s browser locale through the shared UI formatter. The explicit formats are stored in the User’s Calendar settings section so they follow the User across Installations.
  • The authz-matrix connection failure remains unconfirmed and is recorded in the issue comments. Appearance and thumbnail timing results were SLOW under shared-host load.
  • Vitest passed with jsdom notices: Not implemented: Window's scrollTo() method and Could not parse CSS stylesheet.

Cleanup completed: cargo clean reported Removed 19421 files, 17.4GiB total; apps/web/build and apps/web/.svelte-kit were removed. No push, deploy, or merge was performed.

# Forgejo #179 final report Branch: `job/date-formats` Head: `885cc011a2bb26937e6e3f9c6f036322a1fd3395` ## Built - Added shared locale aware date and time formatting in `packages/ui`, including explicit date, clock, and week-start preferences. - Persisted Calendar preferences through `GET/PUT /api/v1/calendar/preferences`; updated the settings UI, OpenAPI contract, and generated API client. - Migrated Calendar, analytics, files, Notes, Photos, search, notifications, AI, and settings date labels to shared formatters. Fixed the localized yearly trend label call caught by `svelte-check`. - Extended adversarial coverage for invalid date, clock, and week-start values and mixed concurrent preference writes. - Added 24 production screenshots in `artifacts/date-formats-review/` for the visual reviewer. ## Key files `packages/ui/src/time.ts`, `packages/ui/src/formatPreferences.svelte.ts`, `packages/ui/src/weekStart.ts`, `apps/web/src/lib/calendar/prefs.ts`, `apps/web/src/routes/settings/calendars/CalendarsSection.svelte`, `apps/web/src/lib/components/analytics/BklitAnalytics.tsx`, `crates/plugins/calendar/src/items.rs`, `contracts/openapi.json`, `packages/api-client/src/generated.ts`, `tests/adversarial/attack.py`, `tests/adversarial/attack2.py`, `tests/adversarial/authz_matrix.py`, and `artifacts/date-formats-review/` (90 changed paths total relative to `dev`). ## Commits `32aa5028` merged current `dev`; `ddab912c`, `f16b6e57`, `0100654b`, `77904d25`, `825f3350`, and `885cc011` contain the job changes. ## Gates `cargo fmt --check` exited 0 with no output. `cargo clippy --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 8m 06s ``` Full `cargo test` rerun used `RUST_TEST_THREADS=1` and exited 0. It emitted 69 `test result:` lines, all with `0 failed`. Representative output: ```text Finished `test` profile [unoptimized + debuginfo] target(s) in 1m 16s test result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.33s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 25.12s test result: ok. 101 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 161.29s test result: ok. 90 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 135.26s ``` The first full test attempt hit `continuous_typing_is_saved_within_the_maximum_wait` with `no save during 5 s of typing` while other worktrees were testing. The isolated test then passed (`1 passed; 0 failed; 10 filtered out; finished in 5.09s`), and the serialized full rerun passed the collab suite (`11 passed; 0 failed`). `bun run check`: ```text svelte-check found 0 errors and 0 warnings ``` `bun run test -- --maxWorkers=1 --no-file-parallelism`: ```text Test Files 71 passed (71) Tests 554 passed (554) Start at 09:05:15 Duration 109.27s (transform 37%, import 31%, environment 16%, tests 12%, setup 2%, worker 1%) ``` The production adversarial runner rebuilt the SPA and server after the analytics fix. The date preference abuse cases passed. The round also reported one authz-matrix transport failure (`POST /api/v1/auth/sessions/revoke-all` as standard returned `-1`; the probe timeout is 20 seconds), an Appearance `SLOW` p95 of 2.649s, and a thumbnail worker delay beyond 30 seconds. The server remained alive, hostile-byte isolation reported `0 findings`, journal-race returned 40 successful creates and 46 entries with no timeouts, and the restart probe reported `0 findings`. A follow-up probe fix now skips the dependent thumbnail 404 checks when the 256 px thumbnail is still pending; the full adversarial round was not repeated. The outer `tee` pipeline masked the runner’s nonzero result, but the probe output recorded the authz failure and the runner sets its status to nonzero for that result. The authz transport finding is filed above; its cause was unconfirmed under concurrent worktree load. ## Known gaps and decisions - The CLI has no separate human date renderer. Normal CLI output remains canonical API JSON so machine-readable dates and paths stay unchanged; no second Rust formatter was added. - `system` format follows the Installation’s browser locale through the shared UI formatter. The explicit formats are stored in the User’s Calendar settings section so they follow the User across Installations. - The authz-matrix connection failure remains unconfirmed and is recorded in the issue comments. Appearance and thumbnail timing results were `SLOW` under shared-host load. - Vitest passed with jsdom notices: `Not implemented: Window's scrollTo() method` and `Could not parse CSS stylesheet`. Cleanup completed: `cargo clean` reported `Removed 19421 files, 17.4GiB total`; `apps/web/build` and `apps/web/.svelte-kit` were removed. No push, deploy, or merge was performed.
Author
Owner

Correction to the final report: dev was merged into job/date-formats at 32aa5028, as required by the job prompt. No push, deploy, or merge into dev or main was performed.

Correction to the final report: `dev` was merged into `job/date-formats` at `32aa5028`, as required by the job prompt. No push, deploy, or merge into `dev` or `main` was performed.
Author
Owner

Claude review (calendar-preview-en-IN-dark.png). Settings and the preview popover are right. Fix before merge:

  1. BUG: Week grid hour labels render twice on top of each other ('10am' drawn over '10:00' → '10amo', '11am0', '1pm0'...). Two label layers or a stale 24h label under the 12h one; only one set of labels, formatted by the new module, and a test that each hour row has exactly one label.
  2. The now-pill '8:17 am' wraps to two lines; it must stay on one line (compact 12h form like '8:17a' or narrower font weight/letter-spacing, or widen the gutter consistently for 12h).
  3. Week start mismatch: the sidebar mini calendar starts on Monday while the week grid starts on Sunday, both under 'System'. Both must use the same resolved first-day-of-week.
    Re-take the calendar screenshots (week and day, 12h and 24h, en-IN and en-US, light and dark).
Claude review (calendar-preview-en-IN-dark.png). Settings and the preview popover are right. Fix before merge: 1. BUG: Week grid hour labels render twice on top of each other ('10am' drawn over '10:00' → '10amo', '11am0', '1pm0'...). Two label layers or a stale 24h label under the 12h one; only one set of labels, formatted by the new module, and a test that each hour row has exactly one label. 2. The now-pill '8:17 am' wraps to two lines; it must stay on one line (compact 12h form like '8:17a' or narrower font weight/letter-spacing, or widen the gutter consistently for 12h). 3. Week start mismatch: the sidebar mini calendar starts on Monday while the week grid starts on Sunday, both under 'System'. Both must use the same resolved first-day-of-week. Re-take the calendar screenshots (week and day, 12h and 24h, en-IN and en-US, light and dark).
Author
Owner

Continuation of #179 on job/date-formats. Current head: 885cc011a2bb26937e6e3f9c6f036322a1fd3395; current dev: eb9acfccf975db10f3ee72e0f58eaf0ed1f3da05; merge base: b176b4c3ec9daa939c564b545f9b27f1e8924e2c. I read the binding repo docs and Claude's latest three-point calendar review. I will fix each finding in a separate commit, then capture the requested production calendar screenshots.

Continuation of #179 on `job/date-formats`. Current head: `885cc011a2bb26937e6e3f9c6f036322a1fd3395`; current `dev`: `eb9acfccf975db10f3ee72e0f58eaf0ed1f3da05`; merge base: `b176b4c3ec9daa939c564b545f9b27f1e8924e2c`. I read the binding repo docs and Claude's latest three-point calendar review. I will fix each finding in a separate commit, then capture the requested production calendar screenshots.
Author
Owner

Finding 1, commit d82c23cb: the grid used the full clock formatter for hour rows, so labels carried minutes and were patched in place when the saved time cycle arrived. It now uses the shared hour-only formatter and keys all 24 rows by their rendered labels, which removes the old row nodes when the labels change. Regression test: bun run test -- --maxWorkers=1 src/lib/calendar/TimeGrid.svelte.test.ts passed (Test Files 1 passed (1), Tests 1 passed (1)); it changes the clock cycle from 24-hour to 12-hour and checks that there are exactly 24 single-label rows.

Finding 1, commit `d82c23cb`: the grid used the full clock formatter for hour rows, so labels carried minutes and were patched in place when the saved time cycle arrived. It now uses the shared hour-only formatter and keys all 24 rows by their rendered labels, which removes the old row nodes when the labels change. Regression test: `bun run test -- --maxWorkers=1 src/lib/calendar/TimeGrid.svelte.test.ts` passed (`Test Files 1 passed (1)`, `Tests 1 passed (1)`); it changes the clock cycle from 24-hour to 12-hour and checks that there are exactly 24 single-label rows.
Author
Owner

Finding 2, commit dd09ecb7: the now pill used a full 12-hour time string and had no no-wrap rule; phone grids also had a 44 px gutter. Added formatCompactClockTime in the shared formatter (English 12-hour examples become 8:17a), white-space: nowrap, and a 56 px phone gutter. Focused verification passed: bun run test -- --maxWorkers=1 src/lib/time.test.ts src/lib/calendar/TimeGrid.svelte.test.ts → Test Files 2 passed (2), Tests 14 passed (14). The final production screenshot pass will check the computed layout across both locales.

Finding 2, commit `dd09ecb7`: the now pill used a full 12-hour time string and had no no-wrap rule; phone grids also had a 44 px gutter. Added `formatCompactClockTime` in the shared formatter (English 12-hour examples become `8:17a`), `white-space: nowrap`, and a 56 px phone gutter. Focused verification passed: `bun run test -- --maxWorkers=1 src/lib/time.test.ts src/lib/calendar/TimeGrid.svelte.test.ts` → `Test Files 2 passed (2)`, `Tests 14 passed (14)`. The final production screenshot pass will check the computed layout across both locales.
Author
Owner

Calendar finding follow-up, commit e006dd32: the production capture measured the en-IN 12-hour “10:19a” pill at 52 px against the 50 px available inside the 56 px gutter after its right inset. I reduced horizontal padding from 5 px to 3 px, kept the complete label and its one-line rule, rebuilt the production SPA, and all 16 capture assertions now pass.

Calendar finding follow-up, commit `e006dd32`: the production capture measured the en-IN 12-hour “10:19a” pill at 52 px against the 50 px available inside the 56 px gutter after its right inset. I reduced horizontal padding from 5 px to 3 px, kept the complete label and its one-line rule, rebuilt the production SPA, and all 16 capture assertions now pass.
Author
Owner

Calendar finding 3 is fixed: both the sidebar mini calendar and Calendar page now derive their first weekday from the same reactive preference. The production-server capture delays the preferences response by 250 ms, starts from a cached System setting, and verifies both surfaces switch to Monday. The assertion passed, as did the weekday checks for the en-IN and en-US Week captures.

Calendar finding 3 is fixed: both the sidebar mini calendar and Calendar page now derive their first weekday from the same reactive preference. The production-server capture delays the preferences response by 250 ms, starts from a cached System setting, and verifies both surfaces switch to Monday. The assertion passed, as did the weekday checks for the en-IN and en-US Week captures.
Author
Owner

Completed

Fixed the three Calendar review points on branch job/date-formats. Final HEAD is 933dbe19a6c8f753a7d049f4e857aeffe1c63efd; the worktree is clean.

  • The Week grid now renders one shared-format hour label per row, including after preferences arrive.
  • The now pill stays on one line and inside its hour gutter in 12-hour mode.
  • The sidebar mini calendar and Week grid use the same reactive first weekday when server preferences arrive after mount.
  • Rebuilt the production SPA and captured 16 screenshots: Week and Day, en-IN and en-US, 12-hour and 24-hour, light and dark. The production capture also checked one label per hour row, the now pill bounds, and the delayed preference update. Captures: artifacts/date-formats-review/calendar-grid/.
  • Merged dev before the gates. Resolved conflicts while preserving its editor preview, hidden-file search behavior and Agenda redesign, and kept shared date/time formatters in those paths.

Files

Calendar changes are in packages/ui/src/components/calendar/TimeGrid.svelte, packages/ui/src/time.ts, packages/ui/src/index.ts, apps/web/src/lib/time.test.ts, apps/web/src/lib/calendar/TimeGrid.svelte.test.ts, apps/web/src/lib/components/app-sidebar.svelte, apps/web/src/routes/calendar/[view]/[date]/+page.svelte, and apps/web/e2e/calendar-format-review.mjs. The merge also resolved apps/web/src/lib/search/SearchPreview.svelte, apps/web/src/lib/search/server.ts and packages/ui/src/components/calendar/AgendaList.svelte. The screenshots and manifest are in artifacts/date-formats-review/calendar-grid/.

Gates

  • cargo fmt --check: exit code 0; no output.
  • cargo clippy --all-targets -- -D warnings: Finished dev profile [unoptimized + debuginfo] target(s) in 6m 40s (exit code 0).
  • cargo test (one test thread): Finished test profile [unoptimized + debuginfo] target(s) in 3m 43s (exit code 0). All 69 test-result lines were ok. Raw examples:
    • test result: ok. 101 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 150.47s
    • test result: ok. 90 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 164.98s
    • test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 83.62s
  • bun run check: svelte-check found 0 errors and 0 warnings (exit code 0).
  • bun run test -- --maxWorkers=1 --no-file-parallelism:
    • Test Files 75 passed (75)
    • Tests 567 passed (567)
    • Duration 140.71s (transform 38%, import 30%, environment 17%, tests 12%, setup 3%, worker 1%)
  • Production build: ✓ built in 1m 11s; Wrote site to "build"; ✔ done.
  • cargo clean: Removed 17006 files, 14.4GiB total (exit code 0). Removed apps/web/build and apps/web/.svelte-kit.

Adversarial result and known gap

The post-merge live API round reported one non-SLOW result in the bookmark capture storm: 11 of 16 requests returned 201 with 11 unique IDs; 5 exceeded its 10-second client timeout and returned no response. The server stayed alive. Other concurrent worktrees were running tests on the shared host. I filed the result for an isolated follow-up as Forgejo #222. Other reported delays were SLOW; the hostile-bytes probe had 0 findings and the restart probe had 0 findings.

Decisions not specified in DESIGN.md

For compact English 12-hour labels, I used a/p after the time (for example, 10:19a) and reduced horizontal pill padding to 3px. This keeps the full label within the existing 56px hour gutter. Other locales keep their localized period text.

## Completed Fixed the three Calendar review points on branch `job/date-formats`. Final HEAD is `933dbe19a6c8f753a7d049f4e857aeffe1c63efd`; the worktree is clean. - The Week grid now renders one shared-format hour label per row, including after preferences arrive. - The now pill stays on one line and inside its hour gutter in 12-hour mode. - The sidebar mini calendar and Week grid use the same reactive first weekday when server preferences arrive after mount. - Rebuilt the production SPA and captured 16 screenshots: Week and Day, en-IN and en-US, 12-hour and 24-hour, light and dark. The production capture also checked one label per hour row, the now pill bounds, and the delayed preference update. Captures: `artifacts/date-formats-review/calendar-grid/`. - Merged `dev` before the gates. Resolved conflicts while preserving its editor preview, hidden-file search behavior and Agenda redesign, and kept shared date/time formatters in those paths. ## Files Calendar changes are in `packages/ui/src/components/calendar/TimeGrid.svelte`, `packages/ui/src/time.ts`, `packages/ui/src/index.ts`, `apps/web/src/lib/time.test.ts`, `apps/web/src/lib/calendar/TimeGrid.svelte.test.ts`, `apps/web/src/lib/components/app-sidebar.svelte`, `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`, and `apps/web/e2e/calendar-format-review.mjs`. The merge also resolved `apps/web/src/lib/search/SearchPreview.svelte`, `apps/web/src/lib/search/server.ts` and `packages/ui/src/components/calendar/AgendaList.svelte`. The screenshots and manifest are in `artifacts/date-formats-review/calendar-grid/`. ## Gates - `cargo fmt --check`: exit code 0; no output. - `cargo clippy --all-targets -- -D warnings`: `Finished `dev` profile [unoptimized + debuginfo] target(s) in 6m 40s` (exit code 0). - `cargo test` (one test thread): `Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 43s` (exit code 0). All 69 test-result lines were `ok`. Raw examples: - `test result: ok. 101 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 150.47s` - `test result: ok. 90 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 164.98s` - `test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 83.62s` - `bun run check`: `svelte-check found 0 errors and 0 warnings` (exit code 0). - `bun run test -- --maxWorkers=1 --no-file-parallelism`: - `Test Files 75 passed (75)` - `Tests 567 passed (567)` - `Duration 140.71s (transform 38%, import 30%, environment 17%, tests 12%, setup 3%, worker 1%)` - Production build: `✓ built in 1m 11s`; `Wrote site to "build"`; `✔ done`. - `cargo clean`: `Removed 17006 files, 14.4GiB total` (exit code 0). Removed `apps/web/build` and `apps/web/.svelte-kit`. ## Adversarial result and known gap The post-merge live API round reported one non-`SLOW` result in the bookmark capture storm: 11 of 16 requests returned 201 with 11 unique IDs; 5 exceeded its 10-second client timeout and returned no response. The server stayed alive. Other concurrent worktrees were running tests on the shared host. I filed the result for an isolated follow-up as Forgejo #222. Other reported delays were `SLOW`; the hostile-bytes probe had 0 findings and the restart probe had 0 findings. ## Decisions not specified in DESIGN.md For compact English 12-hour labels, I used `a`/`p` after the time (for example, `10:19a`) and reduced horizontal pill padding to 3px. This keeps the full label within the existing 56px hour gutter. Other locales keep their localized period text.
Author
Owner

Merged into dev at 57f5556d after Claude's round-2 review (single hour labels, one-line now pill, same week start everywhere); calendar grid a11y roles from #162 kept with the new formatter. Deploy status on #203.

Merged into dev at 57f5556d after Claude's round-2 review (single hour labels, one-line now pill, same week start everywhere); calendar grid a11y roles from #162 kept with the new formatter. Deploy status on #203.
kayg closed this issue 2026-09-27 09:29:36 +00:00
kayg referenced this issue from a commit 2026-09-27 09:29:37 +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#179
No description provided.