Serve the display wall clock and true instant to clients, so consumers render/compare dates without any zone math — the server half of the FileViewer-React #33 / Apollo #37 flips (spec #113, cutover #120).
Rebased onto the tracing migration (#148). DAO calls and date_view::owner_display_zone follow its API (no &opentelemetry::Context params; trace_db_call(op, name, |span|)). Conflicts were only in get_all_with_gps and the metadata handler plumbing, plus four auto-merged call sites still passing the old context, fixed before building. Re-verified after the rebase: fmt, clippy, full suite, e2e.sh check 97/97.
Why server-side: #120's test run showed the drain has a permanent floor (0.5–2% of rows — edited/exported files — never get date_utc). A client reading only the triple would have to carry the per-source legacy conventions forever. row_display_wall already handles every row and already feeds the insight prompts, so prompt and UI agree by construction.
src/date_view.rs # format_wall_clock / row_wall_clock / file_wall_clock / file_true_instant
# owner_display_zone (prefs -> app default)
src/data/mod.rs # MetadataResponse.{wall_clock, true_instant}; GpsPhotoSummary.wall_clock
src/memories.rs # MemoryItem.wall_clock
src/handlers/image.rs # set_capture_dates at all 4 metadata response sites (get + GPS/date/clear)
src/files.rs # gps-summary computes wall_clock from full rows
src/database/mod.rs # get_all_with_gps returns ImageExif rows (the 7-tuple dropped
# date_taken_source, which unconverted rows need)
dev/e2e/ # `e2e.sh check`; deterministic fixture dates; TZ pinned (#120 preflight)
wall_clock = row_display_wall(row) # naive ISO "2018-09-29T14:00:00" — render verbatim
?? filename-class wall # no row yet (unindexed file)
?? earliest fs time in display zone
true_instant = row_true_instant(row) ?? filename instant ?? earliest fs time
# SMS/location window anchor — never displayed
Additive only: legacy fields byte-identical, so current clients are unaffected and this can deploy ahead of the client flip (#120 step 3).
Found while doing this: the mobile SMS window was anchored on the legacy value (wall-as-UTC) while SMS-API stores true instants (verified: 40/40 sampled rows equal their backup epochs), putting the window 4–5 h off for photos taken at home — #113 user story 3. true_instant is the fix. Also: #113's schema table says "wall = instant − offset"; the code (correctly) adds the offset.
Test plan
dev/e2e/e2e.sh check (before and after the #148 rebase): 97/97 fixtures report the expected wall_clock on metadata / memories / gps-summary and the expected true_instant (EXIF+GPS in Tokyo/Paris/LA, no-GPS, filename-class, container video, mtime, duplicate)
Unit tests in date_view (travel-zone wall, no-offset rendering, unconverted floor row, file fallbacks, instants)
End to end on the emulator with the FileViewer #33 branch: Tokyo 14:00 photo shows 2:00 PM in viewer and memories (was 10:00 AM / 1:00 AM)
fmt; clippy (only pre-existing filename_audit warning); full suite (only the pre-existing filename_audit failure)
## Summary
Serve the **display wall clock** and **true instant** to clients, so consumers render/compare dates without any zone math — the server half of the FileViewer-React #33 / Apollo #37 flips (spec #113, cutover #120).
> **Rebased onto the tracing migration (#148).** DAO calls and `date_view::owner_display_zone` follow its API (no `&opentelemetry::Context` params; `trace_db_call(op, name, |span|)`). Conflicts were only in `get_all_with_gps` and the metadata handler plumbing, plus four auto-merged call sites still passing the old context, fixed before building. Re-verified after the rebase: fmt, clippy, full suite, `e2e.sh check` 97/97.
Why server-side: #120's test run showed the drain has a **permanent floor** (0.5–2% of rows — edited/exported files — never get `date_utc`). A client reading only the triple would have to carry the per-source legacy conventions forever. `row_display_wall` already handles every row and already feeds the insight prompts, so prompt and UI agree by construction.
src/date_view.rs # format_wall_clock / row_wall_clock / file_wall_clock / file_true_instant
# owner_display_zone (prefs -> app default)
src/data/mod.rs # MetadataResponse.{wall_clock, true_instant}; GpsPhotoSummary.wall_clock
src/memories.rs # MemoryItem.wall_clock
src/handlers/image.rs # set_capture_dates at all 4 metadata response sites (get + GPS/date/clear)
src/files.rs # gps-summary computes wall_clock from full rows
src/database/mod.rs # get_all_with_gps returns ImageExif rows (the 7-tuple dropped
# date_taken_source, which unconverted rows need)
dev/e2e/ # `e2e.sh check`; deterministic fixture dates; TZ pinned (#120 preflight)
wall_clock = row_display_wall(row) # naive ISO "2018-09-29T14:00:00" — render verbatim
?? filename-class wall # no row yet (unindexed file)
?? earliest fs time in display zone
true_instant = row_true_instant(row) ?? filename instant ?? earliest fs time
# SMS/location window anchor — never displayed
Additive only: legacy fields byte-identical, so current clients are unaffected and this can deploy ahead of the client flip (#120 step 3).
Found while doing this: the mobile SMS window was anchored on the legacy value (wall-as-UTC) while SMS-API stores true instants (verified: 40/40 sampled rows equal their backup epochs), putting the window 4–5 h off for photos taken at home — #113 user story 3. `true_instant` is the fix. Also: #113's schema table says "wall = instant − offset"; the code (correctly) adds the offset.
## Test plan
- [x] `dev/e2e/e2e.sh check` (before and after the #148 rebase): **97/97** fixtures report the expected `wall_clock` on metadata / memories / gps-summary and the expected `true_instant` (EXIF+GPS in Tokyo/Paris/LA, no-GPS, filename-class, container video, mtime, duplicate)
- [x] Unit tests in `date_view` (travel-zone wall, no-offset rendering, unconverted floor row, file fallbacks, instants)
- [x] End to end on the emulator with the FileViewer #33 branch: Tokyo 14:00 photo shows 2:00 PM in viewer and memories (was 10:00 AM / 1:00 AM)
- [x] fmt; clippy (only pre-existing `filename_audit` warning); full suite (only the pre-existing `filename_audit` failure)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Every payload a client renders a capture time from now carries
`wall_clock`, a naive ISO-8601 string computed by the existing
`date_view::row_display_wall`: the camera's wall clock (true instant +
camera offset; the owner's display zone without an offset), with the
per-source fallback for unconverted rows. Clients render it verbatim and
do no zone math, which is the FileViewer-React #33 / Apollo #37 flip with
the rule kept server-side: the drain's permanent `date_utc IS NULL` floor
(#120: edited/exported files) would otherwise force every client to
carry the per-source legacy conventions forever, and insight prompts
already use the same function, so prompt and UI agree by construction.
- `/image/metadata` (+ the GPS / date / date-clear mutation responses):
`file_wall_clock` — the row's wall, else the filename-class date, else
the earliest fs time in the display zone (unindexed files).
- `/memories` items and `/photos/gps-summary` photos: `row_wall_clock`.
- `get_all_with_gps` returns full rows instead of a 7-tuple that dropped
`date_taken_source`, which unconverted rows need.
- `date_view::owner_display_zone` resolves prefs -> app default per request.
Legacy fields are untouched, so current clients see byte-identical
responses (additive; safe to deploy ahead of the client flip, per #120).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`e2e.sh check` compares every indexed fixture's manifest wall clock with
the `wall_clock` reported by /image/metadata, /memories and
/photos/gps-summary; any disagreement fails.
- Fixture videos are stamped like phone containers: the America/New_York
wall as a UTC creation_time (the label previously showed the UTC time).
- mtimes are pinned in America/New_York instead of the host's zone.
- The e2e env sets TZ=America/New_York (the #120 preflight): the Windows
exe has no /etc/timezone, so server_zone() was otherwise None.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mobile viewer anchored its "messages around this photo" window on
the legacy date_taken (a wall clock stored as UTC for EXIF rows), while
SMS-API dates are true instants: the window was off by the photo's UTC
offset (4-5 h for photos taken at home; ImageApi #113 user story 3).
Verified SMS-API side: importers store backup epochs as UTC, and 40/40
sampled rows equal their source epochs exactly.
`/image/metadata` (+ GPS / date mutation responses) now carries
`true_instant` from `date_view::file_true_instant`: `row_true_instant`,
else the filename-class instant, else the earliest fs time. Server-side
for the same reason as `wall_clock`: the drain's permanent floor rows
have no `date_utc`. `set_capture_dates` fills both fields at all four
response sites.
dev/e2e: the manifest records each fixture's expected instant (the wall
read in its GPS zone, else America/New_York) and `e2e.sh check` asserts
it (97/97).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Serve the display wall clock and true instant to clients, so consumers render/compare dates without any zone math — the server half of the FileViewer-React #33 / Apollo #37 flips (spec #113, cutover #120).
Why server-side: #120's test run showed the drain has a permanent floor (0.5–2% of rows — edited/exported files — never get
date_utc). A client reading only the triple would have to carry the per-source legacy conventions forever.row_display_wallalready handles every row and already feeds the insight prompts, so prompt and UI agree by construction.Additive only: legacy fields byte-identical, so current clients are unaffected and this can deploy ahead of the client flip (#120 step 3).
Found while doing this: the mobile SMS window was anchored on the legacy value (wall-as-UTC) while SMS-API stores true instants (verified: 40/40 sampled rows equal their backup epochs), putting the window 4–5 h off for photos taken at home — #113 user story 3.
true_instantis the fix. Also: #113's schema table says "wall = instant − offset"; the code (correctly) adds the offset.Test plan
dev/e2e/e2e.sh check(before and after the #148 rebase): 97/97 fixtures report the expectedwall_clockon metadata / memories / gps-summary and the expectedtrue_instant(EXIF+GPS in Tokyo/Paris/LA, no-GPS, filename-class, container video, mtime, duplicate)date_view(travel-zone wall, no-offset rendering, unconverted floor row, file fallbacks, instants)filename_auditwarning); full suite (only the pre-existingfilename_auditfailure)🤖 Generated with Claude Code
wall_clock) to clients e2f33684c8true_instanton metadata responses for instant matching 9269b5c9fde3477a4710to9269b5c9fd