Skip to content

time: stamps cross the bridge as UTC; local time is a display concern - #80

Merged
nixfred merged 1 commit into
nixfred:mainfrom
zachwilke:cursor/utc-timestamps-fe38
Sep 15, 2026
Merged

nixfred merged 1 commit into
nixfred:mainfrom
zachwilke:cursor/utc-timestamps-fe38

Conversation

@zachwilke

Copy link
Copy Markdown
Contributor

Split out of #47 (first of five, as requested). Rebased onto current main (2.5.0).

What

Stamps cross the bridge as ISO-8601 UTC (2026-09-07T18:33:12Z). Local time is a display concern: bubbles, day dividers, "Today"/"Yesterday" and read receipts convert to the reader's zone at render, so a day still breaks at your midnight.

Why

Stamps arrived as the Mac's naive wall clock and were compared against the Linux clock — the same string only while both machines sat in one timezone. A Mac an hour ahead put every read mark ahead of every message (nothing unread); an hour behind and the backlog re-toasted. In the DST fall-back hour the Mac's own clock repeats, so two messages an hour apart carried the same stamp and lexical order stopped being chronological order. Every a.ts < b.ts in the collector already assumed that property; only UTC actually provides it.

imsg's JSON emits fmt_ts (UTC). Plain-text CLI renders keep fmt_ts_local (the time a person reading imsg recent remembers).

Upgrading is covered from both ends, and both halves are load-bearing together: loadState() migrates marks a pre-UTC release wrote, and stamps are normalised at the two fetch doors (fetchMessages, loadThread, plus fetchChats' last) so a Mac still on the old bridge keeps working. Migrating the marks alone would sort every message below every mark (" " < "T") and silently empty the badge and the toasts.

This closes the open ROADMAP item ("Timestamps are Mac-local wall-clock strings").

How it was verified

  • bun test is green (497 tests; CI-equivalent Mac-tool tests including new test_wire_time.py)
  • Any send/receive behavior was exercised against my own number only — never a contact or a group
  • UI change → screenshot attached
  • Touches an invariant in CLAUDE.md → named below, and the doc updated if it changed

invariants touched: Every stamp inside Blip is UTC; local time is a DISPLAY concern. Marks migrate on load; stamps normalise at the fetch doors. The suite is pinned to TZ=UTC (test-setup.ts); timezone.test.ts and bridge/mac/test_wire_time.py set their own zones and are what actually cover the conversion.

Rebase notes

main moved a long way since #47. Conflict resolutions kept current-main behaviour (chat-id sanitising, pin_name, failed-send PendingSend fields, XDG_CONFIG_HOME test isolation) and applied toUtcStamp on fetchChats().last. A selectIncomingLinks fixture added on main after #47 still used a naive stamp; it is now UTC so it does not sort below a UTC watermark (" " < "T").

Supersedes the UTC slice of #47 (688f2cb).

Stamps arrived as the Mac's naive wall clock ("2026-09-07 14:33:12") and
were compared against the Linux clock — the same string only while both
machines sat in one timezone. A Mac an hour ahead put every read mark ahead
of every message, so nothing ever counted as unread; an hour behind and the
backlog re-toasted. And once a year, in the DST fall-back hour, the Mac's
own clock repeats: two messages an hour apart carried the SAME stamp, so
ordering, the watermark and "newer than the mark" stopped meaning anything
for that hour. Every `a.ts < b.ts` in the collector was already assuming
lexical order equals chronological order; only UTC actually provides it.

imsg's JSON now emits ISO-8601 UTC (fmt_ts); the plain-text renders keep the
Mac's clock (fmt_ts_local), because a person reading `imsg recent` wants the
time they remember. On the Linux side localNowTs() became nowTs() in the same
format, and every label converts to the READER's zone at render: localDay()
and formatStamp() in thread.ts, stampMs()/localDay()/fmtTime() in BlipView.
Day dividers no longer slice the date out of the string — that is UTC's day,
and the divider belongs at your midnight.

Upgrading is covered from both ends, and both halves are load-bearing
together: loadState() migrates the marks a pre-UTC release wrote, and stamps
are normalised at the two fetch doors so a Mac still on the old bridge keeps
working. Migrating the marks alone would have sorted every message below
every mark (" " < "T") and silently emptied the badge and the toasts.

The suite runs pinned to TZ=UTC, where all of this is invisible; the new
timezone.test.ts and bridge/mac/test_wire_time.py set their own zones and
are what actually cover it. Verified live against the gateway Mac on both
the old and the new bridge: 298 threads, unread unchanged, no toast storm
on migration, and 23:47:54Z rendering as 6:47 PM in CDT.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xxS9WCT7ifHhbDjYyGnHS

Co-authored-by: zachwilke <zachwilke@users.noreply.github.com>
@zachwilke
zachwilke requested a review from nixfred as a code owner September 14, 2026 14:14
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-14T14:18:44.743319Z 8da602a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8da602a716

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread collector.ts
// Pre-two-mark state files have no readMark. Inheriting the watermark is
// the safe migration: it reports zero unread rather than a fake backlog.
readMark: typeof s.readMark === "string" ? s.readMark : watermark,
readMark: toUtcStamp(typeof s.readMark === "string" ? s.readMark : watermark),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid interpreting Mac-local marks in the Linux timezone

When upgrading a setup where the Mac and Linux machine use different timezones, this converts the old bridge's Mac-local readMark using the Linux timezone. For example, a UTC Mac's saved 18:00 mark becomes 22:00Z on a New York Linux host, so newly fetched UTC messages remain below the mark and produce no unread badge for four hours. Unlike watermark, this nonempty future readMark is persisted unchanged on every poll, so the problem does not correct itself after one fetch; migrate against the Mac's offset or reset/clamp legacy read marks when switching formats.

AGENTS.md reference: AGENTS.md:L23-L25

Useful? React with 👍 / 👎.

@nixfred

nixfred commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Merged, thanks @zachwilke! stamps now cross the bridge as UTC. It also fixed issue #86 (a send showing twice when Linux and the Mac are in different zones). Your fixes at both ends mean a Mac still on the old bridge keeps working, which made this safe to merge. It's in main as of 186b536 and deployed on my machine. I merged it together with nine other PRs, and all 526 tests pass on the combined tree.

nixfred added a commit that referenced this pull request Sep 15, 2026
…cannot abort it

WHAT: Step 4 of scripts/blip-setup replaced `scp -q "$here"/bridge/mac/*` with
`find "$here/bridge/mac" -maxdepth 1 -type f -print0 | xargs -0 ... scp -q`.
A test in collector.test.ts pins the regular-files-only copy.

WHY: the glob also matches bridge/mac/__pycache__, which appears as soon as
anyone runs the Mac-side Python tests. scp without -r exits 1 on a directory,
and blip-setup runs under set -euo pipefail, so setup died right there, before
install.sh ran, and the Mac silently kept its OLD bridge tools. Found
2026-09-15 while installing the 2.5.0+ bridge (UTC stamps, #80) on the gateway
Mac after the PR Hunter merge batch: the copy failed exactly this way.

VERIFIED: the new copy line run against a temp tree with a file, install.sh
and a __pycache__/x.pyc under a stand-in scp copies the two files and skips
the directory; bash -n clean; full bun suite green.

TAGS: blip, blip-setup, scp, pycache, mac-install, set-e, bridge-tools, pr-hunter

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nixfred added a commit that referenced this pull request Sep 16, 2026
WHAT: CHANGE.log is a running, newest-first log of every change since the last
X post, each entry naming the person with real name, GitHub login and their
X account as a https://x.com/<handle> link. X-POSTS.md is the queue of posts
for @nixfred: Post 1 (CONTRIBUTORS-X-POST.md, 2026-09-11) marked posted,
Post 2 drafted covering the 19 merged PRs since (#54 #71-#77 #80-#82 #84 #85
#87 #88 #92-#95) plus 4bad82d and 481e555.
WHY: Fred's standing rule 2026-09-16. Handles were missed before; a thank-you
without the @ never reaches the person, and a wrong @ tags a stranger.
HANDLES: verified @jondkinney (GitHub profile), @zachwilke_1 (pinefall.dev),
@bbishdotdev (bbish.dev + name), @mwhuss (name + Omarchy posts replying to
@nixfred). @jefehoser likely (same login, Omarchy posts), flagged for Fred.
Ian Swope and cjoh: none verified, recorded as checked.
.gitignore: *.log swallowed CHANGE.log, so it is re-included explicitly.

LR-T: blip, changelog, x-posts, contributors, handles
LR-D: blip, docs, community
LR-K: change-log-verified-x-handles, x-post-queue

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ainpzHJ5F7nsV4hB6HpZy
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.

2 participants