Skip to content

fix: stop the desktop calling yesterday today - #552

Merged
avelino merged 2 commits into
mainfrom
avelino/day-clock-today
Nov 14, 2026
Merged

avelino merged 2 commits into
mainfrom
avelino/day-clock-today

Conversation

@avelino

@avelino avelino commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A desktop window left open overnight kept calling yesterday "Today". This gives the day a single owner that re-asks on its own, so the header, the sidebar calendar ring and the task list stop disagreeing with the clock.

Context

Reported from a real window: opened on the 13th, still reading Today · November 13, 2026 at 08:29 on the 14th, in the accent colour, directly over the page the user was about to type into. The calendar ring was still on the 13th.

Nothing here was stale in the needs-a-refetch sense. The app was asserting a falsehood about the day, on the one surface where believing it means writing into the wrong journal.

Two frozen copies of one fact caused it:

  • PageTitle.tsx resolved the day in a createResource keyed on the page slug, so it re-asked on navigation and never otherwise. No navigation, no new answer, forever.
  • Sidebar.tsx's calendar called new Date() in a component body. Read once at mount, and on the browser clock rather than the configured [calendar] timezone, so that one could be wrong at any hour of any day, not only after midnight.

The TUI never had this (view/sidebar.rs recomputes per frame) and mobile already solved it in Journal.day-nav.ts. The desktop was the client left out.

No issue number found on the branch or in the commits, so none is linked.

Changes

createDayClock in @outl/shared/journal is the one owner now. AppShell wires it into appState.todaySlug, and every surface that names a day reads that field.

Three decisions worth a reviewer's attention:

The day comes from the backend, the schedule comes from the browser. today_slug resolves in outl_actions::clock's timezone. The browser clock only decides when to ask, and because a configured timezone puts midnight where the browser cannot predict it, that delay is capped at an hour. A new Date() used as the answer is the bug this replaces, not a shortcut.

Midnight relabels, it does not navigate. The user may be looking at the window at 00:00. Returning to today happens on the way back in (focus / visibilitychange), under shouldWakeToToday, which is promoted out of outl-mobile/src/lib/ into the shared library rather than copied. Both clients now answer "does this count as opening the app" the same way: never mid-edit, never when today is already on screen.

The clock is created in AppShell's component body, not in its async onMount. It registers an onCleanup, and the owner is gone once that body awaits.

On size: 659 lines, of which 251 are new tests, 35 are docs and roughly 70 are the new module's own doc comments. The remaining logic is small and does not split cleanly. The shared module without its consumers is dead code, and the consumers without it do not compile. The one separable piece is the shouldWakeToToday promotion, which on its own is a move with no visible reason.

Testing

  • @outl/shared: 685 tests, including 14 new in day-clock.test.ts (midnight tick under an untouched app, failed backend call, disposal, wake on return, never mid-edit, no navigation on the tick itself)
  • outl-desktop: 280 tests, including the regression in PageTitle.test.tsx: the day rolls over with no navigation and the header has to stop claiming the page on screen is today
  • outl-mobile: 364 tests, parity after the shouldWakeToToday move
  • tsc --noEmit on all three packages
  • cargo fmt --check, clippy on outl-desktop and outl-tauri-shared, and 320 Rust tests in the crates that read frontend files (wire_mirrors, outl-theme tokens, shortcut parity)
  • /ux-desktop layout battery: 13 rows, PASS: pointing at a row moves nothing. Its captured frame also shows the real app booting with this change and rendering Today · November 14, 2026 from the new store field, which is the wiring vitest cannot see

Not covered: the rollover itself with a window open. The lab uses a fixed fixture and a parked clock, so it never crosses midnight. The unit tests cover the logic. The lived scenario needs a build running overnight.

Screenshots

Touches the page header and the sidebar calendar. Attach a before/after if the label change is worth showing.

Verified

  • Tests added (day-clock.test.ts, PageTitle.test.tsx) and an existing one updated (TasksPanel.test.tsx, which mocked the command this field replaces)
  • Docs updated in the same change: CHANGELOG.md, outl-desktop/CLAUDE.md (new "Which day it is" section), the outl-frontend-shared/CLAUDE.md surface table, and the mobile pointer
  • No breaking change to a published surface. shouldWakeToToday moved between internal modules and AppStateShape gained a field, both repo-internal

Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.

A window opened on the 13th still read `Today · November 13, 2026` at
08:29 on the 14th, in the accent colour, over the page the user was
about to write into, and the sidebar calendar still ringed the 13th.
Nothing was stale in the needs-a-refetch sense. The app was asserting a
falsehood about the day, on the one surface where believing it means
writing into the wrong journal.

Two frozen copies of one fact caused it. The header resolved the day in
a createResource keyed on the page slug, so it re-asked on navigation
and never otherwise. The sidebar calendar called new Date() in a
component body, read once at mount and on the browser clock rather than
the configured [calendar] timezone, so that one could be wrong at any
hour of any day.

createDayClock owns it now, and the header, the calendar ring and the
task list's journal headings all read it. The value is still the
backend's today_slug. The browser clock only decides when to ask, capped
at an hour, because a configured timezone puts midnight where the
browser cannot predict it. Midnight relabels and does not navigate,
since the user may be looking at the window. Returning to today happens
on the way back in, under the shouldWakeToToday rule the phone already
used, now shared rather than mobile-only.

Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>
Signed-off-by: Avelino <31996+avelino@users.noreply.github.com>
@avelino
avelino merged commit d855c59 into main Nov 14, 2026
18 checks passed
@avelino
avelino deleted the avelino/day-clock-today branch November 14, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant