Skip to content

chat: clear deleted unsequenced sends pinned to bottom of scroller - #6123

Open
jamesacklin wants to merge 12 commits into
developfrom
james/tlon-5911-deleted-message-pinned-to-bottom-on-desktop
Open

chat: clear deleted unsequenced sends pinned to bottom of scroller#6123
jamesacklin wants to merge 12 commits into
developfrom
james/tlon-5911-deleted-message-pinned-to-bottom-on-desktop

Conversation

@jamesacklin

Copy link
Copy Markdown
Member

Problem

TLON-5911: a deleted message stays rendered as a "Message deleted" tombstone pinned to the very bottom of the chat scroller — below newer messages — indefinitely. Highly visible on desktop where the inverted list bottom-anchors content.

Root cause

When a message is deleted in the window after the server acknowledged the send but before the sequenced addPost echo arrived (deliveryStatus: 'sent', sequenceNum: 0):

  1. markPostAsDeleted stamps isDeleted: true on the cached row.
  2. getPendingPosts intentionally keeps deleted 'sent' rows as a tombstone source (only 'failed' rows are excluded).
  3. mergePendingPosts sorts unconfirmed (sequenceNum === 0) rows first → bottom of the inverted list.
  4. Because the delete was confirmed server-side, the sequenced addPost that would normally replace the cached row (deleteReplacedCachedPosts) never arrives — so the ghost is permanent.

Fix

  • deletePost (packages/shared/src/store/postActions/postActions.ts): once the server confirms the delete, re-read the row and hard-delete it via the existing clearUnsentPost path if it is still an unsequenced top-level 'sent' / 'needs_verification' row. A sequenced echo that lands during the delete round trip keeps the normal in-place tombstone behavior; in-flight enqueued/pending rows are left to the delivery machinery.
  • db.clearGhostPosts (packages/shared/src/db/queries.ts) + a syncStart sweep: repairs rows already poisoned in users' local DBs (like the May 9th ghost in the issue screenshot) — isDeleted + sequenceNum: 0 + deliveryStatus set + deleteStatus: 'sent' — and repoints affected channel heads.
  • PinnedPostBanner (packages/app/ui/components/Channel/PinnedPostBanner.tsx): secondary bug from the investigation — don't render the banner for a pinned post that has been deleted.

Tests

  • postActions.test.ts: new suite covering the hard-delete after confirmed delete (including pending-layer/channel-head assertions), the mid-round-trip sequenced-echo race, and rollback on poke failure.
  • queries.test.ts: clearGhostPosts removes only the ghost shape (confirmed tombstones, in-flight deletes, un-delete-confirmed rows, and replies all survive) and repoints the channel head.

All 344 shared-package tests pass; tsc clean.

Fixes TLON-5911

🤖 Generated with Claude Code

A message deleted in the window after the server acknowledged the send
but before the sequenced addPost echo arrived (deliveryStatus: 'sent',
sequenceNum: 0) survived as an isDeleted tombstone in the pending merge
layer. mergePendingPosts sorts unconfirmed rows first, so the tombstone
rendered pinned to the bottom of the chat scroller — below newer
messages — indefinitely, since the confirmed delete means the sequenced
addPost that would replace the cached row never arrives.

- deletePost: once the server confirms the delete, hard-delete a
  still-unsequenced top-level row via clearUnsentPost instead of leaving
  the tombstone. Re-reads the row first so a sequenced echo that lands
  during the delete round trip keeps the normal in-place tombstone.
- db.clearGhostPosts + syncStart sweep: repair rows poisoned before this
  fix (isDeleted + sequenceNum 0 + deliveryStatus set + delete
  confirmed) and repoint affected channel heads.
- PinnedPostBanner: don't render a pinned post that has been deleted.

Fixes TLON-5911

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jul 13, 2026

Copy link
Copy Markdown

TLON-5911

@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: 28745e5e49

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/shared/src/db/queries.ts Outdated
Codex review: `clearGhostPosts` matched any non-null deliveryStatus, so
after a restart it could hard-delete a deleted-but-still-in-flight send
(`enqueued`/`pending` with `deleteStatus: 'sent'`) that
`getDeliveryPendingPosts` deliberately keeps for delivery polling to
reconcile. Match the deletePost-side condition instead: only `sent` /
`needs_verification` rows are ghosts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jamesacklin jamesacklin changed the title chat: clear deleted unsequenced sends pinned to bottom of scroller (TLON-5911) chat: clear deleted unsequenced sends pinned to bottom of scroller Jul 13, 2026
Comment thread packages/shared/src/store/postActions/postActions.ts Outdated
Comment thread packages/shared/src/store/postActions/postActions.ts Outdated
Comment thread packages/shared/src/db/queries.ts Outdated
@dnbrwstr

Copy link
Copy Markdown
Contributor

The deletePost cleanup looks right, but I think clearGhostPosts + the syncStart sweep can go away entirely. The sweep only exists to repair rows poisoned before the write-path fix — but that can fall out of the read layer instead: a row with isDeleted + deleteStatus: 'sent' is settled, not pending, so getPendingPosts shouldn't keep it as a tombstone source and getDeliveryPendingPosts shouldn't keep polling for it. Two conditions there make old poisoned rows invisible/inert on first launch with no startup task, and cover any future path that mints this shape. (Watch the NULL handling — deleteStatus is nullable, same trap as the existing comment in getPendingPosts.) sync.ts already points at getPendingPosts as where ghost-row filtering lives, so it keeps this concern in one place.

@jamesacklin

Copy link
Copy Markdown
Member Author

Addressed the review in 4609854: removed the sync-start cleanup sweep, filtered settled deleted rows in both pending read queries with explicit nullable-status handling, and added regression coverage. @codex review

@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: 4609854282

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/shared/src/store/postActions/postActions.ts Outdated
Comment thread packages/shared/src/db/queries.ts
@jamesacklin

Copy link
Copy Markdown
Member Author

Addressed both Codex findings in f8b18b3: hard-deleted rows now emit a distinct live removal state so stale newPosts snapshots disappear immediately, and the chat-list read path performs a bounded non-destructive repair for inconsistent null-id/non-null-timestamp previews. Added regression coverage; 155 focused tests, typecheck, and lint pass. @codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: f8b18b344f

ℹ️ 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".

@jamesacklin

Copy link
Copy Markdown
Member Author

@codex review

@jamesacklin
jamesacklin requested a review from patosullivan July 13, 2026 19:45

@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: f8b18b344f

ℹ️ 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 packages/shared/src/store/postActions/postActions.ts Outdated

@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: 862c632d04

ℹ️ 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 packages/shared/src/db/queries.ts Outdated

@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: 203ac02687

ℹ️ 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 packages/shared/src/store/dbHooks.ts Outdated

@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: b1e780c429

ℹ️ 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 packages/shared/src/db/queries.ts Outdated
Comment thread packages/shared/src/store/postActions/postActions.test.ts
Comment thread packages/shared/src/store/postActions/postActions.ts Outdated
jamesacklin and others added 2 commits July 22, 2026 08:40
The develop->feature merge (b1e780c) spliced the TLON-5911
"sent-but-unsequenced post" and TLON-6133 "pinned post" describe blocks
into each other, corrupting both. Restore each block verbatim from its
parent so both test suites are intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two P2 findings from the latest Codex review:

- postActions.ts: the guarded hard-delete only ran on the delete poke's
  success path. When the ack was lost but the server confirmed the
  delete (serverPost.isDeleted or 404), the verification branches only
  set deleteStatus:'sent', leaving an acknowledged sequenceNum:0 row and
  its live isDeleted overlay behind. Extract settleConfirmedDelete() and
  run it in both verification branches so a confirmed delete never leaves
  a ghost, however it was confirmed.

- queries.ts/dbHooks.ts: getSettledDeletedGhostChannelIds flagged a
  channel if it held any settled-delete ghost, without checking the ghost
  matched the stale head. An unrelated ordinary delete (lastPostId nulled
  with a partial cache) in a channel that also held an older ghost could
  trigger recomputeChannelLastPost and blank/rewind the preview. Match the
  ghost row's receivedAt against the channel's stale lastPostAt so only the
  actual settled-delete head is repaired.

Adds regression coverage for both (unsequenced pinned lost-ack via
isDeleted and 404; receivedAt-mismatch exclusion).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jamesacklin
jamesacklin removed the request for review from dnbrwstr July 22, 2026 13:02
@jamesacklin
jamesacklin requested review from arthyn, dnbrwstr, latter-bolden and patosullivan and removed request for arthyn, latter-bolden and patosullivan July 22, 2026 13:02

@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: ca9c6a83d6

ℹ️ 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 packages/shared/src/store/postActions/postActions.ts
When a user deletes an optimistic post while its original send is still
enqueued/pending, the queued delete can be acknowledged before delivery
flips the row to `sent`. At delete time `deleteUnsequencedAcknowledgedPost`
matches nothing (delivery isn't acknowledged yet), so the guarded
hard-delete never runs and no `'removed'` overlay is emitted. Once
`markPostSent` later flips delivery to `sent`, `getPendingPosts` hides the
DB row but a mounted channel's live `newPosts` snapshot keeps rendering it
as a tombstone until remount.

Add `deleteSettledUnsequencedDeletedPost` (atomic guard: isDeleted +
deleteStatus 'sent' + sequenceNum 0 + top-level + deliveryStatus 'sent',
so it can never touch a normally delivered post) and run it from the
`markPostSent` handler, broadcasting `removeFromChannelPosts` and
recomputing the channel head when it fires.

Adds DB coverage (fires on delivered settled delete; leaves live posts,
in-flight sends, and sequenced echoes untouched) and a sync-handler test
proving markPostSent removes the row and emits the 'removed' overlay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@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: 2c7a207c08

ℹ️ 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 packages/shared/src/store/sync/sync.ts Outdated
Delivery reaches `sent` through two paths, not one: `markPostSent`
subscription events and `verifyPostDelivery()` after a send times out to
`needs_verification`. The previous commit only finished settled deletes on
the `markPostSent` path, so a post deleted while still `pending` (delete
settles first, delete-time guard finds nothing) that later verified as
delivered would strand its settled-delete row as a live tombstone.

Extract the cleanup into `finishSettledDeleteOnDelivery` (neutral module,
no import cycle) and call it from both delivery-resolve paths. Adds
verifyPostDelivery coverage: confirmed delivery hard-deletes a settled
delete and emits the 'removed' overlay, while a live post is only advanced
to 'sent'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants