feat(docker): add multi-stage Dockerfile and docker-compose for Windows#24
feat(docker): add multi-stage Dockerfile and docker-compose for Windows#24hoklims wants to merge 1857 commits into
Conversation
Scans all JSONL files under ~/.claude/projects/ at startup, building a team_name → [TeamJSONLRef] index. Uses memmem SIMD pre-filter to skip files without "teamName" in microseconds. One session may contain multiple teams (nvda-demo + python-vs-go-demo pattern verified in real data). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SessionHit - Add `engines: Vec<String>` to `SessionHit` (e.g. ["tantivy"], ["grep"]) - Remove response-level `search_engine: Option<String>` from `SearchResponse` - Update all SessionHit constructors in query.rs (tantivy) and unified.rs (grep) - Update generated TS types: SearchResponse.ts drops searchEngine, SessionHit.ts gains engines - Update SearchResults.tsx to derive grep indicator from session.engines per-session - Update CommandPalette.search.test.tsx assertions to match new per-session engines shape - All 8 unified:: unit tests pass Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SessionHit - Add `engines: Vec<String>` to `SessionHit` (e.g. ["tantivy"], ["grep"]) - Remove response-level `search_engine: Option<String>` from `SearchResponse` - Update all SessionHit constructors in query.rs (tantivy) and unified.rs (grep) - Update generated TS types: SearchResponse.ts drops searchEngine, SessionHit.ts gains engines - Update SearchResults.tsx to derive grep indicator from session.engines per-session - Update CommandPalette.search.test.tsx assertions to match new per-session engines shape - All 8 unified:: unit tests pass Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reconstructs TeamDetail from JSONL by scanning for: - TeamCreate tool_use → team name + description - Agent/Task spawns with team_name match → member list - First timestamp → created_at Uses SIMD memmem pre-filter on team name. Agent spawns without team_name (regular sub-agents) are correctly excluded. Colors are deterministic (hash of member name → palette index). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reconstructs TeamDetail from JSONL by scanning for: - TeamCreate tool_use → team name + description - Agent/Task spawns with team_name match → member list - First timestamp → created_at Uses SIMD memmem pre-filter on team name. Agent spawns without team_name (regular sub-agents) are correctly excluded. Colors are deterministic (hash of member name → palette index). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scans JSONL for SendMessage tool_use calls (team-lead → member outbound messages). Uses dual memmem pre-filter (teamName + SendMessage) for efficiency. Historical messages are always marked read: true. Messages sorted chronologically by ISO timestamp string comparison. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scans JSONL for SendMessage tool_use calls (team-lead → member outbound messages). Uses dual memmem pre-filter (teamName + SendMessage) for efficiency. Historical messages are always marked read: true. Messages sorted chronologically by ISO timestamp string comparison. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds SearchPrefilter struct and Database::search_prefilter_session_ids() to narrow the session set via SQLite before grep/Tantivy run. Uses the polymorphic project filter pattern (project_id OR git_root) and maps filter fields to actual schema columns (git_branch, primary_model). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds SearchPrefilter struct and Database::search_prefilter_session_ids() to narrow the session set via SQLite before grep/Tantivy run. Uses the polymorphic project filter pattern (project_id OR git_root) and maps filter fields to actual schema columns (git_branch, primary_model). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Tantivy) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Tantivy) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TeamsStore now holds a jsonl_index (TeamJSONLIndex) built at startup. All three lookup methods fall back to JSONL reconstruction when a team is missing from the filesystem: - get(): filesystem → reconstruct_team_from_jsonl() - inbox(): filesystem → reconstruct_inbox_from_jsonl() - summaries(): includes JSONL-only teams in the sorted list Filesystem always wins when a team exists on both (verified by test_get_prefers_filesystem_over_jsonl). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TeamsStore now holds a jsonl_index (TeamJSONLIndex) built at startup. All three lookup methods fall back to JSONL reconstruction when a team is missing from the filesystem: - get(): filesystem → reconstruct_team_from_jsonl() - inbox(): filesystem → reconstruct_inbox_from_jsonl() - summaries(): includes JSONL-only teams in the sorted list Filesystem always wins when a team exists on both (verified by test_get_prefers_filesystem_over_jsonl). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests verify co-primary search with both grep and Tantivy: - Both engines find the same session (grep primary, Tantivy supplements) - CJK queries fallback to grep when Tantivy insufficient - Results properly sorted by recency (modified_at DESC) All 3 tests pass in 0.12s. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Tests verify co-primary search with both grep and Tantivy: - Both engines find the same session (grep primary, Tantivy supplements) - CJK queries fallback to grep when Tantivy insufficient - Results properly sorted by recency (modified_at DESC) All 3 tests pass in 0.12s. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
… execution - Add structured filter params to SearchQuery: project, branch, model, after, before - Add parse_iso_date helper using chrono (already a workspace dep) - Build SearchPrefilter from params (with scope backward compat for project: prefix) - Run SQLite pre-filter when any structured filter is set, skip otherwise - Collect JSONL files narrowed by session IDs from pre-filter - Get search index as Option (no 503 — grep is primary, Tantivy supplements) - Run unified_search via spawn_blocking; set elapsed_ms in handler, not engine - Handler returns SearchResponse directly (result.response) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… execution - Add structured filter params to SearchQuery: project, branch, model, after, before - Add parse_iso_date helper using chrono (already a workspace dep) - Build SearchPrefilter from params (with scope backward compat for project: prefix) - Run SQLite pre-filter when any structured filter is set, skip otherwise - Collect JSONL files narrowed by session IDs from pre-filter - Get search index as Option (no 503 — grep is primary, Tantivy supplements) - Run unified_search via spawn_blocking; set elapsed_ms in handler, not engine - Handler returns SearchResponse directly (result.response) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
summaries() was reading each JSONL file twice per JSONL-only team — once for reconstruct_team_from_jsonl, once for reconstruct_inbox_from_jsonl. New reconstruct_team_and_inbox_from_jsonl() combines both in one pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
summaries() was reading each JSONL file twice per JSONL-only team — once for reconstruct_team_from_jsonl, once for reconstruct_inbox_from_jsonl. New reconstruct_team_and_inbox_from_jsonl() combines both in one pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Real Claude Code JSONL omits teamName on the TeamCreate assistant message itself — the team name only appears inside input.team_name. The previous code skipped these lines via the `line_team != team_name` guard, so JSONL-only teams (nvda-demo, python-vs-go, etc.) never appeared in /api/teams despite being indexed. Fix: allow lines through if they contain a TeamCreate block (detected via SIMD memmem pre-filter), and let the inner input.team_name check confirm the match. Applies to both reconstruct_team_from_jsonl and reconstruct_team_and_inbox_from_jsonl. E2E verified: /api/teams now returns 17 teams (was 10), including all 7 JSONL-only teams that had their filesystem dirs deleted. Regression tests: test_reconstruct_team_from_jsonl_without_toplevel_teamname and test_reconstruct_combined_without_toplevel_teamname use real-world message shapes (no teamName on TeamCreate line) to prevent recurrence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Real Claude Code JSONL omits teamName on the TeamCreate assistant message itself — the team name only appears inside input.team_name. The previous code skipped these lines via the `line_team != team_name` guard, so JSONL-only teams (nvda-demo, python-vs-go, etc.) never appeared in /api/teams despite being indexed. Fix: allow lines through if they contain a TeamCreate block (detected via SIMD memmem pre-filter), and let the inner input.team_name check confirm the match. Applies to both reconstruct_team_from_jsonl and reconstruct_team_and_inbox_from_jsonl. E2E verified: /api/teams now returns 17 teams (was 10), including all 7 JSONL-only teams that had their filesystem dirs deleted. Regression tests: test_reconstruct_team_from_jsonl_without_toplevel_teamname and test_reconstruct_combined_without_toplevel_teamname use real-world message shapes (no teamName on TeamCreate line) to prevent recurrence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…esults Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…esults Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All create tests now pass initialMessage (required for SDK to init) - Multi-turn WS test replaced with HTTP /send endpoint test - Fix biome noNonNullAssertion warnings (use optional chaining) - 12/12 E2E tests pass against real Agent SDK with haiku Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sessions in waiting_permission state need user attention (permission card). Without auto-connect, user navigates to the session but sees nothing — they'd have to type a message first, which is nonsensical when the session is waiting for THEIR input on a tool approval. Matches VS Code / ChatGPT / Cursor pattern: auto-surface states that need user attention. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Silent failure hunter found: fire-and-forget sendMessage with .catch() swallows errors. If sendMessage rejects (auth error, rate limit), waitForSessionInit hangs for 15s then times out — the real error is lost. Fix: Race both promises with Promise.all. If sendMessage rejects, the create route fails fast with the actual error instead of a confusing 15s timeout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two fixes from agent review team: 1. waitForSessionInit now guards against session_init firing with empty sessionId (if sdkSession.sessionId throws, the silent catch in updateSessionState leaves sessionId='' — handler keeps waiting instead of resolving with empty ID) 2. Create route cleans up orphaned sessions on failure — if waitForSessionInit rejects (timeout, fatal error, send failure), closeSession removes the ghost session from the registry instead of leaving it to accumulate Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded model lists with the SDK's authoritative model list. The sidecar fetches models via V1 query().initializationResult() on startup (refreshed hourly), caches them in memory, and serves via GET /supported-models. The Rust backend proxies this to the frontend. ModelSelector priority chain: SDK → /api/models → hardcoded fallback. Includes timeout protection (30s), interrupt()-based cleanup, retry:false on the frontend hook, and all findings from code review addressed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the single blocking `claude plugin marketplace update` call with parallel per-marketplace CLI calls, fixing the 30s timeout and adding real-time per-row status in the Marketplaces dialog. Backend: - New MarketplaceRefreshTracker with Mutex<HashMap> state, 5-min staleness guard, 30s TTL eviction (10 unit tests) - POST /refresh-all spawns parallel tokio tasks (60s timeout each), orchestrator holds MARKETPLACE_LOCK for the batch - GET /refresh-status returns per-marketplace status snapshot - Add timeout_secs param to run_claude_plugin_in (existing callers unchanged at 30s) - Make get_marketplace_lock pub(crate), remove dead bulk update path Frontend: - useMarketplaceRefresh hook with 1s polling, completion toast, cache invalidation - Per-row status: queued (pulse), running (ping), failed (retry button) - Fix dialog centering to Tailwind classes per CLAUDE.md Empirically validated: 7 parallel updates complete in ~6s vs ~28s serial. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # crates/server/src/lib.rs # crates/server/src/routes/jobs.rs # crates/server/src/routes/terminal.rs # crates/server/src/state.rs
…calls Race condition: when multiple frontend requests arrive simultaneously during sidecar startup, the first caller spawns the process and polls the health endpoint, but concurrent callers see "child alive" and return the socket path immediately — before the sidecar has started listening. This causes "No such file or directory" connection errors. Fix: check socket file existence before returning "ready". If child is alive but socket doesn't exist yet, wait for health check (same loop as the spawner) instead of returning immediately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Redesign ModelSelector dropdown to show model name, description (from SDK), and context window size — matching the reference design. Wider dropdown (w-72), two-line layout per item, check mark for active selection, "Select a model" header. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The formatContextWindow() helper returned '200K' for all models, but Opus 4.6 is actually 1M context. Remove fake data rather than show wrong data. Context window display needs real data piped through from LiteLLM (max_input_tokens field) — tracked as follow-up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ring Tailwind v4 `-translate-x-1/2 -translate-y-1/2` generates the CSS `translate` property instead of `transform`, which breaks centering when composed with Radix internals or animations. All 8 dialogs now use `DialogContent` / `AlertDialogContent` wrappers that bake in inline `transform: translate(-50%, -50%)` — immune to TW v4 composability issues. Zero raw Dialog.Content / AlertDialog.Content remains outside the wrapper. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace generic gray rectangles with structured skeletons that match real component layouts (gauge cards, session rows, process rows). Add inline skeleton placeholders for process-tree-dependent data (chevrons, CLI/IDE badges, proc counts, child proc hints, orphaned processes) so the UI shows the final layout shape while the process tree is still loading. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ata source Memory gauge showed 68.7 GB instead of 64 GB because formatBytes used decimal (÷1e9) instead of binary (÷1024³) units. Active Sessions gauge was always red (value/max=100%) and showed a different count than the Claude Sessions panel due to two independent SSE streams. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use node:22-slim as runtime base instead of debian:bookworm-slim to match sidecar's node20+ target and avoid Node version mismatch - Pin @anthropic-ai/claude-code@2.1.76 for reproducible builds - Remove redundant second sed for LTO override (COPY crates/ doesn't overwrite root Cargo.toml) - Make docker-compose.yml cross-platform: use CLAUDE_HOME_DIR env var with $HOME fallback instead of Windows-only $USERPROFILE Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ck ordering - Auto-resume: dormant sessions (no controlId) now call /api/control/sessions/resume before opening WS, enabling send-from-any-state without manual reconnect - Fix optimistic block timeline: user messages appear before assistant response, not appended after stream blocks - Status lifecycle: 'sent' now clears to undefined (block stays visible) instead of being removed; only 'optimistic'/'sending' blocks timeout to 'failed' - deriveEffectiveSend/deriveCanResumeLazy updated to treat sessionId alone as resumable (not just controlId) - session_closed clears controlId + resets lastSeq for clean next-connect - Feature flag: FEATURES.chat=true in dev only, false in prod builds Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes Clippy unnecessary_sort_by warnings across core, search, and db crates by using sort_by_key with std::cmp::Reverse. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
promptId (UUID) appears on type:"user" JSONL records linking user messages to saved prompt templates. 16,411 occurrences in real data. Field is real but not consumed by the parser pipeline — added to intentionally_ignored so the evidence audit passes without extracting unused data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add platform abstraction layer (platform.rs) that replaces Unix-specific code with cross-platform equivalents: - Process management: libc::kill → taskkill on Windows, libc on Unix - IPC: Unix domain sockets → TCP on Windows for sidecar communication - Sidecar (Node.js): detect TCP vs Unix socket mode via SIDECAR_SOCKET - Make libc dependency Unix-only in Cargo.toml - Fix all sort_by → sort_by_key Clippy warnings across server crate - Fix unused variable warnings in time_range.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add crates/db/src/queries/presets.rs with full CRUD operations for the presets and preset_state tables: list, get, create, update metadata, delete, and state management (set active, clear, set active only). Includes 6 passing tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge main's socket readiness check and model cache refresh into Docker/Windows TCP support branch. Added is_addr_ready() helper to handle both Unix socket and TCP address modes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks! Would it be possible to decouple Docker and Windows support for now? The app is currently tightly bound to macOS, and I'm concerned Windows might introduce stability issues we're not equipped to handle at this stage. |
Decouple Docker support from Windows support per maintainer request. Remove platform.rs, revert sidecar to Unix socket only, restore libc::kill/SIGTERM calls, remove TCP dual-mode from sidecar TS. Docker-only changes (Dockerfile, docker-compose, env vars) are preserved. Windows support will be submitted as a separate PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Strip all Windows runtime support (taskkill, sysinfo, TCP sidecar mode) from platform.rs. Keep only Unix implementations with cfg(not(unix)) compile stubs so the crate builds on all platforms but only runs on macOS/Linux (enforced by the platform gate in main.rs). Docker-only changes (Dockerfile, docker-compose, env vars) are preserved. Windows support will be submitted as a separate PR. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Makes sense, you're right that bundling Windows into this was too much at once. I've pushed a cleanup — all the Windows runtime stuff is gone now (the taskkill/sysinfo abstractions, TCP sidecar mode, the whole dual-mode plumbing). What's left is purely Docker: the Dockerfile, compose file, and the two env vars needed to run in a Linux container ( Also addressed the Copilot remarks along the way: Happy to adjust if anything else stands out. |
|
@hoklims Great thx, will do a review and merge this week. Thx for the contribution. |
1bad781 to
1f9dce5
Compare
Summary
Dockerfile(Bun frontend + Node sidecar + Rust backend + Debian slim runtime)docker-compose.ymlwith Windows-compatible volume mounts (%USERPROFILE%/.claude).dockerignorefor fast build contextMotivation
claude-view currently only runs on macOS. This adds Docker support so users on Windows (and Linux) can run the full dashboard via Docker Desktop with a single
docker compose up.Verified working on Windows 11 with Docker Desktop — indexes 463 sessions, live monitor, analytics, and Claude CLI detection all functional.
Architecture
docker-compose mounts:
~/.claude→/root/.claude(session data, r/w for hook registration)claude-view-data→/data(DB, search index persistence)Dependencies
Depends on #23 (
CLAUDE_VIEW_SKIP_PLATFORM_CHECKandCLAUDE_VIEW_BIND_ADDRenv vars).Test plan
docker compose buildsucceeds (4-stage parallel build)docker compose upstarts server on port 47892/api/healthreturns{"status":"ok"}~/.claude/projects/🤖 Generated with Claude Code