Repository navigation
Add Pin and Hide Alerts to the conversation menu - #122
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.
Right-click a direct message to Pin or Unpin it, or to Hide or Show Alerts. Blip updates the row at once. Then imsg-read selects the chat on the Mac, clicks Messages' own menu item, and checks the pinning plist or the ignoreAlertsFlag in chat.properties for the new state. If the action fails, the error shows as the collector error. `imsg chats` now reports muted from chat.properties, so the menu shows the right label. Groups are refused because these menu items address a handle.
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.
WHAT: Pin, Unpin, Hide Alerts and Show Alerts now run through one conversation_action(). The front app is captured BEFORE `open imessage:` selects the DM, and every exit path (selection failure, menu error, failed verification) hands focus back to it. chat_pinned and chat_muted are tri-state (True / False / None) and scoped to the conversation cluster: chat_pinned reads Messages' ordered pin list (pD.pP with pZ aliases, via imsg's new ordered_pins) and matches every cluster id, guid and group id the way `imsg chats` does; chat_muted reads ignoreAlertsFlag on every chat row of the cluster. An unreadable pin list, an unknown pin layout, a missing chat, a malformed properties plist or an unreadable chat.db is None, and None is a failure (exit 75) for the off actions as well as the on ones. settle_flag keeps waiting through a transient None. Opening a DM to pin or mute it no longer silently reads it: the unread count is taken first and, if opening the conversation read it, Mark as Unread puts it back while the chat is still selected; a restore that cannot land is reported on stderr. The four flags refuse group ids on the Mac side too. `imsg chats` reports `muted` over the same cluster (muted_conversations), so the list and the write agree. WHY: The review of nixfred#122 found click_conversation_item selected first and captured the front app second, so it restored Messages to itself; that chat_pinned flattened every string in the plist, compared one handle, and returned False when nothing could be read; and that chat_muted turned a malformed plist or missing chat into False. Together those could acknowledge an Unpin or Show Alerts that never happened. ENABLES: Pin and alerts acknowledged only on Messages' own records; verified, failed and unknown outcomes covered by mocked tests that never open Messages. CONTEXT: crew task blip-pr122-fixes-a2, fixes 1, 2 and 4 from the review of PR nixfred#122 (Add Pin and Hide Alerts to the conversation menu). Added on top of the contributor's commits. TAGS: imsg-read, pin, hide-alerts, focus, verification, tri-state, cluster, merged-dm, unread, tests LR-T: imsg-read, pin, hide-alerts, verification, focus-restore LR-D: blip, mac-bridge LR-K: tri-state-verification, cluster-aware-pins, capture-front-before-select, keep-unread
WHAT: New pure applyAlertsOff(threads, alertsOff) re-applies the cached Hide Alerts set to a shallow poll's threads, the same way applyPins re-applies cached pins. collect() computes alertsOff before the threads and runs the shallow path through applyPins then applyAlertsOff. Unchanged rows are returned as the same objects so the widget can still skip identical lists. Tests cover deep -> shallow (muted survives), Show Alerts at the next chat list (muted clears), row reuse, and a conversationAct failure carrying the Mac's reason. CHANGELOG states the verification and unread behaviour. WHY: buildThreads initializes muted: false and only applyPins ran on shallow output, so a muted conversation rendered as unmuted after every shallow poll; BarWidget's overlay replaced the deep row wholesale and the menu offered Hide Alerts for a chat whose alerts were already off. ENABLES: The conversation menu shows the right Hide/Show Alerts label between deep runs. CONTEXT: crew task blip-pr122-fixes-a2, fix 3 from the review of PR nixfred#122 (Add Pin and Hide Alerts to the conversation menu). TAGS: collector, hide-alerts, shallow-poll, muted, threads, tests LR-T: collector, hide-alerts, shallow-poll LR-D: blip, collector LR-K: apply-alerts-off, shallow-poll-state
|
Thank you, @damonjanis. Pin and Hide Alerts in the conversation menu fit Blip well: they use Messages' own menu, add no config surface, and treat
Results (GitHub CI on 57162ff, merged with current main, all steps green): I also ran the same checks locally on the branch and on its merge with main. Everything passed except Overlaps: #83 touches the same collector and bridge paths, and #25 touches the same UI files. Whichever lands second will need a rebase. nixfred will merge it. Thanks again for building this. |
|
Merged, thank you Damon. Pin and Hide Alerts are live on both my machines with the new Mac bridge. The four review fixes went in on top of your commits, as described above. I re-checked the focus order myself before merging: the front app is captured before the conversation is selected and handed back on every exit. With #120 and #121 that completes the series. |
… they sat open (one-way doors) LR-T: blip, weekly-notes, change-log, roster, door-classifier, #127, #122, #83, #131 LR-D: blip Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BeRMdPWNC2myz9U8hEeeCi
The conversation menu's Pin, Hide Alerts and read/unread icons (#122) live in icons/, which the documented cp never copied. A plugin deployed that way logs 'Cannot open: .../icons/*.svg' and shows the menu without its icons. Clones from the README install path were never affected.
WHAT: the developer deploy line in CLAUDE.md, AGENTS.md and CONTRIBUTING.md now copies icons/ (cp -r ... icons). From Ian Swope (github.com/ianswope, X @ianfs). WHY: #122 added icons/{pin,moon,read,unread}.svg, loaded by BlipView's menuIcon() via Qt.resolvedUrl('icons/<name>.svg'); a plugin deployed by the old line logged 'Cannot open ... icons/*.svg' and the conversation menu rendered without icons. README git-clone installs were unaffected. gus and vic already have icons/ (Larry's deploys rsync icons/***). VERIFIED: docs only, door two-way, CI green. LR-T: blip, docs, deploy, icons, claude-md, ianswope LR-D: blip LR-R: #135 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BeRMdPWNC2myz9U8hEeeCi
Right-click a direct message to Pin or Unpin it, or to Hide or Show Alerts. The row updates immediately.
imsg-readthen clicks Messages' own menu item and checks the pinning plist orignoreAlertsFlag. An unreadable database is a failure, not success.Hide Alerts also stops Blip's own toasts for that chat. The conversation stays in the list. The muted ids come from the last full chat list and are kept across shallow polls. Groups are refused, because these items address a handle.
This is 3 of 3, stacked on #120 and #121. Review the top commit. Do not merge this before those two.
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