feat: TUI overhaul — package extraction, open-source prep, demos#8
Open
gavin-jeong wants to merge 50 commits intomasterfrom
Open
feat: TUI overhaul — package extraction, open-source prep, demos#8gavin-jeong wants to merge 50 commits intomasterfrom
gavin-jeong wants to merge 50 commits intomasterfrom
Conversation
… pref persistence - Fix preview not auto-scrolling to newest content during live tail by removing sp.Focus guard from scrollConvPreviewToTail and calling RefreshFoldCursor after changing BlockCursor - Fix resize resetting preview to first line by preserving CacheKey (entry identity) on dimension changes and only clearing render cache - Add persistent TypeFoldPrefs/TypeFmtPrefs on SplitPane that survive across entry changes, populated via SyncTypePrefs on user interaction - Fix format prefs being cleared by left-key navigation: SyncTypePrefs now takes syncFmt param, false for left key (navigation, not intent) - Fix stale conv.split.List pointer after newConvList() calls - Fix live tail selecting agent/task sub-items instead of last message - Skip updateConvPreview during refreshConversation in live tail mode to prevent cache poisoning - Add scenario-based UX tests for preview updates, live tail, resize, and fold state (18 tests) - Refactor session/scanner and tui/stats into smaller focused files
…CLI flags - Command mode (`:` key): vim-style command palette with fuzzy suggestions for group/preview/view switching, set:ratio, refresh, keymap:edit - Config explorer (v→c): browse and edit Claude config files with split preview - Hooks view (v→h): inspect Claude Code hook configurations - Live pane proxy: unified tmux pane capture for live preview and shell-in-preview - Input modal: send text to live Claude sessions via tmux - CLI flags: -group, -preview for initial state; -search for startup filter - Keymap: configurable Command key, bootstrap config via keymap:edit command
- scrollToSearchMatch now re-renders content with updated highlights so viewport actually moves to each match on repeated n/N presses - Current match highlighted in cyan, other matches in yellow - buildMsgFullSearchMatches uses rendered content (with folds/cursor) so line numbers match the actual viewport
refreshRespondingState() re-checks IsResponding via os.Stat on every 3s tick, even when liveUpdate is disabled. Previously IsResponding was only set at initial scan time and never cleared, causing sessions to show [BUSY] indefinitely after they stopped responding.
switchToTmuxPane now calls `tmux switch-client -t <session>` when the target pane is in a different tmux session than the current one. Previously select-window/select-pane alone couldn't cross session boundaries.
The liveCaptureMsg handler always called GotoBottom(), which snapped the preview back to the bottom after every capture — even when the user had scrolled up via pgup/ctrl+b. Added paneProxy.scrolled flag that is set on scroll keys and prevents GotoBottom(). Reset when exiting copy mode (ctrl+q) or unfocusing the preview (left arrow).
The previous approach entered tmux copy mode on the remote pane for pgup/pgdown, which had two problems: 1. GotoBottom() on every capture snapped back to bottom immediately 2. Copy mode intercepted all keystrokes, breaking key forwarding New approach: on first scroll, fetch scrollback history (capture-pane -S -500) into the local viewport and scroll locally. Background tick captures continue updating content but skip GotoBottom() while scrolled. Scrolling back to bottom auto-exits scroll mode. Keystrokes are always forwarded directly to the tmux pane, never intercepted by copy mode.
Local viewport scrolling didn't work reliably for live preview. Replaced scroll keys with a jump shortcut (J or enter) that switches to the actual tmux pane where native scrollback works properly.
- ctrl+up/ctrl+down: scroll local viewport with scrollback history (fetches 500 lines on first scroll, auto-exits at bottom) - shift+enter: sends backslash + enter to tmux pane for multi-line Claude input - Background captures skip GotoBottom() while scrolled
…lict) ctrl+up/down is intercepted by macOS Mission Control. Changed to ctrl+b (page up) and ctrl+f (page down) which work everywhere.
- Skip content updates from background tick while user is scrolled, preventing scrollback from being overwritten with visible-only capture - Changed jump key from ctrl+J to ctrl+g (ctrl+j = enter in terminals, ctrl+shift+j not distinguishable) - Show scroll position indicator while scrolled
ctrl+b/f now enters copy mode on the remote tmux pane and scrolls there. Captures reflect what the pane displays (scrolled view). GotoBottom is skipped while scrolled so the view stays put. ctrl+q exits copy mode first if scrolled, then unfocuses.
…scrolled capture-pane doesn't reflect tmux copy mode view, so scroll locally: - ctrl+b/f fetches 500 lines of scrollback on first press, scrolls local viewport - Background tick captures are fully skipped while scrolled (not just GotoBottom — SetContent was also resetting the viewport) - ctrl+q exits scroll mode, ctrl+f at bottom auto-exits
…I fixes - Add vim-style navigation defaults (hjkl, g/G, ctrl+b/ctrl+f) across all views - Redesign tab/shift+tab: cycle group when list focused, cycle preview when preview focused - Add floating edit hint box (like views/actions menus) - Fix title bar: use subtle dark slate background instead of garish purple - Fix ANSI style bleed from overlay hint boxes into session list items - Clean up live preview shortcuts: ctrl+g (jump), ctrl+n (newline), ctrl+q (unfocus) - Remove dead tmux scroll code (alternate screen buffer prevents history capture)
…nt boxes overlayLine now tracks the last active SGR sequence from replaced bg cells and re-emits it after the reset, so right-side text keeps its dim/color styling.
Scan live sessions synchronously (~44ms) before starting the TUI, then load all remaining sessions asynchronously in the background. Shows a subtle spinner in the title bar during the full scan (~620ms). - Add ScanSessionsForPaths() to scan only the most recent file per project - Add DetectLiveProjectPaths() to find running Claude processes quickly - Move blocking ScanSessions out of main into async Init() Cmd - Show live sessions immediately, full list populates seamlessly
…ions Cache session metadata keyed by file path + modTime. On subsequent scans, only re-parse JSONL files that have changed. Reduces full scan from ~620ms to ~60ms (10x faster) on warm cache.
Load all session metadata from gob cache on startup instead of scanning live processes. Falls back to live-only detection if no cache exists. Full scan still runs async to refresh stale data.
Resolve conflicts: - internal/tui/app.go: keep HEAD's Config fields (Keymap, GroupMode, PreviewMode) + ClaudeDir - main.go: adopt master's CLAUDE_CONFIG_DIR env var support + keep HEAD's cached session loading
New view for browsing installed Claude Code plugins: - List installed plugins grouped by marketplace - Show version, component counts, blocked status - Detail pane with manifest info, components, install path - Search with / key, navigate with n/N - Mouse support for scroll and click
- Scan skills, scripts, settings, memory dirs (not just agents/hooks/commands/mcps) - Recurse into subdirectories for nested component layouts - Parse marketplace.json sub-plugin definitions with component paths - Discover available (not-installed) plugins from marketplace directories - Group list into INSTALLED / AVAILABLE sections with marketplace sub-headers - Show sub-plugins with component badges in detail preview - Show installed/available status badge in list and detail
Extract reusable isolatedEnv component in tmux.go that creates a fake HOME directory for running Claude in full isolation (no memories, CLAUDE.md, MCP servers, or marketplace discovery). Auth via CLAUDE_CODE_OAUTH_TOKEN. - newIsolatedEnv() seeds onboarding state from both ~/.claude/.claude.json and ~/.claude.json, writes empty MCP config - isolatedEnv.Script() builds shell command with HOME/cd/oauth/mcp isolation - isolatedEnv.RunPopup() launches in tmux display-popup - Refactor plugin test (buildPluginTestEnv) and config test (buildConfigTestEnv) to use shared isolatedEnv - Plugin actions menu with enable/disable/update/uninstall (double-press confirm) - R key to refresh plugin list
…scroll - e key opens plugin component files in $EDITOR - i key installs available (not-installed) plugins via claude plugin install - Reference docs (references/ dir + README.md) shown in plugin detail - R key refreshes both config and plugin views - Nested tmux session in display-popup for scroll support - Reusable isolatedEnv component for test environments
Parse the `source` field from marketplace manifest sub-plugins and scan the directory for all components (settings, references, etc.) instead of relying only on explicitly listed paths.
- Space toggles selection on components (✓ marker) - x opens actions menu (e:edit) - e opens all selected files in $EDITOR (or single cursor item) - esc clears selection before closing detail view
- c: copy plugin install path to clipboard - o: open shell at plugin install path - Available in both plugin list (x actions menu) and detail view
Memory files referenced via @~/.claude/... paths break in test env because HOME points to a temp dir. Instead of symlinking individual memory files, embed their content directly into a generated CLAUDE.md.
Remove --system-prompt and --setting-sources "" flags that prevented Claude from reading the generated CLAUDE.md. Now Claude discovers config files naturally from the fake HOME directory: - CLAUDE.md with embedded memory content - Symlinked memory files for @reference resolution - Symlinked hooks, skills, settings as before
Strip @reference lines from CLAUDE.md to avoid broken paths in test env. Only embed the actual content of selected files, no boilerplate header or unresolvable references.
HOME=tmpDir means @~/.claude/... references resolve correctly to the fake HOME. No need to generate or embed — just symlink selected files (including CLAUDE.md and memory) into place.
Create an editor wrapper script that restores real HOME before launching vim/nvim, so editor config is found. Also exports REAL_HOME for manual use inside the test env.
Files outside ~/.claude/ (project CLAUDE.md, local configs) were silently skipped because extractRelConfigPath returned empty. Now places them at cwd level: project/.claude/* → tmpDir/.claude/*, project/CLAUDE.md → tmpDir/CLAUDE.md.
- Fix test env memory: when root CLAUDE.md selected, symlink all its @referenced files; when only refs selected, generate minimal CLAUDE.md - Export ExtractFileReferences for cross-package use - Add TmuxWindowName to Session, searchable via win:<name> filter - Capture #{window_name} from tmux panes for all sessions - Extract OAuth token from keychain when CLAUDE_CODE_OAUTH_TOKEN unset, enabling connector MCPs (Slack, Atlassian) in test env
- Add -view flag to launch directly into config/plugins/stats view - Send initViewMsg after first WindowSizeMsg to safely initialize views - Comprehensive README rewrite covering all views, keybindings, search filters, command mode, config/plugin explorers, and CLI flags
When deleting a hook from the config explorer, also remove its command entry from settings.json (matched by script path + event type). Cleans up empty matcher blocks and event types. Undo restores both the script file and the original settings.json content. - Export ExtractScriptPath for cross-package use - Store settings path in ConfigItem.RefBy for hooks - Backup settings.json before modification for undo support
- Command mode (:) available from all views, not just sessions - Multi-command support (e.g. "view:config page:hooks") - View+page jump commands (view:stats:tools, page:memory, etc.) - URL extraction action (x→u) with search, multi-select, browser open - File path extraction action (x→f) from tool_use blocks (Read/Write/Edit/Glob/Grep) - Context-aware scoping: block → message → session fallback - Unified actions menu (x) for conversation and message-full views - Vim hjkl navigation in copy mode - Batch tmux process detection (N+2 → 3 subprocess calls) - Fix pgrep self-match by using -x for exact binary name - Fix macOS code signing cache issue in Makefile install
Move tmux interaction code (740 lines) into internal/tmux/ with three files: pane.go (commands, key mapping), live.go (session detection), isolated.go (test environment). Move URL/file extraction logic into internal/extract/ with two files: extract.go (URL parsing, categorization, browser open) and files.go (file path extraction from tool_use blocks). The tui package now imports these as dependencies instead of containing them, reducing its non-test code from ~19K to ~12K lines.
Command suggestions now filter by current view: - Sessions: group, preview, set:ratio, refresh - Config: page filters (memory, hooks, mcp, ...) - Stats: page filters (tools, errors, overview) - All views: view navigation, keymap:edit Also adds set:ratio to the registry for tab completion and compacts page: command entries with view bitmask annotations.
…ype, skip compact files - Use last message timestamp instead of first for subagent ordering - Read agent-*.meta.json for reliable agentType (e.g., "Explore", "general-purpose") - Skip agent-acompact-*.jsonl auto-compaction artifacts from subagent list
…, README - Split XML system tags (<system-reminder>, <task-notification>, etc.) into foldable system_tag content blocks, folded by default - Collapse side-question (aside_question) background context into a single summary entry showing message count - Parse imagePasteIds from JSONL, extract base64 images to temp files - Show 🖼 badge on messages with images in conversation list - `i` key opens first image from current message (macOS open) - `Enter` on image block opens it directly - `e` edit menu includes `i:image #N` choices for cached/extracted images - Add golden snapshot tests for rendering (testdata/*.golden) - Add tests for splitSystemTags, filterSideQuestionContext, defaultFolds - Comprehensive README update with all features documented
…, README - Fold system tags (<system-reminder>, <context>) in message preview - Filter aside_question agent context: show only the btw Q&A, not parent context - Place agents at correct chronological position via timestamp matching - Aside agents display with ?:btw purple badge - Context-aware edit menu: s:agent when inside subagent, p:parent for parent - Image block rendering support in message preview - Add markdown table rendering test with golden file - Export ExtractFileReferences/ExtractScriptPath in session/config.go
- Rename module github.com/sendbird/ccx → github.com/keyolk/ccx - Add 6 animated demo GIFs with automated recording script - README: add demo section, context-aware command table, debug docs - LICENSE: fill copyright (2025-2026 Gavin Jeong), fix MIT→Apache mismatch - Gate debug logging behind CCX_DEBUG env var - Fix old project name csb → ccx in temp file prefix - Expand .gitignore (vendor, *.log, .DS_Store, dist, *.prof)
# Conflicts: # internal/session/config.go # internal/session/models.go # internal/session/scanner_subagent.go # internal/tui/app.go # internal/tui/blockfilter.go # internal/tui/blockfilter_test.go # internal/tui/cmdmode.go # internal/tui/cmdmode_test.go # internal/tui/config.go # internal/tui/config_test.go # internal/tui/conversation.go # internal/tui/conversation_models.go # internal/tui/conversation_render.go # internal/tui/conversation_ux_test.go # internal/tui/keymap.go # internal/tui/live_preview_test.go # internal/tui/messages.go # internal/tui/msgfull.go # internal/tui/plugins.go # internal/tui/plugins_test.go # internal/tui/stats.go # internal/tui/stats_detail.go # internal/tui/tmux.go # main.go
# Conflicts: # README.md # internal/extract/extract.go # internal/extract/files.go # internal/tmux/live.go # internal/tui/app.go # internal/tui/cmdmode.go # internal/tui/config.go # internal/tui/live_preview_test.go # internal/tui/merge_test.go # internal/tui/plugins.go # internal/tui/render_test.go # internal/tui/urls.go # main.go
zzJinux
approved these changes
Mar 23, 2026
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
internal/tmux/(pane management, live detection),internal/extract/(URL/file extraction):) with context-aware suggestions per viewx→u/f) with search, multi-select, scoped to block/message/session/btwaside agents) — correct timestamp placement, context filtering,?:btwbadgesendbird→keyolk, LICENSE copyright, debug gating, README demosdocs/record-demos.shTest plan
go build ./...cleango test ./...— 240 tests passgo vet ./...cleansendbirdreferences in source