feat(windows): agent identity in card tmux + channels store cleanups#115
Open
Aryansharma28 wants to merge 11 commits into
Open
feat(windows): agent identity in card tmux + channels store cleanups#115Aryansharma28 wants to merge 11 commits into
Aryansharma28 wants to merge 11 commits into
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>
8 tasks
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.
Bundle of three Phase-7 follow-ups so a Claude session launched inside a Kanban card can talk to the human (and other cards) via the
kanbanCLI out of the box.Summary
#106— InjectKANBAN_CARD_ID+KANBAN_HANDLEinto every card-launch shell. This is the blocking one — without it thekanbanCLI inside a card falls all the way to defaulthandle="user", andkanban dm readrefuses with the "ambient identity" guard. Now both env vars are prepended to the env-prefix inbuildInnerBashCmdandbuildInnerCmdShellCmd, so they apply to tmux, bash-without-tmux, and native cmd paths (and the launch-dialog preview reflects them). User-supplied env entries still override since identity goes first. Handle is a slug ofcard.displayTitle(lowercased,[^a-z0-9_-]→-, collapsed, trimmed, ≤32 chars, falls back tocardif empty).#107— Collapsetail_messagesintoread_messages.tail_messages(ch, n)was a one-line wrapper aroundread_messages(ch, Some(n)). Removed the method and updated all three call sites (bin/kanban.rs:268,bin/kanban.rs:350, and thetail_returns_last_n_onlytest).#108— Enableserde_jsonpreserve_order. One-lineCargo.tomlflip so the channels / read-state / drafts JSON files keep insertion order on round-trip and produce byte-stable diffs.Verification
#107and#108are cargo-only — both confirmed green:#106— CLI-side end-to-end check ran against the freshly builtkanban.exewith only the env vars set (no--asflags), proving the identity resolution path the launch shell will exercise:The TS-side injection was traced by hand through both builders — for tmux the inner cmd is
cd /path && KANBAN_CARD_ID=<id> KANBAN_HANDLE=<slug> claude ..., base64-encoded as before, so the env vars survive thewsl bash -lctransport and apply toclaude(and itskanban-CLI children) inside the pane. The launch-dialog preview path uses the same builder, so the user sees what runs.Runtime GUI verification (interactive Tauri window — launching a card and running
env | grep ^KANBAN_/kanban handlefrom inside the pane) was not run in this session and should be done before merge — best with a freshly launched card on each shell variant (tmux+bash, bash-no-tmux, native cmd).Out of scope
$TMUXauto-identity (deferred perwindows-parity-port).; exec bashafter the Claude binary exits (inlineVAR=val cmdform intentional, matches existing envPrefix style; subprocess-of-Claude inheritance is what the agent path needs).envPrefixworks today).Closes #106, closes #107, closes #108
🤖 Generated with Claude Code