Compare commits

...

8 Commits

Author SHA1 Message Date
jknapp ccdfc52dce Merge pull request 'docs: document terminal layout & StatusBar control gotchas' (#11) from docs/terminal-layout-gotchas into main 2026-07-19 16:26:02 +00:00
shadow-test 2e661979ea docs: document terminal layout & StatusBar control gotchas
Capture the non-obvious implementation gotchas from PR #7
(terminal-layout-statusbar) in TECHNICAL.md: wrapper-vs-host xterm
padding, global StatusBar controls, recordingSessionIdRef transcript
pinning, active-only Jump-to-Current state, and the Zustand
object-merge rule for publishing action callbacks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:12:54 -07:00
jknapp 59d89bcd1b Merge pull request 'Rename backup archive root so extraction dir mode isn't clobbered' (#10) from fix/backup-root-dir-mode into main
Build App / compute-version (push) Successful in 4s
Build App / build-macos (push) Successful in 2m21s
Build App / build-windows (push) Successful in 4m35s
Build App / build-linux (push) Successful in 5m1s
Build App / create-tag (push) Successful in 4s
Build App / sync-to-github (push) Successful in 10s
2026-07-01 13:35:02 +00:00
shadow-test 26adccce5b Rename backup archive root so extraction dir mode isn't clobbered
Build App / compute-version (pull_request) Successful in 4s
Build App / build-macos (pull_request) Successful in 2m14s
Build App / build-windows (pull_request) Successful in 4m26s
Build App / build-linux (pull_request) Successful in 5m0s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
The transform used `s,^\./,workspace/,`, which rewrites the workspace
*contents* (`./foo` -> `workspace/foo`) but leaves tar's root member as a
bare `./`. That `./` entry carries the source root's mode/mtime, and on
extraction tar stamps them onto the extraction directory itself.

Match the leading `.` instead (`s,^\.,workspace,`) so the root member is
renamed `./` -> `workspace`, giving the archive a proper `workspace/`
directory entry and no bare `./`. The extraction directory is left
untouched. Contents, hidden files, excludes, symlink targets and the
`flags=rh` hardlink handling are unchanged.

Verified in-container: archive top level is exactly `workspace/` +
`home-claude/`, no `./` member, node_modules excluded, extraction into a
0755 dir leaves it 0755, workspace/.git preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 06:33:02 -07:00
jknapp 876ba8a8fc Merge pull request 'Nest workspace under workspace/ in project backup' (#9) from fix/backup-workspace-nesting into main
Build App / compute-version (push) Successful in 2s
Build App / build-macos (push) Successful in 2m22s
Build App / build-windows (push) Successful in 4m36s
Build App / build-linux (push) Successful in 5m5s
Build App / create-tag (push) Successful in 3s
Build App / sync-to-github (push) Successful in 12s
2026-07-01 13:23:08 +00:00
shadow-test 5cd528a4ef Use flags=rh so intra-workspace hardlinks survive the transform
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 4m24s
Build App / build-linux (pull_request) Successful in 5m3s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
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>
2026-07-01 06:22:14 -07:00
shadow-test c3fc029b1d Nest workspace under workspace/ in project backup
Build App / compute-version (pull_request) Successful in 4s
Build App / build-macos (pull_request) Successful in 2m17s
Build App / build-linux (pull_request) Successful in 5m7s
Build App / build-windows (pull_request) Successful in 5m9s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
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>
2026-07-01 06:18:08 -07:00
jknapp dc253e8da0 Merge pull request 'Fix backend-switch AWS auth + add /workspace backup and terminal file drag-and-drop' (#8) from fix/backend-switch-aws-creds into main
Build App / compute-version (push) Successful in 3s
Build Container / build-container (push) Successful in 34s
Build App / build-macos (push) Successful in 2m23s
Build App / build-windows (push) Successful in 3m14s
Build App / build-linux (push) Successful in 6m22s
Build App / create-tag (push) Successful in 5s
Build App / sync-to-github (push) Successful in 10s
2026-06-30 22:20:00 +00:00
2 changed files with 22 additions and 1 deletions
+10
View File
@@ -57,6 +57,16 @@ Tauri uses a Rust backend paired with a web-based frontend rendered by the OS-na
- **Web links addon** — `@xterm/addon-web-links` makes URLs in terminal output clickable. Combined with `tauri-plugin-opener`, clicked URLs open in the host browser — essential for the `claude login` OAuth flow where Claude prints an authentication URL that must be opened on the host.
- **Bidirectional data flow** — xterm.js exposes `term.onData()` for user keystrokes and `term.write()` for incoming data. This maps directly to our Tauri event-based streaming architecture.
#### Terminal Layout & StatusBar Controls
Implementation gotchas for the terminal view and its global controls (merged in PR #7, `terminal-layout-statusbar`):
- **xterm padding lives on a wrapper, never the host.** FitAddon measures the same element that `term.open()` mounts into, so any padding on that host element makes the grid overhang and clip its rightmost column / bottom row. Padding must live on a **wrapper `div`**; the xterm host fills it with no padding of its own. Do not reintroduce padding on the host element in `TerminalView.tsx`.
- **STT mic and "Jump to Current" live in the global `StatusBar`, not per-terminal overlays.** There is a single `useSTT` instance in `App.tsx` bound to the active session. `Ctrl+Shift+M` routes through the Zustand store (`sttToggle`).
- **Recording is pinned to where it started.** The STT transcript targets `recordingSessionIdRef` (the session recording began in), **not** the live active session — switching tabs mid-recording must not misroute the transcript.
- **"Jump to Current" state is written only by the active terminal.** The active `TerminalView` surfaces `terminalAtBottom` and `scrollActiveToBottom` through the store; only the active terminal writes them, and they are cleared on its unmount.
- **Set store function values via object-merge, not the updater form** — `set({ fn: value })`, not `set(state => ...)` — when publishing action callbacks (like `scrollActiveToBottom`) into the Zustand store.
### bollard (Docker API)
**Chosen over:** Shelling out to the `docker` CLI, dockerode (Node.js), docker-api (Python)
+12 -1
View File
@@ -155,7 +155,7 @@ pub async fn download_container_file(
/// Create a `.tar.gz` backup of the container and stream it to a host file.
/// The archive contains:
/// - the workspace (default /workspace), minus regenerable build artifacts
/// (node_modules, target), at the archive root, and
/// (node_modules, target), under `workspace/`, and
/// - a sanitized copy of the home config under `home-claude/`: ~/.claude.json
/// with secret-bearing keys removed (mcpServers/settings kept) and ~/.claude/
/// minus the OAuth `.credentials.json`, so MCP servers, settings and skills
@@ -204,6 +204,16 @@ pub async fn download_container_backup(
// transient unreadable file from aborting the whole backup. If jq can't
// parse ~/.claude.json we substitute an empty object — never the raw file —
// so secrets can't leak through the sanitization fallback.
// The `--transform` nests the workspace under `workspace/` (parallel to
// `home-claude/`) so an extracted archive has both clearly labeled instead
// of scattering the workspace files into the extraction dir. Rewriting the
// leading `.` (rather than `./`) also renames tar's root member from `./` to
// `workspace`, so the archive carries a proper `workspace/` dir entry rather
// than a bare `./` that would stamp the source root's mode/mtime onto the
// extraction directory. `flags=rh` rewrites regular member names AND
// hardlink target names (so an intra-workspace hardlink pair still resolves
// on extract) while leaving symlink targets untouched (rewriting those would
// corrupt relative/absolute links).
let script = r#"set -e
STAGE=$(mktemp -d)
trap 'rm -rf "$STAGE"' EXIT
@@ -221,6 +231,7 @@ if [ -d "$HOME/.claude" ]; then
fi
tar czf - --ignore-failed-read \
--exclude='*/node_modules' --exclude='*/target' \
--transform='flags=rh;s,^\.,workspace,' \
-C "$TC_BACKUP_SRC" . \
-C "$STAGE" home-claude"#;