Repository navigation
Add Mark as Unread and Mark as Read to the conversation menu - #121
Merged
Merged
Conversation
A read pushed to the Mac was a detached imsg-read call. When it failed, the Mac and the iPhone stayed unread and Blip showed nothing. Collector: every explicit read is saved to pendingReads in state.json before any Mac call. Each poll runs one due action. A failed action keeps its bounded error, retries with 2-60 s backoff, and survives restarts. Only a complete read snapshot from the Mac retires an intent. Under push_read=thread, per-chat reads cover DMs and groups, the same ids pushReadArgs already accepts. A read carries --through, so an inbound newer than the rendered --seen stays unread and a stale retry cannot clear it. Each intent records a desired read/unread state, so the saved format stays the same when other gestures use the queue. While the watcher is alive, the fallback poll runs every 10 s instead of every 60 s, so a queued retry is not held for a minute. Mac: read_state.py is the metadata-only unread snapshot that `imsg read-state` and imsg-read share. It follows is_read: it counts the trailing unread inbound rows, reactions included, and a read row is a barrier against old ghost rows. It folds merged phone/email DMs and never selects message bodies. imsg-read --chat counts over the whole conversation cluster, rechecks --through, and wakes a dormant menu once. It exits 0 only when chat.db agrees. `imsg chats` keeps quiet unread conversations past the sidebar limit, and rich rows carry the reaction's activity_ts. UI: blue dots and the badge follow the snapshot's per-chat counts. The badge and the header count conversations, not messages. A reaction in the rendered view advances seen_ts without moving its bubble. Refresh coalescing moves to read-sync.ts and never merges across mark-all. Mark-all stays on the bar right-click, the list button, and the a key. The conversation menu is unchanged.
Right-click a conversation and choose Mark as Unread. If it already has a dot, the item is Mark as Read. You can also press U in the list. The menu now holds these read-state items. Review contact is still on the conversation header's button. Mark as Unread works on direct messages only. Messages' menu item addresses a handle, and a group has no handle to select, so groups stay local. For a DM, the gesture queues an unread intent in the same pendingReads queue that reads use. imsg-read --unread selects the chat, clicks Mark as Unread, and exits 0 only when read_state counts the chat unread. It never opens a chat that is already unread, because opening it would mark it read. Locally, unreadSince is a per-chat override that can sit below the global read mark, so only that chat badges again. localUnreads keeps the dot through a poll that is already in flight. With push_read=off the override stays. Otherwise a complete snapshot retires it once the Mac agrees. Mark as Read from the menu is an explicit read (--act read) and queues even under the default push_read=all. Refresh coalescing treats both gestures as barriers, so read, unread, read keeps its order.
This was referenced Sep 27, 2026
Older chat.db schemas and the chat-metadata fixtures have mime_type and no uti. The voice-message MIME expression referenced a.uti unconditionally, so imsg chats aborted before it could list anything.
sqlite3.Row and a tuple expose the column name at index 1. A test that stubs execute() returns dicts, and indexing those with 1 raised KeyError before the attachment query could be skipped.
Owner
Owner
|
Merged, thank you Damon. Mark as Unread was one of the things people kept asking for, and the shape is right: it rides #120's verified queue, refuses to open a chat that is already unread (since opening would read it), records the front app first and hands focus back, and only reports success when chat.db agrees. Groups staying local, with the reason in the commit, is the honest call. Merged with main without conflicts: 672 tests, all bridge tests and the read-UI test green. It is live on both my machines with the new bridge on the Mac. #122 is next, once the focus-order fix in |
nixfred
added a commit
that referenced
this pull request
Oct 2, 2026
…icts WHAT: #117 (with #116) predates #120/#121/#123. Conflicts, all resolved by keeping both: ci.yml py_compile list gets imsg-react AND read_state.py; install.sh installs imsg-react AND read_state.py; thread.ts decorate() emits guid (tapbacks) AND seen_ts (read sync); thread.test.ts keeps the 'tapback targets' describe block AND the reaction-activity test. ui.test.ts auto-resolved to #117's guid-following version (agreed on #117). LR-T: blip, tapbacks, merge, conflicts, #116, #117 LR-D: blip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Right-click a conversation to Mark as Unread, or press
Uin the list. If it already has a dot, the item is Mark as Read. Review contact stays on this menu and on the conversation header.Direct messages only. The gesture uses the same
pendingReadsqueue as a read.imsg-read --unreadclicks Messages' own item and exits 0 only whenread_statecounts the chat unread. It does not open a chat that is already unread, because opening it would mark it read. Groups stay local. Mark as Read from the menu is an explicit read, so it queues even under the defaultpush_read=all.This is 2 of 3, stacked on #120. Review the top commit. Do not merge this before #120.
Test plan:
bun testpython3 scripts/test-read-ui.pypython3 bridge/mac/test_read.pypython3 bridge/mac/test_read_sync.pypython3 bridge/mac/test_read_verification.py