Repository navigation
fix(chat): bound a waiting copy by the PC clock of every read and a queued row stamp - #2973
Conversation
… a queued row's own stamp (#2949) - GET /api/pane/conversation sends x-server-time on 200 and 304; the PC proxy passes it on, and the relay sends it only to a phone that offered the server-time-header mux feature. - The browser records each answered read's PC time with its monotonic receipt time (lib/api.ts), so an idle chat, a cached remount and an upgraded bridge all use the latest read, and a wall clock step does not move the bound. - A queued row carries sending_at (the PC's clock as it went out); its waiting copy is matched against that directly.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 8 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
Review findings and how each was resolvedReviews: omo (
|
…ped row its chat never read
Second review round (omo at 6cddf78: BLOCK)
|
|
Local merge gate (owner-approved): |
…efore typing (#3165) Fixes #2974. ## What changes An immediate (composer) send now gets the same PC stamp a queued row has. The bridge takes its clock right before it types the message and returns it as `sending_at` on the `submit-result` (immediate sends, a queued send typed at once with `typed_now`, and a held copy's resend that typed). The browser bounds the send's waiting copy by that stamp alone (`stampedBaseline` in `src/lib/sentWaiting.ts`), so neither clock's moves since the chat's last read matter: 1. PC clock set back between the last read and the send: the send's own turn is no longer taken as older than the send (it stayed faded for 60 s). A shown turn's time no longer bounds a stamped send either, since a clock set back writes the new turn before it. 2. A send long after the last read (a hidden tab): the 0.1% drift allowance no longer opens a window in which an identical turn written just before the send clears its waiting copy. An older bridge sends no stamp, and the read-based bound from #2973 stays for it. The queued-row path in `machineSession.ts` uses the same helper (same behaviour, plus the shown-turn time no longer overrides the row's stamp). Protocol: `sending_at?: string` on `submit-result` (optional, additive; older clients ignore it). ## Validation - Failing first: the new tests in `src/lib/sentWaiting.test.ts`, `src/lib/ws.test.ts` and `server/pending-input.contract.test.ts` fail on origin/main (4 fails) and pass with the fix. - `bun test src/lib/sentWaiting.test.ts src/lib/ws.test.ts src/lib/machineSession.test.ts server/pending-input.contract.test.ts server/pending-input.test.ts`: pass. - `bun run check fast`: ok (unit 12666 pass, 0 fail). - Not run: full integration/browser suites (CI runs them).
Change
Fixes #2949.
#2898 bounded a sent message's waiting copy with the PC's
server_timefrom the chat's first read ofits turns. Three windows stayed open (and two more were found by reading the code): an idle chat
kept that first read and the drift allowance grew with it, a queued row fell back to the turns'
times, a bridge upgraded under an open chat was never asked again, a cached answer was timed by
the mount, and a wall clock step on this device could keep the copy beside its own turn until it
expired. In each case an identical turn another device or the terminal wrote just before the send
could clear its waiting copy (#2849's symptom), or the copy could stay too long.
The fix gives the browser a fresh PC time for every read and every queued try, and measures what
has passed since on this browser's monotonic clock:
GET /api/pane/conversationsendsx-server-timeon both 200 and 304 answers (the body'sserver_timestays). It is not part of the ETag, so an unchanged transcript still answers 304.server/machine-api.ts) passesx-server-timethrough.x-server-timeonly to a phone that offered the newserver-time-headermux feature in HELLO(
shared/relay-mux.ts,server/relay/session.ts,shared/relay-channel.ts;docs/relay.md).src/lib/api.tsrecords each answered read's PC time withperformance.now()as the answerarrived (
observeWaitingClock), whatever becomes of the body. A cached answer drawn on remounttherefore keeps its real receipt time, and each read replaces the one before.
waitingBaselineturns that intopcFloor: the PC's clock was at least the answer's time plusthe monotonic time since (less 0.1% drift) when the send began. A turn older than that is not the
send's own. No wall clock time of this browser enters that bound, so a clock step does not move
it. Without any PC time (an older bridge) the previous turns-only bound applies unchanged.
sending_at(PendingMessage, optional): the PC's clock as the try wentout, before anything is typed. It is dropped when the row is queued or held again and kept while
uncertain. A row's waiting copy is matched against it directly, so a
sendingframe replayedafter the Enter is still settled by its own turn.
Design reference: as in #2717 and #2898, the waiting copy follows Claude Code's queued-message look;
nothing visible changes here, only when the copy clears.
This PR changes an HTTP contract (a response header), a relay mux feature and the pending-message
frame, so it waits for a real review.
Validation
src/lib/sentWaiting.test.ts: another device's identical turn before the send (rewritten forthe new clock), an idle chat bounded by a later 304 from an upgraded bridge (chat: a waiting copy can still clear early for queued sends, idle chats and upgraded bridges #2949 items 2 and
3), an empty chat. Removing the
pcFloorbound fails all three. A wall clock step (item 5) andan hour of drift are covered as non-regressions.
src/lib/machineSession.test.ts: a queued row whosesendingframe came after its Enter, withanother device's identical turn 300 ms before the PC took it (item 1). Using the chat's clock
instead of
sending_atfails it.src/lib/conversationFetch.test.ts: a 304'sx-server-timerefreshes the bound, timed as theanswer arrived (items 2 and 4). Ignoring the 304 header fails it.
server/relay/session.contract.test.ts:x-server-timeonly to a phone that offered thefeature. Sending it to every phone fails it.
server/pane-routes.test.ts(header on 200 and 304),server/machine-proxy.test.ts(passedthrough on a 304),
server/pending-input.test.ts(sending_atstamped and dropped).bun run typecheckpassed; the affected unit and contract files passed (1,059 tests, 0 fail).bun run check faston e064856, merged with main (heavy wrapper on old-desktop): workflow syntax,generated types and themes, typecheck, build and unit tests all passed. Unit tests: 12,016 pass,
0 fail, 37 skip.
two pre-existing windows went to Chat waiting copy: an immediate send has no PC stamp, so a PC clock correction or a long-idle tab still misjudges its turn #2974. A sleeping device (whose monotonic clock stops) is counted
by the wall clock, and a queued row its chat never read is bounded by its stamp.
check full(integration and browser lanes, left to CI) and native macOS,Windows and phone runs. There is no visual change.