feat(windows): DM panel + chat notifications + drafts watcher#116
Merged
Conversation
Closes the gap in the 4-event channels watcher contract: writes to drafts.json now emit a DRAFTS_CHANGED event, and the frontend store refetches via get_drafts. This lets a second app instance — or an external edit — pick up draft changes within ~100ms instead of being stuck on the in-memory copy until the next save_drafts call.
The chat panel renders a Direct Messages section alongside Channels. Threads from list_dm_pairs are shown in the sidebar; selecting one loads via read_dm_messages and shows the same thread layout as a channel. Sends go through send_dm with from=SELF; drafts and read-state are persisted under their respective dms.<pairKey> slots, mirroring the channel flow. Adds a Start DM affordance that accepts an @handle or card_<ksuid>, plus the parsePartyKey / dmPairKey / otherPartyOfPair helpers needed to map between the on-disk sorted-pair-key format and ChannelParticipant.
Channel and DM message events now fire an OS notification (via tauri-plugin-notification) and a Pushover push (via the existing pushover module) when: - SELF is a channel member, or SELF is one of the DM pair parties - The message isn't from SELF - The message kind is "message" (system/join/leave are skipped) - The chat panel isn't open on that exact thread (foreground = skip) A per-thread 2s debounce coalesces bursts: 5 agent messages within 2s fire one notification, not five. The frontend store does the "should we notify?" decision; the new notify_chat_message Tauri command reuses the same dispatch path the card-finish notifications already use, so settings toggles + Pushover creds work the same way.
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.
Summary
Three follow-ups that round out the Phase 7 channels work shipped in #105:
feat(windows): DM panel in chat UI #109 — DM panel in chat UI. Adds a Direct Messages section to the chat sidebar. Threads from
list_dm_pairsshow up under the channel list; selecting one loads the thread viaread_dm_messagesand renders the same layout as a channel. Send goes throughsend_dmwithfrom = SELF. Drafts + read-state for DMs persist under theirdms.<pairKey>slots, mirroring the channel flow. A "Start DM" button accepts@handleorcard_<ksuid>and opens (or creates) the thread.feat(windows): wire OS + Pushover notifications for inbound channel/DM messages #110 — notifications for inbound chat messages. Channel and DM events now fire OS + Pushover notifications, reusing the existing
pushovermodule andtauri-plugin-notification(same dispatch path as the card-finish toast atlib.rs:907–979). Suppression rules: SELF-sends, system/join/leave messages, non-member channels, foreground-focused threads. Per-thread 2s debounce coalesces bursts.feat(windows): emit drafts-changed watcher event #111 — drafts-changed watcher event. Closes the gap in the 4-event watcher contract:
drafts.jsonwrites now emitDRAFTS_CHANGED, and the store refetches viaget_draftswithin ~100ms.Closes #109, closes #110, closes #111.
Verification
npm run buildandcargo buildboth green (existing-warnings-only).CLI round-trip on the worktree:
The on-disk pair-key format (
@some-other-handle__@user.jsonl) matches theparsePartyKey/otherPartyOfPairhelpers the frontend uses to render the sidebar and resolve thetoparty on send.Debounce validation (#110)
Module-scoped
lastNotifyAtByThread: Map<string, number>is keyed bych:<name>/dm:<pairKey>and read insidedispatchChatNotification. Ifnow - lastAt < 2000, the dispatch returns early without updatinglastNotifyAtByThread— so the 2 s window is anchored at the first notification, not the most recent. A burst of 5 messages within 2 s = 1 notification; the 6th message after the window starts a fresh debounce.lastSeenIdByThreadupdates regardless so we still track what's been observed for the next event.Other suppression paths (each verified at the code level):
isSelf(matchescardId === null && handle === "user")(m.type ?? "message") === "message"useBoardStore.getState().chatOpen && selectedThread === threadchannel.members.some(m => m.cardId === null && m.handle === SELF.handle)DM panel screenshots
Screenshots best captured from a local
npm run tauri devsession — the GUI is functional but I can't reliably screenshot a Tauri window from this environment. The sidebar gains a "Direct Messages" section under the existing "Channels" section, each row showing@handle(blue dot) orcard_<id>(purple dot) with an unread badge identical in styling to channel rows. Selecting a row swaps the main pane to a thread layout identical to a channel.Test plan
cd windows && npm run buildgreencd windows/src-tauri && cargo buildgreennpm run tauri dev; open chat panel; verify DM section renders existing threadskanban dm send --as agent-A @user "ping"→ DM appears in sidebar within ~100ms with unread badge; click → reads, badge clearsdrafts.jsonon disk: store re-fetches and the relevant draft field updates without a restart🤖 Generated with Claude Code