Files
Ignacy Łątka a92c859d07 feat(argent-lens): argent lens — bind a claude session to the preview window (#416)
## What

Adds `argent lens` — open Argent Lens bound to a fresh `claude` session.

## Why

The Lens preview window and the agent communicate through a shared,
process-wide tool-server that carries **no per-agent identity** (only a
coarse telemetry header). So when the user requests changes in the
window, there's no reliable way to route them to the *specific* agent
that opened it.

`argent lens` sidesteps the problem by inverting ownership: the human
launches Lens, and Lens spawns (and therefore owns) exactly one `claude`
terminal. The binding is 1:1 by construction — no identity plumbing
required.

## How it works

1. `argent lens` ensures a tool-server and **begins a CLI Lens
session**, which opens the preview window up front (no
`await_user_selection` needed) and keeps it open across rounds.
2. It spawns a tracked `claude` terminal in the current directory — a
TypeScript port of the macOS applet's `osascript` spawn/track mechanism
— seeded to propose variants without blocking.
3. A watcher polls `GET /preview/outcome`; on each submitted round it
types a one-line feedback summary into that terminal, queuing it to the
agent as its next prompt. Requesting changes therefore "talks back" to
the bound agent instead of closing the window.

macOS only (drives Terminal/iTerm via `osascript`); off-darwin it exits
with a clear message.

## Changes

- **CLI** `lens-terminal.ts` — `osascript` spawn/track + a new
write-into-running-session path (the "outputting" channel; injects input
into the running TUI, which a tty write cannot do).
- **CLI** `lens.ts` — the command + feedback formatter.
- **tool-server** — a `cliSession` flag in the variant store (opens the
window, suppresses auto-close on submit); `POST /preview/cli-session`
and `GET /preview/outcome` routes.
- **UI** — relabels the submit action to "Request changes" while a CLI
session is active.

Behind the existing `argent-lens` feature flag.

## Verification

- Build + typecheck across the workspace; ESLint (`--max-warnings 0`) +
Prettier clean.
- 34 new unit tests; full `@argent/cli` suite **118 passing**,
tool-server variant/preview suites **46 passing**.
- **E2E (observed on this machine):**
- **tool-server HTTP** — `cli-session` toggles `cliSession` true/false;
submitted feedback round-trips through `/preview/outcome`.
- **terminal injection** — spawned a window running `cat`, confirmed
`writeToSession` lands text in the running program's **stdin** (both
iTerm and Terminal), with embedded newlines flattened.
- **full relay** — the real `watchAndRelay` loop against a real
tool-server typed two distinct submission rounds' feedback into a
stand-in agent terminal (dedup on `completedAt` verified).
- `argent lens --help` and the feature-flag gate (exit 1 with guidance,
no spawn).

Note: a real end-to-end with a live `claude` agent was not run (it would
drive a paid interactive session); the agent-facing channel is proven by
injecting into a stand-in process's stdin via the exact same code path.

---------

Co-authored-by: Filip131311 <contact@filipkaminski.com>
2026-07-01 19:57:56 +02:00

246 lines
14 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* Argent Preview — THEME
*
* The ONE place every visual value lives. index.html must not contain a single
* hardcoded colour, pixel, duration, easing, z-index or opacity — only
* var(--token) references into this file. Tokens are named for what they ARE
* (their role), not an abstract scale, and shared only when they represent the
* same design decision. The "Motion & feel" block at the bottom is also read
* by the JS at boot (getComputedStyle), so animation feel is configured here
* too — not buried in the script.
*
* Light and dark are colocated per token via light-dark(LIGHT, DARK). The
* active side follows `color-scheme` on :root — the OS theme by default, or a
* forced value the in-app theme toggle writes to documentElement.style. So
* every consumer themes for free, with no second palette to keep in sync.
* ========================================================================== */
:root {
color-scheme: light dark;
/* ── Palette ─────────────────────────────────────────────────────────── */
/* Light + dark live side by side via light-dark(LIGHT, DARK); the active
side follows the `color-scheme` above (so the OS theme by default, or a
forced value when the user flips the in-app theme toggle). Tokens that are
self-contained ink-on-fill pairs (pin, danger) or only ever sit over the
device stream (touch dots, spotlight scrim, ring) read on any backdrop and
so stay single-valued. */
--color-bg: light-dark(#f4f5f7, #0e0e10);
--color-fg: light-dark(#1b1d21, #eaeaea);
--color-muted: light-dark(#5f636b, #8a8a90);
--color-accent: light-dark(
#2f6fd0,
#9bc7f0
); /* brand sky-blue on dark; a deeper blue carries the same role on light */
--color-on-accent: light-dark(#ffffff, #06122b); /* ink on accent-filled surfaces */
--color-border: light-dark(#d8dbe2, #2a2a2e);
--color-surface: light-dark(#ffffff, #16161a); /* panels (toolbar, cards, pops) */
--color-surface-raised: light-dark(#f6f7f9, #1c1c21); /* card header — lifted off surface */
--color-surface-hover: light-dark(#eef1f5, #1c1c22); /* hovered list item */
--color-row-hover: light-dark(#e8ecf3, #20202a); /* hovered variant row */
--color-ok: light-dark(#138a48, #5cdc8b);
--color-error: light-dark(#cf2f2f, #ff8080);
--color-thumb-bg: light-dark(#e3e5ea, #000000); /* letterbox behind variant previews */
--color-pin-bg: #d8a657; /* saved-annotation pin */
--color-pin-fg: #1a1205;
--color-danger-bg: light-dark(#c2334a, #7a2230); /* destructive (delete) button */
--color-danger-fg: #ffd9df;
/* ── Translucent effect colours ──────────────────────────────────────── */
--color-row-staged: light-dark(
rgba(19, 138, 72, 0.12),
rgba(92, 220, 139, 0.13)
); /* ok tint on staged row */
--touch-fill: rgba(155, 199, 240, 0.25); /* multi-touch dot body (accent) */
--touch-fill-pressed: rgba(155, 199, 240, 0.55);
--touch-stroke: rgba(155, 199, 240, 0.9); /* multi-touch dot ring */
--touch-anchor-fill: rgba(255, 200, 80, 0.9); /* anchored-touch dot */
--touch-anchor-stroke: rgba(255, 200, 80, 1);
--spotlight-scrim: rgba(8, 8, 10, 0.55); /* dim/blur over the stream */
--footbar-bg: light-dark(
rgba(255, 255, 255, 0.94),
rgba(22, 22, 26, 0.94)
); /* surface, semi-opaque */
/* ── Elevation (box-shadow values, by purpose) ───────────────────────── */
/* Only the shadow COLOUR flips per theme: heavy black halos read as grime on
a light surface, so light mode uses a soft blue-grey ink at low alpha. */
--shadow-stream: 0 10px 48px light-dark(rgba(18, 26, 42, 0.14), #0008);
--shadow-pill: 0 4px 14px light-dark(rgba(18, 26, 42, 0.12), #0008);
--shadow-card: 0 10px 30px light-dark(rgba(18, 26, 42, 0.13), #0009);
--shadow-pin: 0 3px 10px light-dark(rgba(18, 26, 42, 0.16), #0008);
--shadow-pop: 0 12px 36px light-dark(rgba(18, 26, 42, 0.18), #000b);
--ring-outline: 0 0 0 1px #000a; /* spotlight ring edge (over the stream) */
--touch-glow: 0 0 12px rgba(155, 199, 240, 0.5);
--dot-glow: 0 0 8px var(--color-ok);
--glow-accent: 0 10px 34px light-dark(rgba(47, 111, 208, 0.32), rgba(155, 199, 240, 0.3)); /* accent halo — footbar "ready to submit" pop */
/* ── Typography ──────────────────────────────────────────────────────── */
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-mono: ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
--fs-title: 15px;
--fs-body: 13px;
--fs-note: 12px;
--fs-read: 12.5px;
--fs-label: 11px;
--fs-tag: 10px;
--fs-mini: 9px;
--fw-semibold: 600;
--fw-bold: 700;
--fw-extrabold: 800;
--lh-normal: 1.5;
--lh-snug: 1.45;
--tracking-label: 0.04em;
--tracking-where: 0.03em;
/* ── Radii (by role) ─────────────────────────────────────────────────── */
/* Brand geometry: the argent site rounds generously (terminal card, pills).
Mid radii bumped a notch toward that softer feel; tiny tags and the
pin-tail teardrop stay tight so their shape doesn't distort. */
--radius-chip: 4px;
--radius-control: 6px; /* toolbar controls, spot ring */
--radius-input: 7px; /* textareas, pop buttons */
--radius-button: 8px;
--radius-item: 10px; /* device-picker item */
--radius-card: 14px; /* stream, cards, popovers, footbar */
--radius-pin-tail: 2px; /* annotation-pin teardrop corner */
--radius-round: 50%;
--radius-pill: 999px;
/* ── Borders ─────────────────────────────────────────────────────────── */
--border-hairline: 1px;
--border-strong: 2px; /* spotlight ring, touch dot */
--connector-width: 1.5; /* SVG stroke-width (unitless) */
/* ── Spacing — gaps (recurring roles) ────────────────────────────────── */
--gap-tight: 5px;
--gap-inline: 6px;
--gap-list: 8px;
--gap-bar: 10px;
--gap-stack: 12px;
/* ── Spacing — padding (per role) ────────────────────────────────────── */
--pad-bar: 8px 12px; /* toolbar */
--pad-control: 4px 10px; /* select / button */
--pad-toggle: 4px 12px;
--pad-view: 12px; /* viewport */
--pad-empty: 32px;
--pad-picker: 24px;
--pad-item: 12px 14px; /* device-picker item */
--pad-refresh: 6px 12px;
--pad-code: 2px 6px;
--pad-pill: 3px 11px;
--pad-card-head: 7px 10px;
--pad-row: 5px 10px; /* tighter vertical so a 2-variant card fits its column without clipping */
--pad-cmt: 6px 10px; /* in-card comment box */
--pad-input: 6px 8px; /* popover textareas */
--pad-pop: 10px; /* comment / annotation popover */
--pad-btn: 6px 10px; /* popover buttons */
--pad-lab: 1px 7px; /* spotlight ring label */
--pad-footbar: 8px 10px;
--pad-footbar-input: 6px 9px;
--pad-complete: 8px 14px;
/* one-off margins / nudges (kept named by what they position) */
--sub-margin: -4px 0 6px; /* device-picker caption */
--comment-title-gap: 7px; /* comment popover heading → body */
--annrow-mt: 9px; /* annotation popover button row */
--annpop-gap: 8px; /* popover ↔ pin gap, resting */
--annpop-gap-show: 14px; /* popover ↔ pin gap, shown */
--press-shift: 1px; /* :active downward nudge */
/* ── Component sizes ─────────────────────────────────────────────────── */
--card-width: 232px;
--vcard-body-max-h: none; /* JS sets a per-card max-height only when the column overflows the viewport; otherwise the body fits its variant count. */
--thumb-size: 96px;
--collapsed-thumb-h: 58px; /* once a variant is picked, the others collapse to a row with the preview ~3 lines of body text tall (3 × --fs-body × --lh-normal ≈ 58px) */
--pin-size: 20px;
--dot-size: 8px; /* status dot, device-picker dot */
--card-dot-size: 7px; /* card-header dot */
--icon-btn-size: 34px; /* floating top-right icon controls (theme, comment, close) */
--icon-size: 17px; /* glyph inside an icon button */
--comment-pop-width: 248px;
--annpop-width: 232px;
--annpop-txt-max-h: 160px;
--textarea-min-h: 64px;
--cmt-min-h: 28px;
--footbar-input-width: 260px;
--footbar-bottom: 14px;
--footbar-right: 14px;
--resize-edge: 6px; /* drag-layer inset so the frameless window's edges still resize */
--traffic-light-gutter: 84px; /* left inset for top-left chrome to clear the native macOS window buttons (Electron only) */
--footbar-max-width: 70%;
--select-min-width: 240px;
--debug-min-width: 220px;
--empty-max-width: 480px;
--picker-max-width: 420px;
--lab-max-width: 240px;
--lab-top: -22px;
--complete-note-max-width: 200px;
--modal-max-width: 360px;
--pad-modal: 18px;
--stream-max-h: 80dvh; /* dvh, not vh: in a browser tab `vh` is the toolbar-retracted (large) viewport, so the centered stream overflowed the %-height container and got clipped at the bottom. `dvh` tracks the visible viewport. In the frameless Electron window (no toolbar) dvh == vh, so production is unchanged. */
--backdrop-blur: 1.67px; /* light haze over the dimmed stream (3× softer than the former 5px) */
/* ── Cursors ─────────────────────────────────────────────────────────── */
/* Comment-mode cursor — a Figma-style speech bubble that makes "you are
placing a comment" unmistakable (replaces the generic crosshair). Accent
fill + white outline & dots so it reads on the bright stream AND on the
dark spotlight scrim. "7 24" is the hotspot: the tail tip, where the click
actually lands. Baked as a data-URI because a cursor image can't reference
CSS vars — so the colours live here, in the theme, like every other value. */
--cursor-comment:
url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2228%22%20height%3D%2228%22%20viewBox%3D%220%200%2028%2028%22%3E%3Cpath%20d%3D%22M6%203h16a3%203%200%200%201%203%203v10a3%203%200%200%201-3%203h-9l-6%205v-5h-1a3%203%200%200%201-3-3V6a3%203%200%200%201%203-3z%22%20fill%3D%22%232f6fd0%22%20stroke%3D%22%23ffffff%22%20stroke-width%3D%221.5%22%20stroke-linejoin%3D%22round%22%2F%3E%3Ccircle%20cx%3D%229%22%20cy%3D%2211%22%20r%3D%221.4%22%20fill%3D%22%23ffffff%22%2F%3E%3Ccircle%20cx%3D%2214%22%20cy%3D%2211%22%20r%3D%221.4%22%20fill%3D%22%23ffffff%22%2F%3E%3Ccircle%20cx%3D%2219%22%20cy%3D%2211%22%20r%3D%221.4%22%20fill%3D%22%23ffffff%22%2F%3E%3C%2Fsvg%3E")
7 24,
crosshair;
/* ── Opacities ───────────────────────────────────────────────────────── */
--opacity-disabled: 0.45;
--opacity-connector: 0.7;
--opacity-pill: 0.92;
/* ── Z-index layers ──────────────────────────────────────────────────── */
--z-connectors: 4;
--z-cards: 5;
--z-pin: 7;
--z-spot-hit: 8;
--z-pop: 9; /* off-screen pill, comment popover */
--z-overlay: 10; /* annotation popover, footbar */
--z-modal: 12; /* confirm modal (above the footbar) */
--z-ctx: 14; /* right-click context menu (above every popover/menu) */
/* ── Motion & feel (CSS transitions AND read by JS at boot) ──────────── */
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--ease-pop: cubic-bezier(0.34, 1.56, 0.64, 1);
--dur-press: 0.06s;
--dur-hover: 0.15s;
--dur-fade: 0.18s;
--dur-pill: 0.2s;
--dur-card-in: 0.22s;
--dur-spot: 0.28s;
--dur-card-move: 0.34s;
--dither-dur: 0.72s; /* light↔dark dithered wavefront sweep */
--card-gone-scale: 0.8; /* shrink as a card pops away */
--footbar-ready-scale: 1.05; /* footbar grows a touch once every element is chosen */
/* Spring feel — JS integrates these (ω rad/s, ζ damping ratio). The bubble
spring is slow & barely-overshooting (cards drift into place); the tight
spring is fast & critically damped (connectors/pins track every frame). */
--spring-bubble-omega: 4.2;
--spring-bubble-zeta: 1; /* DEMO: critically damped — no overshoot/bounce on cards */
--spring-tight-omega: 26;
--spring-tight-zeta: 1;
--card-gone-grace-ms: 550; /* ride out brief edge drop-outs before fading */
--poll-interval-ms: 1200; /* /preview/variants poll cadence */
--tree-stale-ms: 6000; /* drop the describe tree if older than this */
--flick-max-vel: 1500; /* clamp thrown-card release velocity (px/s) */
--spot-idle-dismiss-ms: 150; /* stopped-aimless grace before releasing spotlight */
--spot-traj-window-ms: 80; /* recent-sample window used to derive pointer velocity */
--spot-traj-stopped-speed: 0.05; /* below this normalized speed (1/s) the pointer counts as stopped */
--spot-traj-reach: 0.5; /* normalized forward distance to look for a trajectory target */
--spot-traj-hitbox-margin: 0.2; /* per-side margin (fraction of element size). Inflates BOTH the sticky focus hitbox (so a tiny drift out of an element keeps it focused) and the trajectory predictor hitbox. Strict containment still wins, so margins never steal focus from an obviously hovered element. */
--spot-max-frame-area: 0.85; /* nodes whose frame covers more than this fraction of the screen are not treated as highlightables (catches Android full-screen container roots that lack iOS's explicit root flag) */
--annpop-hover-grace-ms: 280; /* hover-card grace: pin ↔ popover travel */
--success-toast-show-ms: 1200; /* how long the post-submit confirmation card lingers (browser mode; in Electron the window closes around it sooner) */
}