Skip to content

fix(app): dedupe message.updated rows by id when time.created differs - #49934

Open
wulart wants to merge 1 commit into
anomalyco:devfrom
wulart:fix/app-duplicate-message-row
Open

wulart wants to merge 1 commit into
anomalyco:devfrom
wulart:fix/app-duplicate-message-row

Conversation

@wulart

@wulart wulart commented Sep 19, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #41428

Type of change

  • Bug fix

What does this PR do?

Regression from #41001: the message store resolves rows by messageKey (time.created + id). The prompt input inserts an optimistic user row stamped with the client's Date.now() and sends the same id to the server, which persists it with its own timestamp. The message.updated handler misses the optimistic row (different key) and splices in a second row with the same id — the session view renders the same user message twice until a full refetch reconciles by id.

When the composite key misses, the handler now looks up the row by message id and replaces it in place (re-sorted under the new key) instead of inserting a duplicate. This restores the id-identity invariant the fetch path already enforces (reconcileFetched keys rows by id).

mergeOptimisticPage's exact-key confirmation miss (listed in the issue) can no longer fork visible rows once insertion is deduped, so it is left out to keep this change atomic.

How did you verify your code works?

New unit test in packages/app/src/context/server-session.test.ts: add an optimistic user message at time.created: 1000, then apply message.updated for the same id at 1007.

  • Fails on dev: the store holds two rows with the same id.
  • Passes with this change: single row, server time wins.

Full server-session.test.ts suite passes (75 tests) and bun run typecheck is clean.

Screenshots / recordings

Store-level change, no visual diff; the repro is unit-level.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

An optimistic user message row is created with a client-clock
time.created while the persisted message keeps the server clock. The
message.updated handler keys rows by time.created + id, so the server
event misses the optimistic row and inserts a second row with the same
id — the session view renders the same user message twice until a full
page refetch reconciles by id.

When the composite key misses, look for an existing row with the same
message id and replace it in place (re-sorted under the new key) instead
of inserting a duplicate. This matches the id-keyed dedupe the page
fetch path already applies via reconcileFetched.
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

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.

Submitted prompt is stored twice in the app message store (fork dialog duplicates, undo leaves a ghost)

1 participant