Skip to content

fix(chat): bound an immediate send's waiting copy by the PC's stamp before typing - #3165

Merged
devswha merged 2 commits into
mainfrom
fix/immediate-send-pc-stamp
Nov 6, 2026
Merged

devswha merged 2 commits into
mainfrom
fix/immediate-send-pc-stamp

Conversation

@devswha

@devswha devswha commented Nov 6, 2026

Copy link
Copy Markdown
Owner

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).

@coderabbitai

coderabbitai Bot commented Nov 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You'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 16 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 0ebd4b9 and dc5c66e.

Files selected for processing: 10

  • changelog.d/chat-waiting-copy-send-stamp.md
  • server/core/pending.ts
  • server/pending-input.contract.test.ts
  • shared/protocol.ts
  • src/lib/machineSession.ts
  • src/lib/sentWaiting.test.ts
  • src/lib/sentWaiting.ts
  • src/lib/useComposerSends.ts
  • src/lib/ws.test.ts
  • src/lib/ws.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Share

@devswha

devswha commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Review summary at dc5c66e (no fix round needed: no HIGH).

  • omo review: CLEAR.
  • Codex (Terra, xhigh) adversarial review: no HIGH; six MEDIUMs from synthetic bun -e probes, none user-reproduced and none looser than main before this PR except the trade-off in (1):
    1. After a PC clock set back, an unseen identical turn written just before the setback can be newer than the new stamp. Trade-off: main kept the shown turns' newest time as a bound, which leaves the send's own turn stuck for 60 s in the same setback (the bug in 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). The stamped path drops that bound on purpose.
    2. The stamp is taken before submitText's checks, so an identical turn another device writes in that sub-second window can pass. Main's read-based floor is never later than this stamp, so the window is narrower than before.
    3. Whole-second transcript times (Grok) keep their existing one-second allowance against the floor (unchanged by this PR).
    4. A clock set back between the stamp and the turn's write leaves the copy until it expires (inherent; needs a step within that sub-second window).
    5. A queued row's late sending frame can capture its own already-shown turn in the baseline keys (unchanged by this PR).
    6. Older bridges send no stamp and keep fix(chat): bound a waiting copy by the PC clock of every read and a queued row stamp #2973's read-based bound (compatibility limit).
      No follow-up issue: none reproduced as a user-facing failure.

@devswha

devswha commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Merge note (merge pass) at head dc5c66e:

@devswha
devswha merged commit 06c2d62 into main Nov 6, 2026
29 of 31 checks passed
@devswha
devswha deleted the fix/immediate-send-pc-stamp branch November 6, 2026 09:46
devswha added a commit that referenced this pull request Nov 6, 2026
…whole-second guard (#3192)

Fixes #3166.

## Problem
A send the PC stamps (`sending_at`, #3165) gets a baseline whose
`pcFloor` is the stamp. For a transcript with whole-second times (Grok),
`newerThanSend` accepted any turn with `at + 1000 >= pcFloor`. When the
stamp came before the chat's first transcript read, the baseline held no
shown turns, so an older identical turn of the same second (already
answered within that second) cleared the new send's waiting copy. The
unstamped first-read path keeps a guard for exactly this case; the
stamped path skipped it.

## Fix (`src/lib/sentWaiting.ts`)
- `stampedBaseline` marks a baseline made from the stamp alone (no read
behind it) with `unread: true`.
- In the `pcFloor` branch, a turn at or after the floor settles at once,
as before. A turn admitted only by the whole-second slack on an `unread`
baseline needs the unstamped path's evidence: it is the last turn read,
or a turn after it is dated in a later second.
- The slack comparison is now strict (`at + coarse > pcFloor`): a
whole-second turn dated exactly one second before the floor was written
before it.

The guard is deliberately limited to `unread` baselines. An adversarial
Codex pass showed that applying it to every stamped send keeps a Grok
send waiting for the full 60 s whenever its answer starts in the same
second (Grok keeps the answer's start time), which is the common case
after a read. Before the first read that trade-off is the one the
unstamped path already accepts.

## Known limit (unchanged, shared with the unstamped path)
Before the chat's first read, if an older identical turn is followed by
an unrelated turn from another device in a later second before the new
send's own turn is written, the later turn counts as the older one's
answer and settles the send. Same behaviour as the existing unstamped
guard.

## Validation
- Failing first: the new test "keeps a send the PC stamped before the
chat's first read waiting past an older identical turn of the stamp's
second" failed on origin/main and passes with the fix.
- New test "settles a stamped send after the chat's read by its own
whole-second turn though the answer began in that second" pins the
regression Codex found in a broader first version.
- `bun test src/lib/sentWaiting.test.ts src/lib/useComposerSends.test.ts
src/lib/machineSession.test.ts src/components/SentWaiting.test.tsx`: 120
pass.
- `bun run check fast`: pass (12799 unit tests, typecheck, build).
- Skipped: integration and browser suites (client logic only, covered by
unit tests).
- Review: Codex (gpt-6.1-terra, adversarial) on the first version; its
MEDIUM regression and LOW boundary are fixed here, its second MEDIUM is
the known limit above.
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.

Chat waiting copy: an immediate send has no PC stamp, so a PC clock correction or a long-idle tab still misjudges its turn

1 participant