Review caught that `flags=r` disables rewriting of both symlink AND
hardlink target names. Leaving symlink targets alone is intended, but a
hardlink's stored target is an archive-internal reference to another
member's name — when member names become `workspace/...` but the
hardlink target stays `./hard_link`, extraction fails hard:
tar: workspace/file.txt: Cannot hard link to './hard_link':
No such file or directory
`flags=rh` rewrites regular member names and hardlink target names
together (keeping the pair consistent) while still leaving symlink
targets untouched. Verified in-container: extract exit 0, symlink target
preserved, hardlink pair shares one inode, nesting under workspace/ intact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The backup archive placed the workspace at the archive root (`./...`)
while the sanitized home config sat under `home-claude/`. On extraction
the workspace files scattered loose into the extraction directory and
only `home-claude/` showed up as a distinct folder, so the backup read
as "config only, workspace missing" — and some archive viewers didn't
surface the root-level entries at all.
Add `--transform='flags=r;s,^\./,workspace/,'` so the workspace nests
under `workspace/`, parallel to `home-claude/`. `flags=r` scopes the
rewrite to member names only, leaving symlink targets (relative and
absolute) intact. Excludes still match the pre-transform names.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- F2: upload_host_file_to_container now reads the dropped file into a Vec
inside the blocking task and sizes the tar entry from those exact bytes,
rather than stat-then-stream where a file changing size between the
stat and the read could desync the tar header and silently corrupt the
archive. Still runs off the async worker; memory stays bounded by the
256 MiB drop cap.
- F4: the "Backup saved" confirmation now auto-clears after 8s (guarded
against clobbering a newer status message) instead of lingering in the
project card's status line indefinitely.
F1 (claimed AWS CLI regression from empty-env neutralization) was a false
positive: verified against aws-cli 2.35 that an empty AWS_ACCESS_KEY_ID is
treated as absent and botocore falls through to ~/.aws/credentials (the
call reached AWS and returned InvalidClientTokenId for the file's key, not
PartialCredentialsError). No change needed.
cargo check / tsc / vitest all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- L-e: route terminal file drops purely by a bounds hit-test instead of
the `active` flag. Inactive panes are display:none (zero-size rect) so
they never match; a zero-size guard makes that explicit. Correct for
the current tabbed layout and future-proof for split panes, where a
drop on a visible-but-unfocused pane previously matched no handler.
- L-f: stream the dropped file straight into the upload tar inside a
blocking task (new exec::upload_host_file_to_container) instead of
reading the whole file into a Vec and then re-packing it. Peak memory
drops from ~2x to ~1x the file size, and the synchronous file IO no
longer runs on the async worker.
cargo check / tsc / vitest all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- M1 (race): don't mount the host AWS dir for static-credential Bedrock.
sync_bedrock_credentials() is the sole writer of ~/.aws/credentials in
that mode, and mounting /tmp/.host-aws let the entrypoint's
`rm -rf ~/.aws; cp -a` race that write at startup (only when a global
aws_config_path was also set). Static keys + AWS_REGION env are
self-sufficient and don't need the host config, so skipping the mount
removes the dual-writer entirely.
- L-a: exit codes are now read via wait_for_exec_exit(), which polls
inspect_exec until the exec reports finished, so a non-zero tar/cred
exit isn't missed by reading exit_code too early. The backup only fails
on a definitively non-zero code (falls back to the empty-output check
if undeterminable).
- L-b: fixed two comments that referenced the old
write_bedrock_static_credentials name (now sync_bedrock_credentials).
- L-c: entrypoint only rewrites ~/.claude.json when awsAuthRefresh is
actually present, avoiding a needless jq reformat on every non-SSO
start.
- L-d: backup script traps EXIT to remove its mktemp staging dir even
when tar fails, so failed backups don't accumulate temp dirs (with the
sanitized config copy) in the container.
L-e (drop routing) is a non-issue: the layout is tabbed, so only one
terminal pane is ever visible; the active-guard routing is correct.
Verified the race fix, trap cleanup, grep guard, and exit-code polling.
cargo check / tsc / vitest all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- L4: sync_bedrock_credentials (renamed from write_bedrock_static_
credentials) now also clears a stale ~/.aws/credentials when the
project no longer uses static-credential Bedrock, so static keys don't
linger unused in the persistent home volume after switching backends.
Skipped when /tmp/.host-aws is mounted (host-managed ~/.aws). HOME is
also set explicitly on the exec env for robustness.
- M1: the Backup button now has a tooltip and the success toast notes
that the archive includes MCP/config which may contain MCP-embedded
API keys (OAuth tokens are excluded) — keep it private.
- L2: backup now uses async file IO (tokio::fs::File + AsyncWriteExt,
tokio::fs::remove_file) instead of blocking std::fs between awaits;
dropped-file reads use tokio::fs::metadata/read.
- L5: upload_host_file_to_terminal explicitly `mkdir -p`s
/tmp/triple-c-drops instead of relying on Docker's tar extractor to
create the parent dir.
Verified L4 cleanup guard, L5 mkdir, async IO, and exit-code paths
against real containers. cargo check / tsc / vitest all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes from the code review of this branch:
- Backup requires a running container (it runs via `docker exec`, which
can't run on a stopped one). Removed the misleading "Backup" button
from the stopped-project actions, added an explicit running check with
a clear error, and corrected the doc comment. (H1)
- jq sanitization fallback no longer leaks secrets: if ~/.claude.json
can't be parsed, the backup substitutes an empty object and warns to
stderr instead of copying the raw file (which held primaryApiKey /
oauthAccount). Verified the raw key never reaches the archive. (H2)
- Dropped-file paths typed into the terminal are now always single-quoted
(with '\'' escaping), not only when they contain whitespace — a name
like `foo$(whoami).txt` was previously sent raw into the shell. (M2)
- write_bedrock_static_credentials checks the exec exit code via the new
exec_oneshot_env_status and fails loudly on a write/chmod error instead
of silently reporting success. exec_oneshot keeps its
ignore-exit-code behavior so list_container_files is unaffected. (M4)
- Backup removes a partial/truncated archive on any stream error and
treats a non-zero tar exit code as failure (a truncated gzip was
previously reported as success). (L1)
- Dropped files are capped at 256 MiB to avoid ballooning host RAM
(the file is read fully into memory then re-tarred). (M3)
- Stopped excluding .git/objects from the backup so git history,
including unpushed commits, is preserved faithfully. (L3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop files onto a terminal pane and they're copied into the container and
their in-container paths typed into the prompt, so Claude Code can read
them for reference — mirroring the existing image-paste flow.
Backend: upload_host_file_to_terminal reads the dropped host file and
writes it under /tmp/triple-c-drops/<name> in the session's container,
returning that path. Rejects directories and unreadable paths.
Frontend: TerminalView subscribes to Tauri's webview onDragDropEvent
(OS file drops are intercepted at the webview level, so HTML5 ondrop
wouldn't expose paths). The window-wide event is guarded by the pane's
`active` flag plus a bounds hit-test so a drop only affects the terminal
it landed on; multiple files are uploaded and their paths inserted
space-separated (quoted when they contain spaces).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends download_container_backup to also capture the container's home
config so MCP servers, settings, and skills set up directly via Claude
Code (stored in ~/.claude.json / ~/.claude, on the home/config volumes
that a Reset wipes) survive a backup/restore cycle.
Secrets are stripped per the "exclude secrets" choice: ~/.claude.json is
filtered through jq to drop primaryApiKey/oauthAccount/customApiKeyResponses
(mcpServers and settings are kept), and ~/.claude/.credentials.json (the
OAuth tokens) is omitted. Staged config is archived under home-claude/ in
the tarball. Verified on the Ubuntu/jq container base.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a manual backup button on each project card (next to Start/Reset
when stopped, and next to Files when running) that saves a gzipped
tarball of the container's /workspace to a host path via the native save
dialog.
Backend: download_container_backup runs `tar czf -` inside the container
(so excludes + compression happen there rather than streaming a 16 GB
workspace) and pipes stdout straight to the chosen file. Regenerable
build artifacts (node_modules, target, .git/objects) are excluded so the
archive stays restore-sized. Returns bytes written; stderr is captured
for error reporting and a zero-byte result is treated as failure.
Works whether the container is running or stopped (only requires that it
exists). Verified on the Ubuntu/GNU-tar container base.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a project switched backends (e.g. Bedrock -> Anthropic), the
recreated container kept authenticating against Bedrock, and SSO kept
firing after switching away. Three root causes, all fixed:
1. Recreation builds the new container from a `docker commit` snapshot.
commit always bakes the previous container's full ENV into the image
(an empty commit Config does NOT strip it, and the commit API cannot
remove env). So CLAUDE_CODE_USE_BEDROCK=1 / AWS_* survived into the
new container. Fix: create_container now explicitly clears every
managed auth key the active backend does not set (MANAGED_AUTH_KEYS),
so create-time env overrides the stale baked-in values.
2. awsAuthRefresh was written into ~/.claude.json (persisted home
volume) and never removed, so Claude Code kept invoking
triple-c-sso-refresh after switching to a non-SSO backend. Fix:
entrypoint now deletes awsAuthRefresh when AWS_SSO_AUTH_REFRESH_CMD
is unset, idempotent both ways.
3. Static/session AWS creds were baked into Config.Env at create time,
so a stop/start kept stale creds and rotated keys never refreshed
without a full recreation. Fix: static creds are no longer injected
as env vars; write_bedrock_static_credentials() writes
~/.aws/credentials (0600, secrets via exec env not argv) on every
start, and removes a stale ~/.aws/config left from a prior profile/SSO
session. Static creds also dropped from the bedrock fingerprint so a
key rotation refreshes in place instead of forcing recreation.
Adds exec_oneshot_env() for env-carrying one-shot execs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to PR review on terminal-layout-statusbar:
- [Major] Pin STT transcripts to the originating terminal. The single
useSTT instance is bound to the live active session, which can change
mid-recording. Capture the session id at recording start in a ref and
inject the transcript there instead of the live sessionId, so text
always lands in the terminal where recording began.
- [Minor] Clear the status-bar scroll state when the active terminal
unmounts, and null out termRef on dispose, so scrollActiveToBottom
can't point at a disposed terminal. Tab switches don't unmount, so
this only fires when the active session is actually closed.
- [Nit] Fix the terminal padding comment to match the symmetric value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Terminal layout fixes for the xterm pane:
- Stop the terminal grid from clipping its rightmost column / bottom
row. The padding was on the element xterm mounts into, which the
FitAddon measures; the grid overhang got clipped. Padding now lives on
a wrapper and the xterm host fills it with no padding.
- Move the STT mic from a floating bottom-left overlay into the status
bar (far right). A single useSTT instance bound to the active session
now lives in App; Ctrl+Shift+M routes through the store.
- Move "Jump to Current" from a floating terminal overlay into the
status bar. The active TerminalView surfaces its scroll state and
scroll action via the store.
- Tighten terminal padding (was 8/12/48/16) now that nothing floats over
it, so the terminal claims as much area as possible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a time-bounded `claude update` to entrypoint.sh that runs as the
claude user before the container is marked ready, so every terminal
session launches the latest CLI. Non-fatal and capped at 120s so an
offline/slow network never blocks container readiness; PATH covers both
~/.claude/bin and ~/.local/bin install locations.
Add flex-shrink-0 to the FileManagerModal header/footer so a long file
list can't squeeze them and the scroll region stays robust.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Right-click a tab (or double-click) to rename. Renamed labels show
as "ProjectName: CustomName" and are stored in the project's
renamed_session_names map. The entry is cleared on tab close.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New fields: GlobalAwsSettings.default_model_id, plus
GlobalOllamaSettings and GlobalOpenAiCompatibleSettings (base_url +
default_model_id each). When a per-project base_url or model_id is
blank, the container env vars and config fingerprints fall back to
the global value. Container recreation is triggered whenever the
resolved value changes, so editing a global default updates existing
projects on next start.
UI: added the new fields to AwsSettings and two new global settings
components, slotted into the Backends accordion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multiple-open accordion with per-section state persisted to
localStorage. Sections: General, Backends, Container, Git/SSH,
Tools, Updates. General is open by default; the rest are collapsed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persist collapsed state in localStorage. When collapsed, render a
narrow rail with Projects/MCP/Settings icon buttons that expand the
sidebar to that view on click.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mac/Windows release builds failed the Tauri version-mismatch check:
tauri (2.11.0) vs @tauri-apps/api (2.10.1). The Linux fix only updated
the Rust lockfile; the npm lockfile was still at 2.10.x. Both lockfiles
now resolve to 2.11.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's pre-build version check failed: tauri (2.10.2) vs @tauri-apps/api
(2.11.0). Both the Cargo.toml and package.json caret-pin to 2, so this is
purely a lockfile resolution fix — `cargo update -p tauri --precise
2.11.0` brings the Rust side up to match. Schema regeneration is included
since the gen/schemas/ output is keyed to the Tauri version.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sandbox mode: new per-project toggle that turns on Claude Code's bash
sandbox inside the container. Adds `bubblewrap` and `socat` to the
Dockerfile (the two Linux deps required by the sandbox), and emits a
managed `sandbox` block into `~/.claude/settings.json` via the existing
CLAUDE_CODE_SETTINGS_JSON entrypoint merge:
- `enabled` mirrors the Triple-C toggle and is always emitted, so the
entrypoint's recursive jq merge clears any prior on-state from the
persisted named volume — Triple-C is authoritative.
- `enableWeakerNestedSandbox: true` because we run inside Docker without
privileged user namespaces.
- `allowUnsandboxedCommands: false` to disable the `dangerouslyDisableSandbox`
escape hatch — opting into the sandbox shouldn't come with a runtime
bypass.
When sandbox is on, a SANDBOX_INSTRUCTIONS section is appended to
CLAUDE_INSTRUCTIONS so Claude can guide users through allowing extra
paths/domains, excluding `docker *`/`watchman *` from the sandbox, and
the rule that `sandbox.enabled` is owned by Triple-C. The Claude-Code
settings fingerprint includes sandbox state (only when on, to avoid
spuriously flagging existing containers for recreation on upgrade).
Bedrock service tier: new optional field on the per-project Bedrock
config. When set, exported as ANTHROPIC_BEDROCK_SERVICE_TIER (added in
Claude Code 2.1.122) and included in the Bedrock fingerprint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up the deprecation notes on dialog `ask`/`confirm` permissions
(now aliased to `allow-message`/`deny-message` and slated for removal
in Tauri v3). No behavior change — generated artifacts only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When Docker isn't detected on startup, surface a dialog offering a
one-click install (pkexec + get.docker.com on Linux, brew cask on
macOS, winget on Windows) with a graceful fallback to manual steps
and a link to official documentation. Install output streams back
to the UI via a tauri event.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add bottom padding to terminal containers so FitAddon proposes one
fewer row, leaving visible space below Claude Code's mode indicator.
Previously the bottom status line (e.g. "bypass permissions on") was
clipped against the container edge in fullscreen TUI mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes for the v0.3.x initial build failures:
1. **Compute Version step**: When no tags match v0.3.*, `grep` returns
exit 1 which under `pipefail` killed the step before the empty-tag
fallback could run. Added `|| true` to the pipeline so the fallback
(`git rev-list --count HEAD`) runs correctly on first 0.3.x build.
2. **Dockerfile apt-get update**: Transient archive.ubuntu.com mirror
sync failures (stale Packages.gz with mismatched hash) broke the
GitHub CLI install step. Added a shell retry loop (5 attempts with
10s sleep, clearing /var/lib/apt/lists/* between retries) to both
the main system packages step and the GitHub CLI step, plus
Acquire::Retries=3 on the other apt-get update calls for transient
network failures.
Also includes the Cargo.lock 0.2.0 → 0.3.0 rev that went with the
previous version bump commit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## What's New in v0.3.0
### Claude Code Settings (TUI Mode, Effort, Focus, Caching)
- New per-project and global settings for Claude Code CLI behavior
- **TUI Fullscreen Mode**: Flicker-free alt-screen rendering via CLAUDE_CODE_NO_FLICKER
- **Effort Level**: Control reasoning depth (low/medium/high)
- **Focus Mode**: Collapse tool output to one-line summaries
- **Thinking Summaries**: Show Claude's thinking process
- **Session Recap**: Get context when returning to a session
- **Auto-Scroll Disabled**: Disable auto-scroll in fullscreen TUI
- **Env Scrub**: Strip credentials from subprocess environments
- **Prompt Caching (1h)**: Enable 1-hour prompt cache TTL
- New ClaudeCodeSettingsModal accessible from project config and global settings
- Settings injected as env vars and ~/.claude/settings.json via entrypoint
### Session Naming
- Name Claude Code terminal sessions with the -n flag
- Session names displayed in terminal tabs instead of project name
### Global Default Fallbacks
- Global SSH key path now used when per-project SSH path is not set
- Global git name/email now used when per-project values are not set
- New UI in Settings panel for SSH key directory, git name, and git email
### Relaxed Environment Variable Filter
- CLAUDE_CODE_* env vars now allowed in custom env vars for power users
- Only specific internal vars (CLAUDE_INSTRUCTIONS, MCP_SERVERS_JSON, etc.) blocked
### Documentation
- Updated README, HOW-TO-USE, and CLAUDE.md with all new features
- New "Claude Code Tips" section documenting built-in CLI features
(/focus, /recap, /color, /loop, /powerup, /team-onboarding, setup wizards)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds first-class support for Claude Code CLI features (2.1.71-2.1.110):
- New ClaudeCodeSettings struct with per-project and global defaults for
TUI mode, effort level, focus mode, thinking summaries, session recap,
auto-scroll, env scrub, and 1-hour prompt caching
- Settings injected as env vars (CLAUDE_CODE_NO_FLICKER, etc.) and
~/.claude/settings.json entries via entrypoint.sh merge block
- New ClaudeCodeSettingsModal component for configuring settings
- Session naming support (-n flag passed to claude CLI, shown in tabs)
- Relaxed reserved prefix filter: CLAUDE_CODE_* env vars now allowed in
custom env vars UI for power users
- Global SSH key path, git name, and git email now used as fallbacks
when per-project values are not set, with UI in SettingsPanel
- Fingerprint-based change detection triggers container recreation when
Claude Code settings change
- Updated README, HOW-TO-USE, and CLAUDE.md documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For Bedrock Profile projects, SSO credentials are now checked and
refreshed on the host before the container starts, so the entrypoint
copies already-valid tokens. This eliminates the delay where users
had to wait for the terminal to open before being prompted to login.
The terminal-time fallback remains for mid-session credential expiry.
Also consolidates duplicated profile resolution logic into a shared
helper in aws_commands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously the STT container only started on-demand (mic button click or
manual start in settings). Now it auto-starts during app setup if
stt.enabled is true, matching the web terminal auto-start pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the native title attribute with a custom tooltip that appears
instantly on hover, displaying the shortcut in a styled kbd element.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lifts useSTT hook from SttButton into TerminalView so both the hotkey
and the button share the same recording state. The hotkey keeps terminal
focus so after transcription the user just presses Enter. The button
also no longer steals focus via onMouseDown preventDefault.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aligns the npm lockfile with the Cargo crate version to fix the Tauri
build version mismatch check.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cargo had resolved to 2.6.0 while npm had 2.7.0, causing the Tauri
build version check to fail.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a mic button to the terminal UI that captures speech, transcribes
it via a Faster Whisper sidecar container, and injects the text into
the terminal input. Includes settings panel for model selection
(tiny/small/medium), port config, and container lifecycle management.
- stt-container/: Dockerfile + FastAPI server for Whisper transcription
- Rust backend: STT container management, transcribe_audio IPC command
- Frontend: useSTT hook, SttButton, SttSettings, WAV encoder
- CI: Gitea Actions workflow for multi-arch STT image builds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The mission-control (Flight Control) project is being closed upstream.
This embeds the project files directly in the repo under container/mission-control/,
bakes them into the Docker image at /opt/mission-control, and copies them into place
at container startup instead of git cloning from GitHub.
Also adds missing osc52-clipboard, audio-shim, and triple-c-sso-refresh to the
programmatic Docker build context in image.rs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds position:sticky to the topbar and tab bar so they stay pinned
at the top when the virtual keyboard opens on tablets. Also uses
100dvh (dynamic viewport height) so the layout properly shrinks
when the keyboard appears on mobile browsers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a dedicated input bar at the bottom that bypasses mobile IME
composition buffering — characters are sent immediately on each input
event instead of waiting for word boundaries. Also adds helper buttons
(Enter, Tab, Ctrl+C) and a floating scroll-to-bottom button that
appears when scrolled up from the terminal output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds an axum HTTP+WebSocket server that runs alongside the Tauri app,
serving a standalone xterm.js-based terminal UI accessible from any
browser on the local network. Shares the existing ExecSessionManager
via Arc-wrapped stores, with token-based authentication and automatic
session cleanup on disconnect.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate env-var-based checks in container_needs_recreation() to label-based
checks. When a container snapshot is committed, Docker merges the image's env
vars with the container's, causing get_env() to return stale values and
triggering an infinite snapshot→recreate loop. Labels are immutable after
creation and immune to this merging behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The onScroll RAF optimization (only fire when atBottom changes) prevented
the button from showing because xterm's onScroll may not fire from wheel
events. Fix by setting isAtBottom(false) directly in the wheel handler
and removing the RAF guard to always schedule state updates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevent viewport jumping during Claude output by only re-enabling
auto-follow on user-initiated scrolls (wheel events within 300ms),
not on write-triggered xterm scroll events. Add a "Following/Paused"
toggle button in the top-right corner of the terminal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous fix checked isAtBottomRef inside the write callback, but
xterm's own scroll events during write processing could set the ref to
false (viewport desync), breaking auto-follow entirely.
Introduce a separate autoFollowRef that tracks user intent:
- Set to false only by explicit mouse wheel scroll-up (capture phase)
- Set to true when viewport reaches bottom or user clicks the button
- Write callback uses autoFollowRef so desync doesn't kill auto-follow
but user scroll-up correctly pauses it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The shouldFollow flag was captured before term.write() but the callback
ran asynchronously — if the user scrolled up in between, the stale flag
forced the viewport back to bottom, preventing the button from appearing.
Check isAtBottomRef at callback time instead so user scroll-up is respected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auto-scroll viewport on new output when user is at bottom, debounce
scroll state updates to reduce re-renders, preserve scroll position
across resize reflows, and fix "Jump to Current" button by re-fitting
the terminal to clear viewport desync.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- help_commands.rs: fetch HOW-TO-USE.md from GitHub raw instead of Gitea
- DockerSettings.tsx: display GHCR image address in settings UI
- HOW-TO-USE.md: update registry description to ghcr.io
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Work VPN blocks repo.anhonesthost.net, breaking update checks and image
pulls. Move all user-facing distribution to GitHub (releases API) and
GHCR (container images) while keeping Gitea as the source of truth for
development and CI.
- CI: push container images to GHCR alongside Gitea registry
- App updates: switch releases API to api.github.com, filter by asset
filename instead of tag suffix for unified releases
- Image updates: switch registry to ghcr.io with anonymous token auth
- Container pull: point REGISTRY_IMAGE to ghcr.io/shadowdao/triple-c-sandbox
- Rename GiteaRelease/GiteaAsset structs to GitHubRelease/GitHubAsset
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New projects default to standard permission mode (Claude asks before acting).
Existing projects default to full permissions ON, preserving current behavior.
UI toggle uses red/caution styling to highlight the security implications.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>