Claude Code's /voice command incorrectly detects containers running
on WSL2 hosts as unsupported WSL environments. Remove the mic button
from project cards and microphone settings from the settings panel,
but keep useVoice hook and MicrophoneSettings component for re-enabling
once the upstream issue is resolved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Relocates the voice/mic toggle from a floating overlay on the terminal
view to the project command row (alongside Stop, Terminal, Config) so
it no longer blocks access to the terminal window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a dropdown in Settings to choose which audio input device to
use for voice mode. Enumerates devices via the browser's
mediaDevices API and persists the selection in AppSettings.
The useVoice hook passes the selected deviceId to getUserMedia().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enables Claude Code's /voice command inside Docker containers by
capturing microphone audio in the Tauri webview and streaming it
into the container via a FIFO pipe.
Container: fake rec/arecord shims read PCM from a FIFO instead of
a real mic. Audio bridge exec writes PCM from Tauri into the FIFO.
Frontend: getUserMedia() + AudioWorklet captures 16kHz mono PCM
and streams it to the container via invoke("send_audio_data").
UI: "Mic Off/On" toggle button in the terminal view.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Programs inside the container (e.g. Claude Code's "hit c to copy") can
now write to the host system clipboard. A shell script shim installed as
xclip/xsel/pbcopy emits OSC 52 escape sequences, which the xterm.js
frontend intercepts and forwards to navigator.clipboard.writeText().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When using AWS Profile auth (SSO) with Bedrock, expired SSO sessions
caused Claude Code to spin indefinitely. Three root causes fixed:
1. Mount host .aws at /tmp/.host-aws (read-only) and copy to
/home/claude/.aws in entrypoint, mirroring the SSH key pattern.
This gives AWS CLI writable sso/cache and cli/cache directories.
2. For Bedrock Profile projects, wrap the claude command in a bash
script that validates credentials via `aws sts get-caller-identity`
before launch. If SSO session is expired, runs `aws sso login`
with the auth URL visible and clickable in the terminal.
3. Non-SSO profiles with bad creds get a warning but Claude still
starts. Non-Bedrock projects are unaffected.
Note: existing containers need a rebuild to pick up the new mount path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move GitHub release sync into build-app.yml as a final sync-to-github
job that runs after all 3 platform builds complete. This eliminates the
race condition where sync-release.yml triggered before artifacts were
uploaded to Gitea. The old sync-release.yml is changed to manual-only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DefaultHasher is not stable across Rust compiler versions or binary
rebuilds, causing unnecessary container recreations on every app update.
Replace with SHA-256 for deterministic, cross-build-stable fingerprints.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each MCP server can now run as its own Docker container on a dedicated
per-project bridge network, enabling proper isolation and lifecycle
management. SSE transport is removed (deprecated per MCP spec) with
backward-compatible serde alias. Docker socket access is auto-enabled
when stdio+Docker MCP servers are configured.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Model Context Protocol (MCP) server configuration support. Users can
define MCP servers globally (new sidebar tab) and enable them per-project.
Enabled servers are injected into containers as MCP_SERVERS_JSON env var
and merged into ~/.claude.json by the entrypoint.
Backend: McpServer model, McpStore (JSON + atomic writes), 4 CRUD commands,
container injection with fingerprint-based recreation detection.
Frontend: MCP sidebar tab, McpPanel/McpServerCard components, useMcpServers
hook, per-project MCP checkboxes in ProjectCard config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show container start/stop/rebuild progress as a modal popup instead of
inline text that was never visible. Add optimistic status updates so the
status dot turns yellow immediately. Also add a "Jump to Current" button
in the terminal when scrolled away from the bottom.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Emit container-progress events from Rust at key milestones (checking
image, saving state, recreating, starting, stopping) and display them
in ProjectCard instead of the static "starting.../stopping..." text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reconcile stale transient statuses on app startup, add Force Stop button
for transient states, and harden stop_project_container error handling so
Docker failures don't leave projects permanently stuck.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add home volume (triple-c-home-{id}) for /home/claude to persist
.claude.json, .local, and other user-level state across restarts
- Add docker commit before recreation: when container_needs_recreation()
triggers, snapshot the container to preserve system-level changes
(apt/pip/npm installs), then create the new container from that snapshot
- On Reset/removal: delete snapshot image + both volumes for clean slate
- Remove commit from stop_project_container (stop/start preserves the
writable layer naturally; no commit needed)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace OnceLock with Mutex<Option<Docker>> in the Rust backend so
failed Docker connections are retried instead of cached permanently.
Add frontend polling (every 5s) when Docker is initially unavailable,
stopping once detected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CLAUDE_INSTRUCTIONS env var was computed differently during container
creation (with port mapping docs + scheduler instructions appended) vs
the recreation check (bare merge only). This caused
container_needs_recreation() to always return true, triggering a full
recreate on every stop/start cycle.
Extract build_claude_instructions() helper used by both code paths so
the expected value always matches what was set at creation time.
Also add TODO.md noting planned tauri-plugin-updater integration for
seamless in-app updates on all platforms.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The API endpoint /releases/assets/{id} returns JSON metadata, not the
binary file. Use the browser_download_url from the asset object instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two new workflows:
- sync-release.yml: automatically mirrors releases (with assets) to GitHub when published on Gitea
- backfill-releases.yml: manual workflow to bulk-sync all existing Gitea releases to GitHub
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Act runner has Node 18 at /opt/acttoolcache/node/18.20.3/x64/bin/,
not at /usr/local/bin/. Use $(dirname "$(which node)") to find and
remove the actual binary location before installing Node 22.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Act runner has Node 18 at /usr/local/bin/node which takes
precedence over the apt-installed /usr/bin/node. Even after
running nodesource setup and apt-get install, the old Node 18
binary remained in the PATH. Now removes old binaries and uses
hash -r to force path re-lookup. Also removes package-lock.json
before npm install to ensure correct platform-specific bindings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Act runner has Node.js v18 pre-installed, so the check
`command -v node` passes and skips installing v22. Node 18 is
too old for dependencies like vitest, jsdom, and tailwindcss/oxide.
Now checks the major version and upgrades if < 22.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tailwindcss/oxide has platform-specific native bindings. The
package-lock.json was generated on a different platform, so npm ci
installs the wrong native binary. Switching to rm -rf node_modules
+ npm install lets npm resolve the correct platform-specific
optional dependency (e.g., @tailwindcss/oxide-linux-x64-gnu on
Linux, oxide-darwin-arm64 on macOS).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Gitea Act runner's Docker container does not reliably support
$GITHUB_PATH or sourcing ~/.cargo/env across steps. Both mechanisms
failed because the runner spawns a fresh shell for each step.
Adopted the same pattern that already works for the Windows job:
explicitly set PATH at the top of every step that calls cargo or
npx tauri. This is the most portable approach across all runner
environments (Act Docker containers, bare metal macOS, Windows).
Build history shows Linux succeeded through run#46 (JS-based
actions) and failed from run#49 onward (shell-based installs).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GITHUB_PATH only takes effect in subsequent steps, but rustup/rustc/cargo
are called within the same step. Adding `. "$HOME/.cargo/env"` immediately
after install puts cargo/rustup in PATH for the remainder of the step.
Fixed in both Linux and macOS jobs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Gitea Linux runner also lacks Node.js, causing actions/checkout@v4
(a JS action) to fail with "node: executable file not found in PATH".
Same fix as the macOS job: install Node.js via shell before any
JS-based actions run.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Linux app build: cargo was not in PATH for subsequent steps after
shell-based install. Fixed by adding $HOME/.cargo/bin to GITHUB_PATH
(persists across steps) and setting it in the job-level env. Also
removed the now-unnecessary per-step PATH override in the macOS job.
Container build: added QEMU setup and platforms: linux/amd64,linux/arm64
to produce a multi-arch manifest. The Dockerfile already uses
arch-aware commands (dpkg --print-architecture, uname -m) so it
builds natively on both architectures. This fixes the "no matching
manifest for linux/arm64/v8" error on Apple Silicon Macs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace JS-based GitHub Actions (dtolnay/rust-toolchain,
actions/setup-node) in the Linux job with shell commands that
check if Rust and Node.js are already present before installing.
All three jobs (Linux, macOS, Windows) now use the same pattern:
skip installation if the tool is already available on the runner.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The macOS Gitea runner lacks Node.js, causing actions/checkout@v4
(a JS action) to fail with "Cannot find: node in PATH". Fixed by
installing Node.js via Homebrew before checkout and replacing all
JS-based actions (setup-node, rust-toolchain, rust-cache) with
shell equivalents.
Also adds macOS section to BUILDING.md covering Xcode CLI tools,
universal binary targets, and Gatekeeper bypass instructions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Builds a universal binary (aarch64 + x86_64) targeting both Apple
Silicon and Intel Macs. Produces .dmg and .app.tar.gz artifacts,
uploaded to a separate Gitea release tagged with -mac suffix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLAUDE.md provides guidance for Claude Code instances working in this
repo (build commands, architecture overview, key conventions).
HOW-TO-USE.md is a user-facing guide covering prerequisites, setup,
all application features, and troubleshooting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Intercept clipboard paste events containing images in the terminal,
upload them into the Docker container via bollard's tar upload API,
and inject the resulting file path into terminal stdin so Claude Code
can reference the image.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace fragile line-by-line reassembly heuristic with a simpler
approach: flatten the buffer by converting blank lines to spaces
(URL terminators) and stripping remaining newlines (PTY wraps),
then match URLs with a single regex on the flat string.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PTY may deliver a long URL across multiple chunks with enough delay
that the debounce fires between them, emitting a truncated URL. Fixed by:
1. Stripping trailing empty strings from split (artifact of trailing \n)
2. Deferring emission when the URL reaches the end of the buffer — a
confirmation timer (500ms) waits for more data before emitting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PTY hard-wraps long URLs (e.g. OAuth) with \r\n at column width, breaking
xterm.js link detection. This adds a UrlDetector that reassembles wrapped
URLs from the output stream and shows a non-intrusive floating toast with
an "Open" button. Auto-dismisses after 30s, no terminal layout impact.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces a cron-based scheduler that lets Claude set up recurring and
one-time tasks inside containers. Tasks run as separate Claude Code agents
and persist across container recreation via the named volume.
New files:
- container/triple-c-scheduler: CLI for add/remove/enable/disable/list/logs/run/notifications
- container/triple-c-task-runner: cron wrapper with flock, logging, notifications, auto-cleanup
Key changes:
- Dockerfile: add cron package and COPY both scripts
- entrypoint.sh: timezone setup, cron daemon, crontab restore, env saving
- container.rs: init=true for zombie reaping, TZ env, scheduler instructions, timezone recreation check
- image.rs: embed scheduler scripts in build context
- app_settings.rs + types.ts: timezone field
- settings_commands.rs: detect_host_timezone via iana-time-zone crate
- SettingsPanel.tsx: timezone input with auto-detection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add per-project port mapping configuration (host:container port pairs with
TCP/UDP protocol) stored in project config and applied as Docker port
bindings at container creation. Port changes trigger automatic container
recreation via fingerprint detection.
- Create PortMappingsModal UI component following the same pattern as
EnvVarsModal, integrated into ProjectCard config panel.
- Inject port mapping details into CLAUDE_INSTRUCTIONS so Claude inside the
container knows which ports are available for testing services.
- Update default global instructions for new installs to encourage use of
subagents for long-running and parallel tasks.
- Replace app icons with new v2 sun logo design for better visibility at
small sizes (taskbar/dock).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
API key auth only provides short-lived session tokens (8hrs or until
session restart) with no refresh mechanism, unlike OAuth which persists
via .credentials.json. Remove the non-functional API key settings UI
and all supporting code (frontend state, Tauri commands, keyring
storage, container env var injection, and fingerprint-based recreation
checks) to avoid user confusion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both match arms now return None, so Rust needs an explicit type
annotation for the Option<String>.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the three auth modes (Login, API Key, Bedrock) with two
(Anthropic, Bedrock). The Anthropic mode uses OAuth via `claude login`
inside the terminal, which generates and stores its own API key in the
persistent config volume. The separate API Key mode is removed because
Claude Code now requires interactive approval of externally-provided
keys, making the injected ANTHROPIC_API_KEY approach unreliable.
Old projects stored as "login" or "api_key" are automatically migrated
to "anthropic" via serde aliases.
Also fix the Windows taskbar icon showing as a black square by loading
icon.png instead of icon.ico for the runtime window icon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The container was only recreated when the auth mode changed, not when
the API key value itself changed. This meant saving a new key required
a manual container rebuild. Now we store a hash of the API key as a
Docker label and compare it on start, so a key change automatically
recreates the container (preserving the claude config volume).
Also adds a note to the global AWS settings UI that changes require a
container rebuild.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The app crashed on startup because the image-ico Tauri feature was
missing, causing Image::from_bytes to panic when decoding icon.ico.
Added the feature flag and replaced env_logger with fern to log to both
stderr and <data_dir>/triple-c/logs/triple-c.log. A panic hook captures
crash details with backtraces. Store init and icon loading errors are now
logged before failing so future issues are diagnosable from the log file.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Windows taskbar icon by loading icon.ico instead of icon.png (ICO contains
multiple sizes native to Windows taskbar/title bar/alt-tab)
- Add "Container must be stopped to change settings" warning banner in config panel
- Move per-project Environment Variables and Claude Instructions into modal dialogs
for more editing space, with buttons in the config panel to open them
- Move global Claude Instructions into a modal in Settings panel
- Add default global Claude instruction recommending git initialization
- Add global environment variables support (full stack: Rust model, TS types,
container creation with merge logic where project overrides global for same key,
fingerprinting for recreation checks, and Settings UI with modal)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous approach used Builder::default_window_icon() which doesn't
exist in Tauri 2.10. Instead, set the icon via window.set_icon() in the
setup hook, and enable the "image-png" feature flag so Image::from_bytes
can decode the PNG icon at runtime.
Also change bundle identifier from "com.triple-c.app" to
"com.triple-c.desktop" to avoid conflicting with the .app bundle
extension on macOS.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The build step runs `tsc && vite build`, and the test files that import
Node.js built-ins (fs, path) and use __dirname were causing TS2307 and
TS2304 errors during compilation. Excluding test files from tsconfig
keeps the build clean while vitest handles its own TypeScript resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sidebar config panel content was overflowing its container width,
causing project names and directory paths to be clipped. Added min-w-0
and overflow-hidden to flex containers, and restructured folder path
config rows to stack vertically instead of cramming into one line.
The Windows taskbar was showing a black square because no default window
icon was set at runtime. Added default_window_icon() call in the Tauri
builder using the app's icon.png.
Also adds vitest test infrastructure with tests verifying both fixes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tells CLI tools (Claude Code, vim, etc.) that the xterm.js terminal
supports 24-bit RGB color so they use the full palette.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace placeholder icons with the Triple-C branded logo at all required
Tauri sizes. Remove the host_path display from sidebar folder listings to
prevent text overflow. Remove the URL accumulator that injected clickable
login URL text into the terminal — the native WebLinksAddon still handles
URLs when the window is wide enough. Add explicit logging on container
removal confirming named volumes are preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only the active terminal holds a WebGL rendering context now. When
switching tabs the outgoing terminal disposes its WebGL addon (freeing
the GPU context) and the incoming terminal creates a fresh one. This
avoids exhausting the browser's limited WebGL context pool (~8-16) which
caused expensive context loss/restoration lag when switching.
Also skip ResizeObserver callbacks for hidden terminals (zero dimensions)
to avoid unnecessary fit/resize work on inactive tabs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>