feat(windows): Phase 7 follow-ups (Parts 1–4)#115
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.
Previously read_jsonl loaded the whole log file into RAM and then dropped the head when a limit was given — memory O(file size) regardless of n. Adds read_jsonl_tail: walks back from EOF in 64 KB chunks, splitting on '\n' and stitching the trailing partial line across chunks via a `pending` buffer that grows naturally when a single line is bigger than the chunk. Memory is O(n × avg_line) for typical traffic. The None-limit path is unchanged. Behavior for read_channel_messages / read_dm_messages is preserved: lenient parsing, file-not-found → empty. Verified on a synthetic 1M-line / 125 MB jsonl: tail(20) completed in 708 µs (well under the O(ms) acceptance bar). Eight new unit tests cover empty, missing, fewer-than-n, exact, many-more, oversized single line (200 KB), corrupt-line tolerance, chunk-boundary stitching, and agreement with the whole-file path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-channels-followup-part1
The backend image-storage path (persist_images + image_paths field) shipped in Phase 7 but nothing on either surface produced non-null imagePaths. This wires it up. Frontend (Channels.tsx + channelsStore.ts): - Paperclip button opens the @tauri-apps/plugin-dialog file picker (multiple, scoped to common image extensions). - Tauri webview onDragDropEvent collects dropped paths when the drop position lands inside the compose region. - Paste handler iterates ClipboardEvent.clipboardData.items; image MIME blobs are pushed to a new persist_clipboard_image command which writes them to a uniquely-named file in the system temp dir and returns the path. - Queued attachments render as 64×64 thumbnails above the textarea with per-item × buttons. - MessageRow renders message.imagePaths as 160-wide thumbnails; clicking opens the file via @tauri-apps/plugin-shell. - sendMessage accepts an optional imagePaths array; passes through to send_channel_message. Backend (lib.rs): - read_image_bytes returns raw bytes for blob-URL rendering. 25 MB cap to avoid OOM if a bogus path is ever requested. - persist_clipboard_image writes pasted bytes to $TEMP/kanban-code-clipboard/<uuid>.<ext> and returns the path; the existing persist_images downstream copies it to the message's final location at send time. CLI (bin/kanban.rs): - Adds repeatable -i/--image <PATH> on `channel send` and `dm send`. - Pre-validates paths and prints a non-fatal warning + skip for missing files (matches the lenient persist_images contract). Verified: cargo build + npm run build green; `kanban channel send --help` shows the new flag; lenient skip behavior confirmed with mixed real/missing input. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…113) Adds the four chat affordances messages need to feel real, all using the append-only JSONL strategy the macOS Swift app uses — no rewrite of existing rows, so loading a pre-#113 channel still parses cleanly. Schema (channels.rs): - New MessageType variants Edit, Delete, Reaction. Existing Message/Join/ Leave/System untouched. - New MessageRefs struct with optional editsMessageId / reactionTo / emoji carries the cross-row pointer. - ChannelMessage gets optional `refs` and `mentions` fields. Both skip_serializing_if = "Option::is_none", so legacy rows round-trip without phantom fields. - extract_mentions(body): pure helper used at send time to populate mentions from the literal "@handle" tokens in the body. Store + Tauri commands: - edit_channel_message / delete_channel_message / react_channel_message and DM equivalents append the appropriate row with refs populated. - send_message / send_dm now also populate mentions automatically. Render-time collapse helper (src/lib/messageCollapse.ts): - Pure module so the channels view AND the future DM view share one pipeline. Walks raw messages once, builds the rendered view: applies latest Edit per id, marks Delete, aggregates Reaction rows with toggle semantics (odd count per (target, emoji, sender) = on). - tokenizeBody splits body into text + mention spans for styled rendering. UI (Channels.tsx): - MessageRow grew hover affordances: react, edit (own), delete (own). Inline edit mode replaces the body with a textarea; Save / Cancel / Esc behave naturally. - Reaction picker is a small static emoji palette (👍 ❤️ 😄 🎉 😢 😮 🙏); chips show counts and highlight when SELF has reacted; click toggles. - Deleted messages render as a "(message deleted)" stub so the list doesn't reflow. - @mentions in stored bodies are styled as blue handle pills. - MentionTextarea: typeahead popup on `@<query>` against the channel member list, with arrow/Enter/Tab/Escape keybindings. Read state filter: - unreadCount / markRead now ignore edit/delete/reaction rows so a side- channel action on a read message doesn't inflate the unread badge. CLI (bin/kanban.rs): - `channel edit <name> <messageId> <body…>` - `channel delete-msg <name> <messageId>` - `channel react <name> <messageId> <emoji>` - Equivalent `dm edit / delete-msg / react` subcommands. Verified: 32 Rust tests pass (added pre_113_message_still_parses, message_with_refs_round_trips, extracts_mentions_in_first_occurrence_order, and an end-to-end edit_delete_react_round_trip store test). cargo build and npm run build are green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brings the #112/#113/#114 work onto the Part 1+2 trunk so the full channels feature can be reviewed and dogfooded together. Conflict resolution notes: - src-tauri/src/lib.rs — Part 2's notify_chat_message and Part 3's read_image_bytes / persist_clipboard_image both sit immediately after save_drafts; kept both, and registered all of notify_chat_message, read_image_bytes, and persist_clipboard_image in invoke_handler. - src/store/channelsStore.ts — extended sendDm to take imagePaths (matching sendMessage's new signature). Added DM-side editDm/deleteDm/ reactDm actions to mirror the channel-side ones. Hooked the new isVisibleRow filter into markDmRead / unreadDmCount so reactions on already-read DMs don't bump the badge. - src/components/Channels.tsx — collapsed the previously-separate ChannelPane / DmPane / ThreadPane plumbing so ThreadPane now also receives `members` (for @mention autocomplete), `rawMessages`, and the onEdit/onDelete/onReact callbacks. DmPane synthesizes a 2-member list from SELF + the other party so mention autocomplete works inside DMs too. Single textarea-vs-MentionTextarea + drag-drop/paste/attachments surface is now shared between both surfaces. Verified: - cargo test --lib: 69 passed. - npm run build: clean. - CLI end-to-end smoke: create channel → send w/ -i image + warn-skip missing → react → edit (mentions re-extracted) → history → delete. All shapes (mentions, refs.reactionTo, refs.editsMessageId, imagePaths) serialize correctly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two macOS-side features from late May/early June that were missed in the Phase 7 parity audit. Both ride into the Part 1 follow-up branch. Pinned cards (mirrors macOS Sources/.../BoardStore + BoardView): - Adds pinned_at / pinned_sort_order to Link, byte-compatible with the macOS JSON shape. Archive/move-to-all-sessions clear the pin. - Adds set_card_pinned + reorder_pinned_cards Tauri commands. - Adds a Pinned strip above the columns in BoardView with horizontal drag-reorder. Pinned cards are hidden from their real lane so they don't double-render. - Right-click "Pin to top" / "Unpin from top" in the card context menu; pinned cards show a small gold pin glyph next to the title. - merge_ops inherits the pin from source when target wasn't pinned. Self-compact (data shapes + drop-guard, mirrors macOS SelfCompactSettings): - New SelfCompactSettings struct with byte-compatible defaults (500k/ 600k/700k queueing, 750k /compact). - QueuedPrompt grows a selfCompactThresholdTokens field so the guard can distinguish auto-enqueued nudges from manual prompts. - New context_usage.rs reads <data_dir>/context/<sessionId>.json (the statusline-emitted file, same path layout as macOS). - New should_drop_self_compact_prompt command runs the macOS shouldDropStaleSelfCompactPrompt logic. Wired into the CardDetailView auto-send path so a compact nudge that the user already addressed gets dropped instead of sent. - SettingsView gets a Self-compact toggle plus a read-only summary of the configured thresholds. Out of scope here (each deserves its own PR): - Polling/generation loop that *enqueues* compact prompts when usage crosses a threshold. Needs the statusline script to emit the context-usage JSON in the first place — no plumbing on Windows yet. - Channel reorder (the macOS commit bundled "channels + pinned" — only pinned ported here).
8 tasks
…ompact feat(windows): pinned cards + self-compact settings
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
Lands all four follow-up parts from the 2026-06-13 Phase 7 audit into `main` as one bundle. Parts 2, 3, and 4 were merged into this branch in flight, so this PR is the integration target.
Part 1 — Agent runtime wiring
Part 2 — Chat UX completeness
Part 3 — Chat polish
Part 4 — Late-May macOS board features the audit missed
Closes
Closes #106, closes #107, closes #108, closes #109, closes #110, closes #111, closes #112, closes #113, closes #114
Verify
Known follow-ups (NOT in this PR)
🤖 Generated with Claude Code