The visible range is drawn from the events in view, and in the evening the
events are behind you: the bounds end at six, everything after that is in the
trailing strip, and the now line has nowhere to land. The app stops saying
where in the day you are, which is most of what it is open for. Same at seven
in the morning, from the other end.
So when today is one of the columns, the axis takes that one hour in, and
everything the widening reached over folds behind it. At half eleven at night
the fixture week reads 7am to 8pm as usual, then an "8pm to 11pm" strip, then
one 11pm row with the line in it. A row and a strip, not four rows of empty
evening. Only the hour itself is opened, so how far the clock has drifted past
the last event costs nothing.
The pin sits on top of the hysteresis rather than inside it. previous.current
still holds what the events and the user asked for, so an hour pinned open
tonight cannot accumulate into the bounds the grid remembers tomorrow.
One consequence, and it is the only place the axis is allowed to move under
you: page to a week that does not contain today and the row goes again, along
with the strip it was sitting under.
The tick that drives it is an hour long, not a minute, because that is how
often the answer changes. It lives in a new useClock alongside the minute tick
the now line already had, which moves there out of GridNowLine. Both schedule
off the clock rather than off an interval, so a machine that was asleep
catches up on the next tick instead of drifting further out every hour.
The browser suite has to say what time it is now. The shape of the axis
depends on the hour a run happens at, so anything measuring a row height or
counting strips pins the clock to the middle of the working day, the same way
it already pins the theme and the week start. The three tests that are about
the clock ask for half eleven at night, which the fixture leaves empty.
Tauri 2, React 19 and zustand on the front, Rust behind. Rust owns auth,
all HTTP to Google, the SQLite store, the sync loop, recurrence expansion
and timezone maths. TypeScript owns rendering and never talks to Google,
which keeps the content security policy locked to ipc:.
Week, day and agenda views, and no month view: it would be a second layout
engine, and the fit and fold logic that makes a day fit the window without
scrolling is the whole point of the app.
Runs on macOS, Linux, Android and iOS. Desktop catches Google's OAuth
redirect on a loopback port. A phone cannot, and Google rejects loopback
for mobile client types anyway, so it redirects to a custom URI scheme and
needs its own public OAuth clients, which docs/mobile.md covers. Refresh
tokens are sealed with XChaCha20-Poly1305 in the app data directory on
every platform, with no OS credential store in the picture.
On a phone the chrome becomes a top bar and a bottom tab bar, overlays
become sheets, hover affordances become taps, and dragging out an event
waits for a long press. Navigation moves one day at a time everywhere,
a swipe included.