{# One project row. Rendered by the dashboard grid loop AND by
GET /api/projects/{name}/row for reactive insertion (no full-page reload). `idx` is
the row's position for the search/cap visibility gate (0 from the fragment route, so a
freshly created project is always shown).
The `:style` below reorders rows VISUALLY for a non-name sort via CSS `order` — it does
NOT change DOM/tab order (focus + screen-reader order stay A–Z), the deliberate trade for
an opt-in cosmetic sort. Don't turn this into a DOM reorder (the anti-pattern the Active
zone avoids). WCAG 2.4.3 note: visual order diverges from DOM/tab order only on opt-in
non-default sorts; all interactive controls are self-contained within each row so
keyboard navigation is coherent regardless of visual position. #}
{# DES-07: responsive name cap — clamp scales the truncation width with the viewport
(narrow phone → ~10rem, wide desktop → 28rem) instead of a fixed 16rem; min-width:0
lets text-truncate shrink inside the flex row. #}
{{ p.name }}
{% if p.is_git_repo %}Git{% endif %}
{% if p.has_claude_md %}CLAUDE.md{% endif %}
{# FE-578: surface a launch precondition INSIDE the popover; Run stays ENABLED
(disabling it would hide the spawn picker / trust confirm that resolve it).
On open we coerce a non-git+worktree default to a valid mode; the note below
the Spawn picker explains it. #}
{# Dismissing the popover (outside-click / Escape) also clears any pending
trust/bypass gate (#686) so a hidden gate never lingers — reopening starts
a fresh launch. mopen is reset too so More options collapses on reopen. #}
How do you want to drive it?
Connectable from claude.ai / Desktop — converse with it, and Stop it here when done.
{# A
{# Option text + effect tooltip both come from the canonical
permission_labels map (#685) — never a hand-maintained copy.
bypassPermissions is offered only where the per-project ceiling
allows it and only for the Desktop/bridge launch (the typed-confirm
footgun guard); the "(Desktop only)" caveat is a contextual hint
appended here, not baked into the canonical label. #}
{# Plain-language effect of the SELECTED mode, inline (not just title=) so it's
visible on touch — the riskiest per-launch choice shouldn't hide in a tooltip. #}
{# "More options" — a single disclosure (#686) holding the ADVANCED choices
for the Desktop/bridge launch: the Mode selector (Server Mode / Interactive
Session) AND Spawn. Mode is an advanced choice, tucked away by default; the
safe Desktop default works with this closed. Only the Desktop launch exposes
these (browser/background ignore spawn + resume mode). #}
{% if pty_supported %}
{# Mode (Server Mode / Interactive Session) — the advanced launch-shape choice,
tucked inside More options. Drives whether Spawn applies (standard) or the
single-session note shows (pty). #}
{% endif %}
{# Custom bridge/session display name (#780) — Server Mode (standard)
only: it becomes `claude remote-control --name`. The Interactive
Session (pty) flag form has no equivalent flag, so this is hidden
there rather than silently ignored (avoids a name typed here from
looking like it applies when Mode is flipped to pty). Blank keeps
today's default (the project name); the server validates length +
control characters before any spawn side effect. #}
{#
{# Sandbox toggle (#780) removed for 1.0 (#1037): `--sandbox` never
propagated to the server-mode session worker, so the control silently
did nothing. It returns behind dependency preflight + platform gating
in #1046. #}
{# Spawn applies to BOTH modes (#779): standard maps to
`claude remote-control --spawn`; an Interactive Session honors
same-dir/worktree (worktree = `claude --worktree`, its own isolated
git worktree that survives Resume). Only `session` is standard-only —
it stays visible but disabled in pty mode (coercePtySpawn resets it). #}
{# #578: surface (don't disable) — a non-git dir can't worktree, so
note it here and coerceSpawnMode() falls back to same-dir on open.
x-text (auto-escaped, never x-html); no d-* class on this x-show el. #}
{% if pty_supported %}
{# Resume picker (#303) — pty/Interactive Session only: fork a picked
past conversation into the NEW session (`--resume
--fork-session`; the original conversation is never clobbered).
Options come from the transcripts API (non-live, non-empty), loaded
on popover open; labels are server-redacted first prompts rendered
via x-text (never x-html). "" = fresh conversation (the default,
re-coerced on every popover open). #}
Forking a previous
conversation resumes it in a new session — the original stays untouched.
{# Keep d-flex OFF this x-show element — `.d-flex{display:flex!important}`
overrides x-show's inline display:none and would pin this hint visible
even in standard mode (same bug as the live-tail banners). Flex goes on
the inner wrapper. #}
Runs as one interactive session — several can run per project.
{% if p.is_git_repo %}
{# No-worktree collision warning (#779): warn, never block — the launch
proceeds; the server echoes the same advisory (warnings[], #778).
No d-* class on the x-show element (Tabler !important, cf. #538). #}
Sessions sharing the project directory can make conflicting edits — choose
Spawn: worktree to isolate this one.
{% endif %}
{% endif %}
{# Trust-on-start + bypassPermissions gates fold INSIDE the popover (#686) —
no separate below-the-row alerts. Both are hidden until the Run button routes
a Desktop launch into start(), which opens the matching gate; resolving or
cancelling either one dismisses the popover (via the launch-pop-close event). #}
{# When a gate opens INSIDE the popover the Run button hides, so move focus
into the gate's first control (WCAG 2.4.3 / 4.1.3) — else focus is orphaned
on and a keyboard/SR user gets no signal a confirm now blocks launch.
x-effect re-runs when confirmTrust[name] flips; $nextTick waits for x-show. #}
Trust the files in {{ p.name }}? Launching a session here
lets it run tools and execute any code in CLAUDE.md /
.claude in this directory.
bypassPermissions lets sessions in {{ p.name }}
run tools without asking. Type the project name to confirm.
{# #851: interactive-launch MCP-approval SOFT-BLOCK. An Interactive Session
with unapproved committed .mcp.json servers deterministically hangs at
claude's enable prompt (the read-only terminal can't answer it), so block
by default with an error-styled confirm rather than the passive WARN. Two
actions: resolve (a real BUTTON, not a link — #851 feedback) or the explicit
"Launch anyway" override (guards the #850 false-positive case). #}
This Interactive Session will hang at claude's MCP approval prompt and won't connect — the read-only terminal can't answer it. Resolve the unapproved server(s) first.
{# Primary Run action. Hidden while a trust/bypass/MCP gate is showing INSIDE the
popover so the gate's own confirm button is the single next step (no competing
primary). On a clean launch (no gate pending) launchRun() returns true and the
popover closes; when it opens a gate it returns false and the popover stays. #}
{# Semantic end-of-popover sentinel: a stable structural boundary for tests
(so they don't couple to template whitespace/nesting). Renders nothing. #}
Readiness checks for {{ p.name }} before launch
{# #837: an unapproved-server warning is actionable — link straight into
this project's Server-approvals panel (project scope, mcp surface)
rather than leaving the operator to hunt for it. WARN-only / read-only:
this button only navigates, it never approves/rejects/mutates config.
Gated on config-write being enabled (same invariant as the navbar
trigger in dashboard.html): when config-write is OFF the
Server-approvals panel + its /api/config-write/* routes 404, so the
link would lead to a dead panel — the WARNING still shows (it's
informative), only the dead action is suppressed. config_write_enabled
is threaded into BOTH render paths (full page + the api_project_row
fragment) so a dynamically-inserted row never wrongly loses the link. #}
{% if config_write_enabled %}
{% endif %}
{# The trust-on-start + bypassPermissions confirm gates moved INSIDE the launch
popover (#686) — they no longer render as separate below-the-row alerts. The
`data-test="inline-error"` hook is load-bearing: three `.alert-danger.mb-0`
blocks now share this row (the in-popover bypass-confirm, this transient-action
error, and the spawn-failure detail below), so a bare `.alert-danger.mb-0`
selector is ambiguous — tests must target this stable hook. #}
Session failed to start
CLAUDE.mdnew file
A session is running — saved changes apply to new sessions, not the attached one.