Add CCWeb: React web gateway to Claude Code sessions#75
Closed
JanusMarko wants to merge 65 commits intosix-ddc:mainfrom
Closed
Add CCWeb: React web gateway to Claude Code sessions#75JanusMarko wants to merge 65 commits intosix-ddc:mainfrom
JanusMarko wants to merge 65 commits intosix-ddc:mainfrom
Conversation
New env var sets a fixed starting directory for the directory browser, falling back to Path.cwd() if not set (preserving current behavior). https://claude.ai/code/session_01Vn1pxPc8KahAYpofYGhLjY
…6kvZ Add CCBOT_BROWSE_ROOT config for directory browser start path New env var sets a fixed starting directory for the directory browser, falling back to Path.cwd() if not set (preserving current behavior). https://claude.ai/code/session_01Vn1pxPc8KahAYpofYGhLjY
Four callback handlers (CB_DIR_SELECT, CB_DIR_UP, CB_DIR_PAGE, CB_DIR_CONFIRM) and build_directory_browser's invalid-path fallback used raw Path.cwd() instead of config.browse_root. This meant users could escape the configured browse root if user_data was lost or the path became invalid during navigation. https://claude.ai/code/session_01Vn1pxPc8KahAYpofYGhLjY
…6kvZ Fix inconsistent Path.cwd() fallbacks in directory browser callbacks Four callback handlers (CB_DIR_SELECT, CB_DIR_UP, CB_DIR_PAGE, CB_DIR_CONFIRM) and build_directory_browser's invalid-path fallback used raw Path.cwd() instead of config.browse_root. This meant users could escape the configured browse root if user_data was lost or the path became invalid during navigation. https://claude.ai/code/session_01Vn1pxPc8KahAYpofYGhLjY
- session.py: Replace deprecated asyncio.get_event_loop() with asyncio.get_running_loop() (Python 3.12+ compat) - session.py: Remove redundant pass statements - session_monitor.py: Consolidate double stat() call into one - screenshot.py: Add explicit parens in _font_tier() for clarity - bot.py: Add /kill command handler — kills tmux window, unbinds thread, cleans up state, and best-effort deletes the topic. Previously the /kill bot command was registered in the menu but had no handler, falling through to forward_command_handler. https://claude.ai/code/session_01Vn1pxPc8KahAYpofYGhLjY
…6kvZ Fix misc bugs: asyncio deprecation, double stat, missing /kill handler - session.py: Replace deprecated asyncio.get_event_loop() with asyncio.get_running_loop() (Python 3.12+ compat) - session.py: Remove redundant pass statements - session_monitor.py: Consolidate double stat() call into one - screenshot.py: Add explicit parens in _font_tier() for clarity - bot.py: Add /kill command handler — kills tmux window, unbinds thread, cleans up state, and best-effort deletes the topic. Previously the /kill bot command was registered in the menu but had no handler, falling through to forward_command_handler. https://claude.ai/code/session_01Vn1pxPc8KahAYpofYGhLjY
Add timestamp-based deduplication in handle_interactive_ui() to prevent both JSONL monitor and status poller from sending new interactive messages in the same short window. The check-and-set has no await between them, making it atomic in the asyncio event loop. Also add a defensive check in status_polling.py to skip calling handle_interactive_ui() when an interactive message is already tracked for the user/thread (e.g. sent by the JSONL monitor path). https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
…returning list snapshot iter_thread_bindings() was a generator yielding from live dicts. Callers with await between iterations (find_users_for_session, status_poll_loop) could allow concurrent unbind_thread() calls to mutate the dict mid-iteration, causing RuntimeError: dictionary changed size during iteration. Fix: rename to all_thread_bindings() returning a materialized list snapshot. The list comprehension captures all (user_id, thread_id, window_id) tuples eagerly, so no live dict reference escapes across await points. Changes: - session.py: iter_thread_bindings -> all_thread_bindings, returns list - bot.py, status_polling.py: update all 4 call sites - Remove unused Iterator import from collections.abc - Add tests: snapshot independence, returns list type, empty bindings https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
queue.join() in handle_new_message blocked the entire monitor loop while waiting for one user's queue to drain. If Telegram was rate-limiting, this could stall all sessions for 30+ seconds. Fix: use enqueue_callable() to push interactive UI handling as a callable task into the queue. The worker executes it in FIFO order after all pending content messages, guaranteeing correct ordering without blocking. Also fixes: - Callable tasks silently dropped during flood control (the guard checked task_type != "content" which matched "callable" too; changed to explicit check for "status_update"/"status_clear" only) - Updated stale docstring in _merge_content_tasks referencing queue.join() https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
unpin_all_forum_topic_messages was used every 60s to detect deleted topics,
but it destructively removed all user-pinned messages as a side effect.
Replace with send_chat_action(ChatAction.TYPING) which is ephemeral
(5s typing indicator) and raises the same BadRequest("Topic_id_invalid")
for deleted topics. All existing error handling works unchanged.
https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
- Add MAX_TASK_RETRIES=3 retry loop for short RetryAfter (sleep and retry) - Re-queue tasks on long RetryAfter (>10s) with MAX_REQUEUE_COUNT=5 cap - Convert callable_fn from Coroutine to Callable factory (coroutines are single-use; retry requires a fresh coroutine each attempt) - Catch RetryAfter from _check_and_send_status to prevent cosmetic status updates from triggering content message re-sends - Fix test isolation: clear _last_interactive_send in test fixtures https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
The _file_mtimes dict used mtime+size to skip unchanged JSONL files, but this introduced edge cases (sub-second writes, clock skew, file replacement). For append-only JSONL files, comparing file size against last_byte_offset is sufficient and eliminates all mtime-related issues. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
Previously byte offsets were persisted to disk BEFORE delivering messages to Telegram. If the bot crashed after save but before delivery, messages were silently lost. Now offsets are saved AFTER the delivery loop, guaranteeing at-least-once delivery: a crash before save means messages are re-read and re-delivered on restart (safe duplicate) rather than permanently lost. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
Dead sessions were cleaned from persistent state but never from the in-memory _pending_tools dict, causing a slow memory leak over time. Add pop() calls in both cleanup paths (startup + runtime). https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
Previously _pending_thread_text was cleared from user_data BEFORE attempting to send it to the tmux window. If send_to_window() failed, the message was lost and the user had to retype it. Now the pending text is only cleared after a successful send. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
Typing indicators in forum topics were silently failing because message_thread_id was not passed to send_chat_action calls. Users in forum topics wouldn't see typing indicators while Claude worked. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
The except Exception handler was catching RetryAfter (Telegram 429
rate limiting) and BadRequest("message is not modified"), preventing
proper rate limit propagation and causing unnecessary duplicate
message sends.
Changes:
- Re-raise RetryAfter in both edit and send paths so the queue
worker retry loop can handle rate limiting correctly
- Treat BadRequest "is not modified" as success (content identical)
- For other BadRequest errors (message deleted, too old), delete
orphan message before falling through to send new
- Log exception details in catch-all handler for debugging
https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
When JSONL monitoring enqueues _send_interactive_ui, the callable may execute after the interactive UI has been dismissed. This caused stale callables to potentially send duplicate interactive messages. Fix: introduce a monotonically incrementing generation counter per (user_id, thread_id) key. Every state transition (set_interactive_mode, clear_interactive_mode, clear_interactive_msg) increments the counter. The JSONL monitor captures the generation at enqueue time and passes it to handle_interactive_ui via expected_generation parameter. If the generation has changed by execution time, the function bails out. The status poller is unaffected (passes None, skipping the guard). https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
The second all_thread_bindings() call gets a fresh snapshot that naturally excludes entries unbound by the topic probe loop above. This is correct behavior, not a bug — add a comment to clarify the intent for future readers. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
The return value was already handled correctly (proceed regardless), but the ignored bool looked like a bug. Add a comment explaining that on timeout the monitor's 2s poll cycle picks up the entry, and thread binding, pending text, and topic rename work without session_map. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
…e-messages-FpKAU Document intentionally ignored wait_for_session_map_entry return value The return value was already handled correctly (proceed regardless), but the ignored bool looked like a bug. Add a comment explaining that on timeout the monitor's 2s poll cycle picks up the entry, and thread binding, pending text, and topic rename work without session_map. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
…edia Telegram clients fail to re-render document thumbnails when editing document-type media in place via editMessageMedia, causing a "white circle with X" on screenshot refresh. Switch from reply_document + InputMediaDocument to reply_photo + InputMediaPhoto, which Telegram clients handle reliably for inline image edits. Also adds debug logging for the key-press screenshot edit path. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
…e-messages-FpKAU Fix screenshot refresh showing broken preview by switching to photo m…
Check pane_current_command before sending keys to tmux windows. If the pane is running a shell (bash, zsh, etc.), Claude Code has exited and user text must not be forwarded — it would execute as shell commands. Guards added to: send_to_window (safety net), text_handler (with auto-unbind), esc_command, usage_command, and screenshot key-press callback. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
When send_to_window detects the pane is running a shell, it now captures the pane content and looks for: - "Stopped ... claude" → sends "fg" (suspended process) - "claude --resume <id>" → sends the resume command Waits up to 3s (fg) or 15s (--resume) for Claude Code to take over the terminal, then sends the user's original text. If no resume command is found, the text_handler unbinds the topic and tells the user to start a new session. https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
…_pane Reduces tmux subprocess calls from ~120/s to ~21/s with 20 windows by: - Adding 1-second TTL cache to list_windows() (all callers in the same poll cycle share one tmux enumeration instead of N) - Unifying capture_pane() to always use direct `tmux capture-pane` subprocess (plain text mode previously used libtmux which generated 3-4 tmux round-trips per call) - Invalidating cache on mutations (create/kill/rename) https://claude.ai/code/session_016c4b8ioybZyscNayeY6Y18
…e-messages-FpKAU Claude/fix duplicate interactive messages fp kau
When an assistant message contains both text blocks and an interactive tool_use (ExitPlanMode/AskUserQuestion), the text entries were processed first in handle_new_message, clearing the interactive UI state set by the status poller. This caused the JSONL callable to send a second interactive message instead of editing the existing one. Fix: pre-scan assistant messages for interactive tools and suppress text block emission when present — the terminal capture already includes that preamble text. https://claude.ai/code/session_01WHUN1GLBFr2ZkuEmeVtuPW
…icates-eUNgW Fix duplicate interactive UI messages for numbered answers When an assistant message contains both text blocks and an interactive tool_use (ExitPlanMode/AskUserQuestion), the text entries were processed first in handle_new_message, clearing the interactive UI state set by the status poller. This caused the JSONL callable to send a second interactive message instead of editing the existing one. Fix: pre-scan assistant messages for interactive tools and suppress text block emission when present — the terminal capture already includes that preamble text. https://claude.ai/code/session_01WHUN1GLBFr2ZkuEmeVtuPW
The 1.x release removed _update_block and renamed escape_latex, breaking the markdown_v2.py import. Pin to 0.5.x which has the API our code depends on. https://claude.ai/code/session_01Db4zZKSaAkJrgHGzeLjRsz
New self-contained project in ccweb/ that replaces the Telegram interface with a browser-based web frontend for Claude Code sessions via tmux. Backend (Python/FastAPI): - Forked 7 core modules from ccbot (tmux_manager, terminal_parser, session_monitor, transcript_parser, monitor_state, hook, utils) with imports adapted for ccweb (ccweb_dir, separate ~/.ccweb/ state) - New config.py: web server settings, no Telegram dependencies - New session.py: simplified client_bindings model (no thread_bindings) - New server.py: FastAPI + WebSocket for real-time bidirectional comms - New ws_protocol.py: typed message definitions (message, interactive_ui, decision_grid, status, sessions, health, error, replay) - New ui_parser.py: parses raw terminal text into structured interactive UI data with fallback to raw text display - New main.py: CLI with ccweb (serve), ccweb install (hook + commands), ccweb hook (SessionStart handler) - Startup health checks: tmux running, hook installed, config valid - Stale UI guard on interactive prompts (re-captures pane before acting) Decision grid protocol: file-based detection (.ccweb/pending/*.json) with AskUserQuestion blocking to prevent timing issues. Docs: - Full design plan (docs/architecture/design-plan.md) - V2 roadmap (docs/architecture/v2-roadmap.md) https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
- Fix stale UI guard blocking Escape-to-interrupt: Escape key is now always allowed through (it's the interrupt key), guard only applies to interactive UI navigation keys - Add lookup_window_state() that doesn't auto-create phantom entries, use it in _handle_new_message to prevent state pollution and disk I/O churn from create-delete cycles - Remove dead code find_clients_for_session (misleading name, never called) - Implement decision grid file polling in status_poll_loop: watches .ccweb/pending/*.json, validates JSON, sends to frontend, moves to completed/ or failed/ - Add submit_decisions WebSocket handler: formats user selections as text and sends to Claude via tmux, moves grid files to completed/ https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Found by adversarial import chain review: - Move backend/ under ccweb/ccweb/ so hatch builds a proper wheel with the ccweb.backend module hierarchy matching the entry point - Add ccweb/__init__.py as top-level package marker - Fix pyproject.toml: packages = ["ccweb"], inline readme (no missing file) - Fix static files path in server.py (extra parent level after restructure) - Fix CLI dispatch: `ccweb garbage` now shows error instead of silently starting the server https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Found by fresh adversarial data flow review: - Add debug logging when message routing skips a client due to missing window_state (previously silently continued, making dropped messages invisible) - Clear _sent_grid_files on new WebSocket connection so reconnecting clients can see pending decision grid files - Add send_ack response on successful text send so the frontend knows the message was delivered to Claude https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Found by fresh adversarial protocol review: - Add WsPong, WsHistory, WsSendAck dataclasses to ws_protocol.py, replacing hardcoded raw dicts in server.py - Remove dead code: WsReplay and CLIENT_REPLAY_REQUEST (replay not implemented, deferred to v2) - Replace all 5 silent `except Exception: pass` blocks with debug logging so dead WebSocket clients don't silently swallow errors - Fix shutdown: await _status_task cancellation properly, clear module-level state (_clients, _client_bindings, _sent_grid_files) to prevent test pollution Also confirmed by install/run review: all imports resolve, pip install succeeds, entry point works, no remaining ccbot references in code paths. https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
React + TypeScript + Vite + Tailwind frontend with: Components: - App.tsx: main layout with sidebar + content area - SessionSidebar.tsx: session list, create/kill, health warnings - MessageStream.tsx: styled message display with markdown rendering, auto-scroll, copy buttons on code blocks, role-based styling - MessageInput.tsx: multi-line textarea, visible Submit button, Ctrl+Enter shortcut, Escape toolbar button - InteractiveUI.tsx: renders AskUserQuestion as clickable option cards, PermissionPrompt as Allow/Deny buttons, ExitPlanMode as Proceed/Edit, with raw-text fallback + navigation keyboard for unknown UI types - FilterBar.tsx: toggleable chips (All/Chat/No Thinking/Tools) - ExpandableBlock.tsx: collapsible sections for thinking/tool results - StatusBar.tsx: connection status indicator + Claude status text Hooks: - useWebSocket.ts: WebSocket connection with auto-reconnect (exponential backoff), 30s keepalive pings, typed message dispatch - useSession.ts: session state, message history, filter logic Protocol: - protocol.ts: mirrors ws_protocol.py — all server/client message types Config: - Vite proxy: ws: true for WebSocket, /api proxy to FastAPI - Dark terminal theme (Catppuccin-inspired) - TypeScript strict mode, builds clean https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Critical fixes: - Add DecisionGrid component: full modal overlay with option cards, notes column per row, submit/dismiss. Wired into App.tsx + useSession - Add decision_grid handler in useSession.ts (was silently dropped) - Fix stale closure: use activeWindowIdRef instead of closing over state, removing [activeWindowId] dependency from handleServerMessage - Gate history response by window_id to prevent wrong-session display on rapid session switching WebSocket fixes: - Guard reconnect timer against unmount (mountedRef prevents orphaned timers and leaked connections during HMR) - send() now returns boolean so caller can detect dropped messages Cleanup: - Remove duplicate ConnectionStatus type (components/types.ts), import from useWebSocket.ts canonical export instead - StatusBar imports from hook instead of deleted types.ts https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Backend fixes: - Catch UnicodeDecodeError in session_monitor (corrupted byte offset mid-UTF8 char was blocking ALL sessions from being monitored) - Per-client grid tracking replaces global _sent_grid_files (prevents duplicate grids to existing clients on reconnect, allows multi-client) - Interactive UI dedup: don't re-send same UI content every second - resolve_session_for_window uses lookup_window_state (no phantom entries) - Clean up per-client state on disconnect Frontend fixes: - DecisionGrid resets row state when grid prop changes (prevents crash if new grid has fewer items than previous) - Clear messages/status/UI on session switch (prevents stale messages from previous session showing during history load) - Add clearSessionState to useSession return type https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Use key={grid.id} on DecisionGrid to force remount when a new grid
arrives. This eliminates the useEffect-based state reset which had a
one-render-cycle window where rows[i] could be undefined if the new
grid had more items than the old one, causing a TypeError crash.
https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Backend:
- GET /api/sessions/{window_id}/skills: discovers slash commands from
the session's .claude/commands/ directory (name + first-line description)
- GET /api/browse?path=: directory browser for session creation
Frontend:
- CommandPalette.tsx: dropdown with grouped commands (Project Commands
+ Built-in Commands), keyboard navigation (arrow keys + Enter),
fuzzy filtering as you type
- MessageInput now triggers palette on "/" at start of line, or via
"/ Commands" toolbar button
- 15 built-in Claude Code commands defined with descriptions
- Project commands fetched from backend on session switch
- Palette positioned above input with smooth keyboard interaction
https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
…rtcuts
Gaps identified by plan-vs-implementation audit. Fixes for phases 3-7:
Phase 3 gap — FileUpload:
- New FileUpload.tsx: paperclip button with drag-and-drop support
- New POST /api/sessions/{window_id}/upload: saves to docs/inbox/,
sends file path to Claude via tmux
Phase 4 gap — DirectoryPicker:
- New DirectoryPicker.tsx: full modal with file-tree browser using
GET /api/browse, parent navigation, path text input, session name field
- SessionSidebar simplified: "+ New" now opens DirectoryPicker overlay
Phase 3 gap — Command history:
- Ctrl+Up/Down in MessageInput cycles through previously sent messages
Phase 7 gap — Screenshot button:
- New GET /api/sessions/{window_id}/screenshot endpoint (captures pane text)
- Screenshot button in MessageInput toolbar
Phase 3/12 gap — Image rendering:
- WsMessage now carries images field (base64-encoded from tool_result)
- server.py passes image_data through to WebSocket clients
- MessageStream renders inline images below message content
Phase 12 gap — Keyboard shortcuts:
- Ctrl+N: open new session (DirectoryPicker)
https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
- Clamp selectedIndex on Enter to prevent out-of-bounds crash when filter shrinks the command list faster than state updates - Only register global keydown handler when palette is visible, preventing Enter interception after palette closes - Memoize allCommands array in App.tsx to prevent unnecessary CommandPalette re-renders and handler re-registrations https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Backend: - Add python-multipart to pyproject.toml dependencies (required by FastAPI's UploadFile — app crashed on startup without it) Frontend: - Fix skill fetch race condition: add AbortController to cancel stale fetches when rapidly switching sessions - Fix CommandPalette scrollIntoView: use data-cmd-index attribute instead of child index (group headers shifted indices, causing wrong element to scroll into view) - Memoize allCommands with useMemo to prevent unnecessary re-renders https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
- Wrap grid file rename in try/except FileNotFoundError to handle race where submit handler already moved the file - Validate window_id is non-empty in CLIENT_SWITCH_SESSION handler (prevents empty binding causing wasted poll work) - Eliminate double is_interactive_ui + extract_interactive_content call — use single extract_interactive_content, fix indentation https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Wrap grid file rename in CLIENT_SUBMIT_DECISIONS handler with try/except FileNotFoundError, matching the same fix already applied to _check_decision_grids. Prevents WebSocket disconnect when a file is moved between glob enumeration and rename. https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Infrastructure:
- CLAUDE.md at ccweb root with wiki update reminder (persists across
commits — "when you change any feature, update the corresponding doc")
- Backend: GET /api/docs (tree with frontmatter), GET /api/docs/{path}
(raw markdown, frontmatter stripped), GET /api/docs-search?q= (full
text search with snippets)
- _parse_frontmatter() helper (simple YAML key:value parser, no deps)
- Path traversal protection on doc content endpoint
Frontend:
- WikiSidebar.tsx: section-grouped navigation tree, live search with
debounced API calls, active page highlighting
- WikiPage.tsx: markdown rendering with react-markdown + remark-gfm,
internal link resolution (relative paths navigate within wiki),
breadcrumb navigation, styled tables and code blocks
- App.tsx routing: wikiPath state toggles between session view and wiki
view. WikiSidebar replaces SessionSidebar when in wiki mode.
- "Wiki / Help" button at bottom of SessionSidebar
Example docs:
- index.md (home page with section links)
- getting-started/installation.md, quickstart.md
- features/sessions.md, interactive-ui.md
- troubleshooting/common-issues.md
- Existing: architecture/design-plan.md, v2-roadmap.md
All docs use YAML frontmatter (title, description, order) for sidebar
sorting. Internal links use relative paths that work both on GitHub
and in the wiki UI.
https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
- Replace str.startswith() with Path.is_relative_to() for path traversal check (prevents prefix attack on sibling directories) - Replace exists() + read_text() with try/except to handle race condition where file is deleted between check and read - Catches FileNotFoundError, IsADirectoryError, and generic OSError https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Layout: - Desktop (>1024px): sidebar visible alongside content (unchanged) - Tablet (<=1024px): sidebar becomes a slide-out drawer with hamburger button, backdrop overlay to close New hook: - useResponsive.ts: detects tablet breakpoint via matchMedia listener App.tsx changes: - Sidebar wrapped in positioned container with CSS transition - Hamburger button (fixed top-left, 44x44px touch target) - Backdrop closes drawer on tap - Session selection auto-closes drawer on tablet - Main content area gets top padding for hamburger on tablet CSS additions: - @media (max-width: 1024px) enforces 44px min button/touch sizes - font-size: 16px on textarea to prevent iOS auto-zoom - -webkit-fill-available for virtual keyboard push-up https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
On tablet, the hamburger button (z-index 200) was visible and clickable above DecisionGrid and DirectoryPicker modals (z-index 100), allowing the sidebar drawer to open behind a modal. Now the hamburger is hidden when showDirectoryPicker or decisionGrid is active. https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Backend gaps filled:
- REST POST /api/sessions and DELETE /api/sessions/{window_id} endpoints
(plan called for both REST and WebSocket — WebSocket-only before)
- POST /api/sessions/{window_id}/setup-ccweb: creates .ccweb/instructions.md
- Stale grid file cleanup: files >1hr in pending/ moved to failed/
- Auto-create .ccweb/pending/ directory on session creation (both REST
and WebSocket create_session paths)
Frontend gaps filled:
- localStorage persistence: recent directories (DirectoryPicker shows
last 5 used paths), message filter state, last active session
- Swipe gestures: swipe right from left edge opens sidebar drawer,
swipe left closes it (tablet only)
- Ctrl+K keyboard shortcut toggles command palette via
externalPaletteToggle prop
https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
- Wrap addRecentDir localStorage.setItem in try/catch (was the only unguarded localStorage call — would crash handleConfirm if storage unavailable, preventing session creation) - Change Ctrl+K palette toggle from boolean to counter so React StrictMode double-invocation doesn't cancel the toggle. Effect now sets palette to true (idempotent) instead of toggling. https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
- Auto-send switch_session when WebSocket connects/reconnects and there is an active session (restored from localStorage or currently selected). Fixes: empty message stream on page reload, and silent message loss after WebSocket reconnection. - Clear all session state (including DecisionGrid) when killing the active session. Fixes: stale DecisionGrid overlay lingering after its session is killed. https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Strip directory components from uploaded filename using Path.name to prevent ../../../ sequences from writing files outside docs/inbox/. https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Backend: - /api/browse now enforces containment within browse_root (defaults to home dir). Prevents arbitrary filesystem directory enumeration. Parent nav stops at the root boundary. - load_session_map skips stale cleanup when valid_wids is empty, preventing total window_states wipe during transient session_map reads (mid-write race or mismatched tmux session name) Frontend: - switch_session useEffect only fires on status transition TO "connected" (using prevStatusRef), not on every activeWindowId change. Prevents double switch_session (and duplicate history load / potential message loss) on manual session selection. https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
When the server broadcasts an updated sessions list that no longer includes the currently active window_id, clear the active session state (messages, status, interactive UI, decision grid) and remove from localStorage. Prevents the user from interacting with a phantom session that was killed by another client or externally. https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
Browser notifications (useNotifications.ts):
- Request permission on first visit
- Notify when interactive UI appears and tab is not focused
- Click notification to focus the tab
Session rename:
- Double-click session name in sidebar to edit inline
- PUT /api/sessions/{window_id}/rename backend endpoint
- Updates tmux window name + display name + broadcasts to all clients
Export conversation:
- GET /api/sessions/{window_id}/export?fmt=markdown|json|plain endpoint
- Formats messages as Markdown (with thinking in details tags, tools
in code blocks), JSON, or plain text
- Export button in StatusBar triggers download
Context/cost indicator:
- Parse "Context: N%" from Claude Code's status bar chrome via regex
- Added context_pct field to WsStatus protocol message
- StatusBar shows colored context percentage (green <50%, yellow
50-80%, red >80%)
- Clears on session switch
https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
- README.md: project overview, quick start, architecture diagram, links to all documentation, configuration reference, standalone repo instructions - docs/architecture/deferred-items.md: prioritized grid of all 27 deferred features with effort, usefulness, success probability, and reason for deferral. Includes top-5 impact/effort ranking. - docs/architecture/session-history.md: complete history of the build session — origin, all 12 phases, key architectural decisions, adversarial review process (40+ bugs found/fixed), what a new Claude Code session needs to know to pick up where this left off https://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw
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
Introduces CCWeb, a new React-based web interface that replaces Telegram as the primary UI for interacting with Claude Code sessions. The backend is a FastAPI + WebSocket server that reuses transport-agnostic core modules from ccbot (tmux management, session monitoring, terminal parsing). The frontend is a React SPA that renders Claude Code output as styled messages and interactive UI components (permission prompts, decision grids, etc.) instead of terminal navigation.
Both ccbot and ccweb can coexist, connecting to the same tmux sessions simultaneously.
Key Changes
New CCWeb Backend (Python/FastAPI)
ccweb/backend/server.py: FastAPI application with WebSocket endpoint for bidirectional client communicationccweb/backend/config.py: Environment-based configuration (mirrors ccbot's config structure)ccweb/backend/session.py: Session state management (simplified from ccbot, removes Telegram-specific routing)ccweb/backend/ui_parser.py: Converts raw terminal text to structured interactive UI dataccweb/backend/ws_protocol.py: WebSocket message type definitions (JSON schema for client/server communication)ccweb/backend/main.py: CLI entry point withserve,install, andhooksubcommandsCore Modules (Forked from ccbot)
ccweb/backend/core/tmux_manager.py: Async tmux window/pane management via libtmuxccweb/backend/core/terminal_parser.py: Detects interactive UIs in pane output via regex patternsccweb/backend/core/session_monitor.py: Async polling loop that watches JSONL files for new messagesccweb/backend/core/transcript_parser.py: JSONL parser for Claude Code session files (removed Telegram expandable quote sentinels)ccweb/backend/core/monitor_state.py: Persists byte offsets for incremental JSONL readingccweb/backend/core/hook.py: SessionStart hook handler for window-to-session mappingccweb/backend/core/utils.py: Shared utilities (atomic JSON writes, directory resolution)New CCWeb Frontend (React/TypeScript)
ccweb/frontend/src/App.tsx: Main application component with session sidebar, message stream, and inputccweb/frontend/src/protocol.ts: TypeScript types mirroringws_protocol.pyccweb/frontend/src/hooks/useWebSocket.ts: WebSocket connection management with auto-reconnectccweb/frontend/src/hooks/useSession.ts: Session state and message filtering logicccweb/frontend/src/components/: UI components for messages, interactive UIs, decision grids, file upload, directory picker, command palette, etc.ccweb/frontend/src/index.css: Catppuccin-inspired dark theme with CSS variablesvite.config.ts,tsconfig.json,package.jsonDocumentation
ccweb/docs/architecture/design-plan.md: Comprehensive architecture and design rationale (843 lines)ccweb/docs/architecture/v2-roadmap.md: Deferred features and future enhancementsccbot-workshop-setup.md: Complete setup guide for fresh Windows machinesCCBot Enhancements
src/ccbot/process_info.py: New module for process tree inspection, memory usage, and OOM-kill detectionsrc/ccbot/handlers/status_polling.py: Added system-wide memory pressure monitoring with escalating actions (warn → interrupt → kill)src/ccbot/handlers/interactive_ui.py: Improved error handling for interactive UI responsessrc/ccbot/session.py: Refactorediter_thread_bindings()→all_thread_bindings()for snapshot semanticssrc/ccbot/tmux_manager.py: ExportedSHELL_COMMANDSconstant for reusehttps://claude.ai/code/session_01TxXyTqEAaG3ExRtaooMBKw