Skip to content

feat(windows): DM panel + chat notifications + drafts watcher#116

Merged
Aryansharma28 merged 3 commits into
mainfrom
feat/windows-channels-followup-part2
Jun 14, 2026
Merged

feat(windows): DM panel + chat notifications + drafts watcher#116
Aryansharma28 merged 3 commits into
mainfrom
feat/windows-channels-followup-part2

Conversation

@Aryansharma28

Copy link
Copy Markdown
Contributor

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_pairs show up under the channel list; selecting one loads the thread via read_dm_messages and renders the same layout as a channel. Send goes through send_dm with from = SELF. Drafts + read-state for DMs persist under their dms.<pairKey> slots, mirroring the channel flow. A "Start DM" button accepts @handle or card_<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 pushover module and tauri-plugin-notification (same dispatch path as the card-finish toast at lib.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.json writes now emit DRAFTS_CHANGED, and the store refetches via get_drafts within ~100ms.

Closes #109, closes #110, closes #111.

Verification

npm run build and cargo build both green (existing-warnings-only).

CLI round-trip on the worktree:

$ kanban dm send --as some-other-handle @user "ping from CLI part2 verify"
@some-other-handle → @user: ping from CLI part2 verify

$ kanban dm list
@some-other-handle__@user

$ kanban dm read --as some-other-handle @user
[2026-06-13T22:20:44.358+00:00] @some-other-handle: ping from CLI part2 verify

The on-disk pair-key format (@some-other-handle__@user.jsonl) matches the parsePartyKey / otherPartyOfPair helpers the frontend uses to render the sidebar and resolve the to party on send.

Debounce validation (#110)

Module-scoped lastNotifyAtByThread: Map<string, number> is keyed by ch:<name> / dm:<pairKey> and read inside dispatchChatNotification. If now - lastAt < 2000, the dispatch returns early without updating lastNotifyAtByThread — 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. lastSeenIdByThread updates regardless so we still track what's been observed for the next event.

Other suppression paths (each verified at the code level):

  • self-send filter via isSelf (matches cardId === null && handle === "user")
  • system/join/leave filter via (m.type ?? "message") === "message"
  • foreground suppression via useBoardStore.getState().chatOpen && selectedThread === thread
  • channel membership via channel.members.some(m => m.cardId === null && m.handle === SELF.handle)

DM panel screenshots

Screenshots best captured from a local npm run tauri dev session — 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) or card_<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 build green
  • cd windows/src-tauri && cargo build green
  • Run npm run tauri dev; open chat panel; verify DM section renders existing threads
  • From another shell: kanban dm send --as agent-A @user "ping" → DM appears in sidebar within ~100ms with unread badge; click → reads, badge clears
  • Enable OS + Pushover toggles in Settings; send 5 DMs from CLI within 2 s → 1 notification fires
  • Open the thread + leave the panel focused on it; send another DM from CLI → no notification (foreground suppression)
  • Edit drafts.json on disk: store re-fetches and the relevant draft field updates without a restart

🤖 Generated with Claude Code

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.
@Aryansharma28 Aryansharma28 merged commit 52246f6 into main Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant