Commit Graph
18 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 5 c16f0d5b70 Put the cursor in the terminal after sending a note
Secret Scan / scan (push) Successful in 6s
Build App (Preview) / compute-version (pull_request) Successful in 3s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-macos (pull_request) Successful in 2m44s
Build App (Preview) / build-windows (pull_request) Successful in 5m3s
Build App (Preview) / build-linux (pull_request) Successful in 5m21s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
Sending already switched to the target terminal's tab, which looks like it
should be enough: `TerminalView` focuses xterm whenever a terminal becomes
active. But that effect keys off `active`, so it only fires on a *change* —
and the dock's ordinary case is sending to the terminal already on screen.
`setActiveTabKey` writes the key that is already set, nothing changes, no
effect re-runs, and focus stays on the Send button. The note is sitting in the
prompt and the user still has to click the terminal before pressing Enter.

So the send now asks for focus explicitly, through a one-shot request in the
store that `TerminalView` consumes and clears — the shape `pendingHomeTab`
already uses. Clearing is not tidiness: hold the id and the second send to the
same terminal writes a value that is already there, which is precisely the
no-op this exists to fix.

Focus is requested only on success. A failed send toasts and leaves the user
where they are, because there is nothing in the prompt to press Enter on.

The three `TerminalView` tests give focus away after mounting before making
any assertion, so what they observe is the request landing and never the focus
that `active` already grants on mount — which would pass with the feature
absent.

752 tests pass, 62 files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YPqHpjV4EL6RNEwrRKqQm
2026-09-02 13:28:31 -07:00
shadowdaoandClaude Opus 5 5c47656444 Cache notes in one place, and serialise a project's writes
Implements the design spec's §2 — "notes cached in zustand keyed by
project id" — which the plan substituted with a hook-local `useState`.

Sharing the `NotesPanel` *component* between the Project Home sub-tab and
the dock did not share the *cache*. Both resolve to the same project, so
two panels mount two `useNotes(P)`, each with its own list. Edit a note
in the dock and blur; the tab's copy is still pre-edit, and the tab's
next blur commits `{...staleRecord, title, body}` — the dock's edit gone
from disk with no error and no indicator. That is the feature's own
primary workflow: take notes in the dock while the agent runs, which is
the reason the dock exists, then go back to the tab.

`notesByProject` plus a per-project in-flight flag now hold the list.
Both surfaces render from one array; two panels mounting for one project
make one read; and because the write is keyed by project, a response
that lands after the user has moved on updates the project it belongs to
rather than whichever is on screen. This is also the boundary §8 says a
detached notes window needs.

Three more bugs in the same code, fixed with it:

- Delete-after-edit could resurrect the note. Clicking Delete with the
  textarea focused fires blur first, so `save_note` and `delete_note` go
  out back to back; Rust's `write_lock` stops them interleaving but does
  not order them, and a delete that wins the lock is undone by the
  upsert behind it. A project's mutations now go through one promise
  chain, module-scoped for the reason `useTerminal`'s input queue is.
- An unsaved draft vanished when any other note was saved, because the
  re-read replaced the list with the backend's. "New note" now persists,
  so the backend owns the row from the start — chosen over merging local
  drafts because a local-only row in a *shared* cache would exist in the
  panel that made it and nowhere else.
- The save outcome was reported for the wrong project after a switch:
  the guard covered only the list replacement, so the new project's
  SaveIndicator flashed "Saved ✓" for the old project's write. The
  indicator now resets on a project change and reports only its own.

`NotesPanel` also re-seeds its draft when the *stored* text of the note
it has selected changes, so an edit made in the other surface reaches
the editor and not only the list. It never overwrites something
half-typed; that still blurs into a last-writer-wins save, as any
blur-commit editor does.

NotesPanel.shared.test.tsx is the configuration none of the existing
tests had: two panels, one project, the real hook. Four of its six
assertions fail against the previous implementation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjL1E2JFNctUqCYotUwqqb
2026-09-01 13:47:01 -07:00
shadowdao 037ed78570 Test the dock's load-path clamp and keyboard resize direction
- notesDockWidth store initialization now clamps/defaults a bad
  localStorage value on load, not just on write (verified this fails
  without the clamp).
- The keyboard resize test asserts the exact widened/narrowed value
  instead of just that the setter was called, so a swapped or
  inverted arrow-key branch would be caught.
2026-09-01 13:22:35 -07:00
shadowdao 31e8f9df5f Add the notes dock 2026-09-01 13:15:13 -07:00
shadowdaoandClaude Opus 5 f7db4323be Make the drop gate a state question, not a geometry one
The gate that decides whether a native file drop is accepted has been wrong
twice in opposite directions, both times because it tried to be precise about
*which points* a dialog covers:

- Round 1 asked `el.contains(elementFromPoint(x, y))` and was handed the inner
  xterm host while the overlays are siblings, so the always-rendered
  Following/Paused button made the terminal's top-right corner permanently
  refuse drops.
- Round 2 replaced that with "is a blocking overlay painted here?" and deleted
  the document-wide gate. `elementFromPoint` returns the *topmost* element, and
  ToastHost is z-[60] against the Modal backdrop's z-50 in the same stacking
  context — so a refused drop pushed a toast, the toast covered the dialog, and
  the next drop released on it was reported clear and landed in the directory
  the dialog was covering. The gate armed its own hole.

Split the two questions instead of merging them:

- Geometry answers *whose* drop it is (rect hit test, unchanged), so exactly
  one listener speaks for a drop and a hidden pane's zero-size rect still keeps
  TerminalView and FilesTab from both firing.
- `dropIsBlocked` answers whether the app should take a drop at all —
  document-wide, no z-index in it. While a modal or blocking overlay is on
  screen anywhere, every drop is refused.

There is no `elementFromPoint` call left, so no future overlay can become a
drop hole by being painted high enough and no chrome can become a dead zone by
being painted at all. The cost is over-refusal while a dialog is open, in a
state the user entered deliberately, announced, writing nothing.

Also:
- `[aria-hidden="true"]` no longer disqualifies a blocker. It is not a
  visibility statement (it sits on visible decorative content), so a blocker
  nested in such a wrapper would have silently stopped blocking.
- Modal drops `data-blocks-drop` when its pane hides, and moves focus out of
  itself rather than leaving it inside a `display:none` panel.
- The refusal notice stays `kind: "info"` (an expected refusal is not an
  error, and an error card never auto-dismisses) and carries a `dedupeKey`, so
  repeated refusals replace rather than stack.

Tests: mutation-checked against the previous implementation — four in
dropTarget.test.ts, two in each of TerminalView/FilesTab, two in Modal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
2026-08-23 15:31:13 -07:00
shadow-testandClaude Opus 5 ab747ce53d Say what "open in container" is doing, and land on the pane doing it
Build App (Preview) / compute-version (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m40s
Build App (Preview) / build-linux (pull_request) Successful in 5m33s
Build App (Preview) / build-windows (pull_request) Successful in 5m40s
Build App (Preview) / prune-previews (pull_request) Successful in 1s
Opening a page is a container probe, a browser launch, a page load and
often a viewer start — several seconds during which the only feedback
was the click itself. Worse from a terminal, where the result appears in
a pane the user is not looking at.

So: the backend emits progress on the existing `container-progress`
channel at each step, the Browser tab renders that line whenever it is
set — the progress belongs to the project, not to whoever pressed the
button, which is what lets a terminal-initiated open report anywhere at
all — and the terminal's "In container" now selects the project's
Browser tab before starting, so the line has somewhere to appear.

Selecting a sub-tab from outside needed a route: `ProjectHome` keeps it
in local state, so `openProjectHomeTab` parks a request in the store and
the pane consumes it once. Consumed once, so it cannot fight the user's
own clicking afterwards.

Preview releases now prune themselves to the newest KEEP_PREVIEWS (2),
in a job that runs only if all three platforms published — a
half-finished run must not evict a good older build. The cleanup
workflow's manual sweep stays as the backstop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:13:21 -07:00
shadow-testandClaude Opus 5 d73096c937 Reorder tabs by dragging, and pop the browser view into its own window
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m31s
Build App / build-linux (pull_request) Successful in 5m35s
Build App / build-windows (pull_request) Successful in 6m9s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped
Two things the UI couldn't do: rearrange the tab strip, and watch the
browser while working somewhere else.

**Drag to reorder.** `moveTab`/`moveActiveTab` on the store, HTML5 drag on
the strip with a marker showing where the drop lands, `Ctrl+Shift+←/→` for
the same thing without a mouse. Reordering deliberately does not select
what it moves, so a drag aimed at a background tab doesn't yank the main
area away from a terminal mid-run. A tab being renamed is not draggable —
a draggable ancestor swallows the mouse-drag that selects text in its
input.

**Pop the browser view out.** `browser_view/popout.rs` opens the view's
existing token-bearing loopback URL as a second OS window, with a
"Keep on top" toggle so it can float above the app. Window-only: the
viewer, the proxy and the container are untouched, so popping out and
back interrupts nothing.

Three things it rests on:

- No capability lists that window, so it has no IPC surface — right for a
  page served out of a container, and it must stay that way.
- The app CSP is irrelevant to it: `frame-src` constrains what the app's
  document may *embed*, and this is a top-level document. The port range
  and the token gate are what actually protect it, unchanged.
- The window is owned by the session, so the supervisor's teardown closes
  it. A window onto a viewer that no longer exists is worse than none.

The pane drops its iframe while popped out — two viewers can both *drive*
the browser, and two cursors on one page is not a feature.

`lib.rs`'s `on_window_event` is now guarded on `label() == "main"`. It
fires for every window and its body stops every container and exits, so
without the guard closing a pop-out would quit the app.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 06:50:40 -07:00
shadow-testandClaude Opus 5 01a2f6aec8 Add Project Home, Auth Bridge, shared auth token, and Tier-1 polish
Project Home (DESIGN-REVIEW §B2): the project is promoted from a 280px
sidebar card to a first-class main-area view. ProjectCard.tsx (1,257
lines) is replaced by a select-only ProjectRow plus tabs for Overview,
Sessions, Automation, Config and Files. The PortMappings, FileManager
and ContainerProgress modals are absorbed rather than reimplemented.
Config gains a Saved/Saving/Failed indicator — save-on-blur failures
previously reached only console.error.

Tier-1 polish (DESIGN-REVIEW §A): new elevation, muted-accent, disabled
and focus-ring tokens; a global :focus-visible ring with every
focus:outline-none removed; filled buttons moved to --accent-emphasis
and white-on-success toggles retired, fixing three WCAG AA failures
(2.1:1, 2.5:1, 2.4:1); a shared Modal primitive with role="dialog",
focus trap and restore, adopted by all remaining modals; status
indicators that carry a glyph and word rather than colour alone.

Ctrl+Shift+W closes a tab, deliberately not Ctrl+W — that is readline's
kill-word, used constantly in the terminal this app is built around.

Auth Bridge: a general loopback-callback bridge so browser logins run
inside a container (aws sso login, Concourse fly login, claude login)
can complete against the host browser. Listeners are discovered from
/proc/net/tcp{,6} — ss/netstat/lsof are absent from the image — bound on
host 127.0.0.1 only, and tunnelled in over the Docker API via socat,
which keeps working on Docker Desktop where container IPs are not
routable. Falls back to [::1] because Node resolves localhost to IPv6
first, so claude login often binds ::1 alone. Opt-in per project.

This extracts create_attached_exec() and moves the existing terminal
session path onto it, so there is one attached-exec implementation
rather than two.

Shared auth token: `claude setup-token` is run in a container, the token
is stored in the OS keychain and injected as CLAUDE_CODE_OAUTH_TOKEN
into Anthropic-backend projects. Contrary to the initial design note,
setup-token uses an Anthropic-hosted redirect and blocks on a stdin
paste prompt rather than a loopback callback, so a stdin command is
required for the flow to complete.

The token is never logged, never returned to the frontend, and is
redacted from the streamed output with a stateful matcher that withholds
any tail that could still grow into a secret. Change detection uses a
random rotation id rather than a hash, since a hash in a docker-inspect
readable label would be an offline verification oracle.

Frontend 33 -> 51 tests; Rust 34 tests. Both builds clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 11:35:42 -07:00
shadow-testandClaude Opus 5 657c61939f Remove MCP tab and per-project MCP UI from frontend
Claude Code manages MCP natively now (`claude mcp add/list/remove`,
`.mcp.json`, `/mcp`), so Triple-C's own MCP server library is redundant.

Deletes components/mcp/, hooks/useMcpServers.ts, the MCP sidebar tab and
rail icon, the per-project enable checkboxes on ProjectCard, the
mcpServers slice of the Zustand store, the four IPC wrappers, and the
McpServer/McpTransportType types.

Rust backend is untouched in this commit; the commands simply become
unreachable. Backend removal and the legacy container/network cleanup
follow separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 10:04:25 -07:00
shadow-testandClaude Opus 4.8 3d2d979197 Fix xterm clipping; move mic + Jump to Current into status bar
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m19s
Build App / build-linux (pull_request) Successful in 5m3s
Build App / build-windows (pull_request) Successful in 7m34s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
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>
2026-06-28 19:26:05 -07:00
shadow-testandClaude Opus 4.7 7acc8b8d39 Add collapsible sidebar with icon rail
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>
2026-05-24 08:40:54 -07:00
shadow-testandClaude Opus 4.6 280358166a Show copy hint in status bar when terminal text is selected
Build App / compute-version (push) Successful in 3s
Build App / build-macos (push) Failing after 7s
Build App / build-windows (push) Failing after 19s
Build App / build-linux (push) Failing after 1m57s
Build App / create-tag (push) Has been skipped
Build App / sync-to-github (push) Has been skipped
When the user highlights text in the terminal, a "Ctrl+Shift+C to copy"
hint appears in the status bar next to the project/terminal counts.
The hint disappears when the selection is cleared.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:14:08 -07:00
shadow-testandClaude Opus 4.6 38082059a5 Rename AuthMode to Backend, fix LiteLLM variant typo, add image update alerts, clean up Settings
Build App / compute-version (push) Successful in 6s
Build App / build-macos (push) Successful in 2m21s
Build App / build-windows (push) Successful in 3m28s
Build App / build-linux (push) Successful in 5m14s
Build App / create-tag (push) Successful in 2s
Build App / sync-to-github (push) Successful in 10s
- Fix serde deserialization error: TypeScript sent "lit_llm" but Rust expected "lite_llm"
- Rename AuthMode enum to Backend across Rust and TypeScript (with serde alias for backward compat)
- Add container image update checking via registry digest comparison
- Improve Settings page: fix image address display spacing, remove per-project auth section
- Update UI labels from "Auth" to "Backend" throughout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:26:58 -07:00
shadow-testandClaude Opus 4.6 625d48a6ed feat: add MCP server support with global library and per-project toggles
Build App / build-macos (push) Successful in 2m20s
Build App / build-windows (push) Successful in 3m21s
Build App / build-linux (push) Successful in 5m8s
Build Container / build-container (push) Successful in 1m4s
Sync Release to GitHub / sync-release (release) Successful in 2s
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>
2026-03-04 08:57:12 -08:00
shadow-testandClaude Opus 4.6 da078af73f Remove Anthropic API key authentication support
Build App / build-windows (push) Successful in 2m28s
Build App / build-linux (push) Successful in 3m13s
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>
2026-03-01 03:59:58 +00:00
shadow-testandClaude Opus 4.6 7e1cc92aa4 Add app update detection and multi-folder project support
Build App / build-linux (push) Successful in 2m54s
Build App / build-windows (push) Successful in 4m18s
Build Container / build-container (push) Successful in 1m30s
Feature 1 - Update Detection: Query Gitea releases API on startup (3s
delay) and every 24h, compare patch versions by platform, show pulsing
"Update" button in TopBar with dialog for release notes/downloads.
Settings: auto-check toggle, manual check, dismiss per-version.

Feature 2 - Multi-Folder Projects: Replace single `path` with
`paths: Vec<ProjectPath>` (host_path + mount_name). Each folder mounts
to `/workspace/{mount_name}`. Auto-migrate old single-path JSON on load.
Container recreation via paths-fingerprint label. AddProjectDialog and
ProjectCard support add/remove/edit of multiple folders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 21:18:33 +00:00
shadow-testandClaude Opus 4.6 0f188783e1 Add container registry pull, image source settings, and global AWS config
Build Container / build-container (push) Successful in 1m59s
Support pulling images from registry (default: repo.anhonesthost.net/cybercovellc/triple-c/triple-c-sandbox:latest),
local builds, or custom images via a new settings UI. Add global AWS configuration
(config path auto-detect, profile picker, region) that serves as defaults overridable
per-project for Bedrock auth.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:22:49 +00:00
shadow-testandClaude Opus 4.6 97a0745ead Initial commit: Triple-C app, container, and CI
Tauri v2 desktop app (React/TypeScript + Rust) for managing
containerized Claude Code environments. Includes Gitea Actions
workflow for building and pushing the sandbox container image,
and a BUILDING.md guide for manual app builds on Linux and Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 04:29:51 +00:00