Performance and polish improvements across dashboard components:
Transition optimizations (reduces reflow/repaint overhead):
- OptionButton: transition-all → transition-[transform,border-color,
background-color,box-shadow]
- QuestionBlock: Add transition-colors to textarea, transition-all →
transition-[transform,filter] on send button
- SimpleInput: Same pattern as QuestionBlock
- Sidebar: transition-all → transition-colors for project buttons
Animation additions:
- App: Add animate-fade-in-up to loading and error state containers
- MessageBubble: Make fade-in-up animation conditional on non-compact
mode to avoid animation spam in card preview
Using specific transition properties instead of transition-all tells
the browser exactly which properties to watch, avoiding unnecessary
style recalculation on unrelated property changes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removes unnecessary complexity from ChatMessages while adding proper
scroll management to SessionCard:
ChatMessages.js:
- Remove scroll position tracking refs and effects (wasAtBottomRef,
prevMessagesLenRef, containerRef)
- Remove spinner display logic (moved to parent components)
- Simplify to pure message filtering and rendering
- Add display limit (last 20 messages) with offset tracking for keys
SessionCard.js:
- Add chatPaneRef for scroll container
- Add useEffect to scroll to bottom when conversation updates
- Provides natural "follow" behavior for new messages
The refactor moves scroll responsibility to the component that owns
the scroll container, reducing prop drilling and effect complexity.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements animated modal open/close with accessibility support:
- Add closing state with 200ms exit animation before unmount
- Refactor to React hooks-compliant structure (guards after hooks)
- Add CSS keyframes for backdrop fade and panel scale+translate
- Include prefers-reduced-motion media query to disable animations
for users with vestibular sensitivities
- Use handleClose callback wrapper for consistent animation behavior
across Escape key, backdrop click, and close button
The animations provide visual continuity without being distracting,
and gracefully degrade to instant transitions when reduced motion
is preferred.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds comprehensive test coverage for the amc_server package:
- test_context.py: Tests _resolve_zellij_bin preference order (which
first, then fallback to bare name)
- test_control.py: Tests SessionControlMixin including two-step Enter
injection with configurable delay, freeform response handling,
plugin inject with explicit session/pane targeting, and unsafe
fallback behavior
- test_state.py: Tests StateMixin zellij session parsing with the
resolved binary path
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Polish the working indicator animations:
- Use cubic-bezier easing for smoother spinner rotation
- Add will-change hints for GPU acceleration
- Add display: inline-block to bounce-dot spans (required for transform)
- Add prefers-reduced-motion media query to disable animations for
motion-sensitive users (accessibility)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two fixes for tool call display in the dashboard:
1. **filterDisplayMessages includes tool_calls** (MessageBubble.js)
Previously filtered out messages with only tool_calls (no content/thinking).
Now correctly keeps messages that have tool_calls.
2. **Type-safe getToolSummary** (markdown.js)
The heuristic tool summary extractor was calling .slice() without
type checks. If a tool input had a non-string value (e.g., number),
it would throw TypeError. Now uses a helper function to safely
check types before calling string methods.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Visual feedback when an agent is actively processing:
1. **Spinner on status dots** (SessionCard.js, Modal.js)
- Status dot gets a spinning ring animation when session is active/starting
- Uses CSS border trick with transparent borders except top
2. **Working indicator in chat** (ChatMessages.js, Modal.js)
- Shows at bottom of conversation when agent is working
- Bouncing dots animation ("...") next to "Agent is working" text
- Only visible for active/starting statuses
3. **CSS animations** (styles.css)
- spin-ring: 0.8s rotation for the status dot border
- bounce-dot: staggered vertical bounce for the working dots
4. **Status metadata** (status.js)
- Added `spinning: true` flag for active and starting statuses
- Used by components to conditionally render spinner elements
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Codex conversation parser was only handling "message" payload types,
missing tool calls entirely. Codex uses separate response_items:
- function_call: tool invocations with name, arguments, call_id
- reasoning: thinking summaries (encrypted content, visible summary)
- message: user/assistant text (previously the only type handled)
Changes:
- Parse function_call payloads and accumulate as tool_calls array
- Attach tool_calls to the next assistant message, or flush standalone
- Parse reasoning payloads and extract summary text as thinking
- Add _parse_codex_arguments() helper to handle JSON string arguments
This fixes the dashboard not showing Codex tool calls like exec_command,
read_file, etc.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two reliability fixes for response injection:
1. **Zellij binary resolution** (context.py, state.py, control.py)
When AMC is started via macOS launchctl, PATH is minimal and may not
include Homebrew's bin directory. The new `_resolve_zellij_bin()`
function tries `shutil.which("zellij")` first, then falls back to
common installation paths:
- /opt/homebrew/bin/zellij (Apple Silicon Homebrew)
- /usr/local/bin/zellij (Intel Homebrew)
- /usr/bin/zellij
All subprocess calls now use ZELLIJ_BIN instead of hardcoded "zellij".
2. **Two-step Enter injection** (control.py)
Previously, text and Enter were sent together, causing race conditions
where Claude Code would receive only the Enter key (blank submit).
Now uses `_inject_text_then_enter()`:
- Send text (without Enter)
- Wait for configurable delay (default 200ms)
- Send Enter separately
Delay is configurable via AMC_SUBMIT_ENTER_DELAY_MS env var (0-2000ms).
3. **Documentation updates** (README.md)
- Update file table: dashboard-preact.html → dashboard/
- Clarify plugin is required (not optional) for pane-targeted injection
- Document AMC_ALLOW_UNSAFE_WRITE_CHARS_FALLBACK env var
- Note about Zellij resolution for launchctl compatibility
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace the monolithic single-file dashboards (dashboard.html,
dashboard-preact.html) with a proper modular directory structure:
dashboard/
index.html - Entry point, loads main.js
main.js - App bootstrap, mounts <App> to #root
styles.css - Global styles (dark theme, typography)
components/
App.js - Root component, state management, polling
Header.js - Top bar with refresh/timing info
Sidebar.js - Project tree navigation
SessionCard.js - Individual session card with status/actions
SessionGroup.js - Group sessions by project path
Modal.js - Full conversation viewer overlay
ChatMessages.js - Message list with role styling
MessageBubble.js - Individual message with markdown
QuestionBlock.js - User question input with quick options
EmptyState.js - "No sessions" placeholder
OptionButton.js - Quick response button component
SimpleInput.js - Text input with send button
lib/
preact.js - Preact + htm ESM bundle (CDN shim)
markdown.js - Lightweight markdown-to-HTML renderer
utils/
api.js - fetch wrappers for /api/* endpoints
formatting.js - Time formatting, truncation helpers
status.js - Session status logic, action availability
This structure enables:
- Browser-native ES modules (no build step required)
- Component reuse and isolation
- Easier styling and theming
- IDE support for component navigation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enhance the launcher script with robust process management:
Process Lifecycle:
- Use macOS launchctl to spawn server process, avoiding parent-shell
cleanup kills that terminate AMC when Claude Code's shell exits
- Fall back to setsid+nohup on non-macOS systems
- Graceful shutdown with 5-second wait loop before force-kill
Health Checking:
- Replace simple PID file check with actual HTTP health check
- curl /api/state to verify server is truly responsive, not just alive
- Report "healthy" vs "running but not responding" states
PID Recovery:
- Recover orphan PID file from port listener (lsof -tiTCP:7400)
- Validate listener is actually amc-server (not another process on 7400)
- Auto-refresh PID file when process exists but file is stale
Logging:
- New LOG_FILE at ~/.local/share/amc/server.log
- show_recent_logs() helper for diagnostics
- Consistent log routing through launchctl submit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Split the 860+ line bin/amc-server into a modular Python package:
amc_server/
__init__.py - Package marker
context.py - Shared constants (DATA_DIR, PORT, CLAUDE_PROJECTS_DIR, etc.)
handler.py - AMCHandler class using mixin composition
logging_utils.py - Structured logging setup with signal handlers
server.py - Main entry point (ThreadingHTTPServer)
mixins/
__init__.py - Mixin package marker
control.py - Session control (dismiss, respond via Zellij)
conversation.py - Conversation history parsing (Claude JSONL format)
discovery.py - Session discovery (Codex pane inspection, Zellij cache)
http.py - HTTP response helpers (CORS, JSON, static files)
parsing.py - Session state parsing and aggregation
state.py - Session state endpoint logic
The monolithic bin/amc-server becomes a thin launcher that just imports
and calls main(). This separation enables:
- Easier testing of individual components
- Better IDE support (proper Python package structure)
- Cleaner separation of concerns (discovery vs parsing vs control)
- ThreadingHTTPServer instead of single-threaded (handles concurrent requests)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Markdown & syntax highlighting:
- Replace homegrown backtick parser with marked + DOMPurify + highlight.js
- Register 10 language grammars (JS, TS, Bash, JSON, Python, Rust, CSS,
HTML/XML, SQL, YAML) for fenced code block highlighting
- Sanitize rendered HTML via DOMPurify to prevent XSS from message content
Project-based session grouping:
- Replace four status-based groups (attention/active/starting/done) with
project-directory grouping via groupSessionsByProject()
- Groups sort by most urgent status first, then most recent activity
- Sessions within each group sort by urgency then recency
- Group headers show project name, agent count, and per-status chips
- Project directory path shown below group header
Visual refinements:
- Darken color palette across the board (bg, surface, scrollbar, glass
panels) for deeper contrast
- Add agent-header-codex and agent-header-claude CSS classes for
color-coded card and modal headers (teal vs purple tints)
- Increase agent badge visibility with higher opacity borders/backgrounds
- Cards now fixed-width 600px with taller height (850px) in flex-wrap
layout instead of responsive grid
- Input focus border color now matches the session's status color
instead of hardcoded active/attention colors
- Send button background also matches status color for visual coherence
- Lock body scroll when session modal is open
- Add agent badge to modal header for consistent identification
Replace the zellij dump-layout approach with direct process inspection
for matching Codex sessions to Zellij panes. The old method couldn't
extract pane IDs from layout output, leaving them empty. The new
approach uses a three-step pipeline:
1. pgrep -x codex to find running Codex PIDs
2. ps eww to extract ZELLIJ_PANE_ID and ZELLIJ_SESSION_NAME from each
process's inherited environment variables
3. lsof -a -p <pids> -d cwd to batch-resolve working directories
Session-to-pane matching then uses a two-tier strategy:
- Primary: lsof -t <session_file> to find which PID has the JSONL open
- Fallback: normalized CWD comparison
Also adds:
- _codex_pane_cache with 5-second TTL to avoid running pgrep/ps/lsof
on every dashboard poll cycle
- _dismissed_codex_ids set to track Codex sessions the user has
dismissed, preventing re-discovery on subsequent polls
- Clearer error message when session lacks pane info for input routing
Document AMC (Agent Mission Control) for new users and contributors:
- Overview: real-time dashboard for Claude Code session monitoring
- Features: session grouping, conversation history, response injection,
question detection (AskUserQuestion and prose), session lifecycle
- Installation: symlink setup and PATH configuration
- Hook Setup: complete settings.json configuration for all required
Claude Code hooks (SessionStart, UserPromptSubmit, Stop, SessionEnd,
PreToolUse/PostToolUse for AskUserQuestion)
- Architecture: ASCII diagram showing data flow from Claude Code hooks
through amc-hook to disk storage to amc-server to dashboard
- Components: descriptions of amc launcher, amc-server, amc-hook,
and dashboard-preact.html
- Data Storage: runtime data paths in ~/.local/share/amc/
- API Reference: all 6 endpoints with methods and descriptions
- Response Injection: request body format and two-phase freeform flow
- Session Statuses: starting, active, needs_attention, done
- Cleanup Behavior: orphan detection, stale session removal, event log gc
- Requirements: Python 3.8+, Zellij, Claude Code hooks
- Optional Zellij Plugin: for focus-free response injection
bin/amc:
- Add cross-platform browser opening: try 'open' (macOS) first,
fall back to 'xdg-open' (Linux) for desktop environments
- Wrap browser opening in conditional to avoid errors on headless systems
bin/amc-hook:
- Fix edge case in question extraction where tool_input.get("questions")
could return None instead of empty list, causing TypeError
Extend AMC to monitor Codex sessions alongside Claude Code:
Codex Integration:
- Discover active Codex sessions from ~/.codex/sessions/*.jsonl
- Parse Codex JSONL format (response_item/message payloads) for
conversation history, filtering out developer role injections
- Extract session metadata (cwd, timestamp) from session_meta records
- Match Codex sessions to Zellij panes via cwd for response injection
- Add ?agent=codex query param to /api/conversation endpoint
Session Lifecycle Improvements:
- Cache Zellij session list for 5 seconds to reduce subprocess calls
- Proactive liveness check: auto-delete orphan "starting" sessions
when their Zellij session no longer exists
- Clean up stale "starting" sessions after 1 hour (likely orphaned)
- Preserve existing event log cleanup (24h for orphan logs)
Code Quality:
- Refactor _serve_conversation into _parse_claude_conversation and
_parse_codex_conversation for cleaner separation
- Add _discover_active_codex_sessions for session file generation
- Add _get_codex_zellij_panes to match sessions to panes
- Use JSON error responses consistently via _json_error helper
Replace vanilla JS dashboard with a modern Preact-based implementation:
- Component architecture: Header, SessionCard, SessionGroup, Modal,
ChatMessages, QuestionBlock, SimpleInput, OptionButton, EmptyState
- Status-grouped display: sessions organized by needs_attention, active,
starting, and done with color-coded visual indicators
- Real-time conversation view: fetch and display chat history from
Claude Code JSONL logs with auto-scroll on new messages
- Inline response input: send messages directly from cards without
opening modal, supports both structured options and freeform text
- Modal detail view: full conversation with markdown rendering,
timestamps, and keyboard navigation (Enter to send, Escape to close)
- Smart polling: 3-second state refresh with silent conversation
updates for active sessions to minimize UI flicker
- Flexoki color scheme: custom dark theme with attention (amber),
active (green), starting (blue), and done (purple) status colors
- Berkeley Mono font stack with graceful fallbacks
- Tailwind CSS via CDN with custom color configuration
- Responsive card layout with fixed 600px height and overflow handling
The dashboard provides a unified view for monitoring multiple Claude
Code sessions, detecting when agents need human input, and responding
directly via Zellij pane injection.