Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@
text-overflow: ellipsis; line-height: 1.4;
}
.sb-item-meta { font-size: 11px; color: hsl(var(--muted-foreground)); margin-top: 1px; display: flex; gap: 6px; }
.sb-resume { padding: 8px; border-top: 1px solid hsl(var(--border)); }
.sb-resume-btn { width: 100%; display: flex; align-items: center; gap: 8px; padding: 6px 8px; border: none; background: none; border-radius: 6px; cursor: pointer; transition: all .12s; color: hsl(var(--muted-foreground)); font: inherit; font-size: 13px; }
.sb-resume-btn:hover { color: hsl(var(--foreground)); background: hsl(var(--muted) / .5); }
.sb-footer { padding: 12px 16px; border-top: 1px solid hsl(var(--border)); transition: border-color .2s; }
.sb-footer a {
color: hsl(var(--muted-foreground)); text-decoration: none; font-size: 12px;
Expand Down Expand Up @@ -639,6 +642,7 @@
<div class="sb-item"><div class="sb-item-icon"><svg viewBox="-0.27 -0.23 16.55 16.55"><path d="M14.949 6.547a3.94 3.94 0 0 0-.348-3.273 4.11 4.11 0 0 0-4.4-1.934A4.1 4.1 0 0 0 8.423.2 4.15 4.15 0 0 0 6.305.086a4.1 4.1 0 0 0-1.891.948 4.04 4.04 0 0 0-1.158 1.753 4.1 4.1 0 0 0-1.563.679A4 4 0 0 0 .554 4.72a3.99 3.99 0 0 0 .502 4.731 3.94 3.94 0 0 0 .346 3.274 4.11 4.11 0 0 0 4.402 1.933c.382.425.852.764 1.377.995.526.231 1.095.35 1.67.346 1.78.002 3.358-1.132 3.901-2.804a4.1 4.1 0 0 0 1.563-.68 4 4 0 0 0 1.14-1.253 3.99 3.99 0 0 0-.506-4.716m-6.097 8.406a3.05 3.05 0 0 1-1.945-.694l.096-.054 3.23-1.838a.53.53 0 0 0 .265-.455v-4.49l1.366.778q.02.011.025.035v3.722c-.003 1.653-1.361 2.992-3.037 2.996m-6.53-2.75a2.95 2.95 0 0 1-.36-2.01l.095.057L5.29 12.09a.53.53 0 0 0 .527 0l3.949-2.246v1.555a.05.05 0 0 1-.022.041L6.473 13.3c-1.454.826-3.311.335-4.15-1.098m-.85-6.94A3.02 3.02 0 0 1 3.07 3.949v3.785a.51.51 0 0 0 .262.451l3.93 2.237-1.366.779a.05.05 0 0 1-.048 0L2.585 9.342a2.98 2.98 0 0 1-1.113-4.094zm11.216 2.571L8.747 5.576l1.362-.776a.05.05 0 0 1 .048 0l3.265 1.86a3 3 0 0 1 1.173 1.207 2.96 2.96 0 0 1-.27 3.2 3.05 3.05 0 0 1-1.36.997V8.279a.52.52 0 0 0-.276-.445m1.36-2.015-.097-.057-3.226-1.855a.53.53 0 0 0-.53 0L6.249 6.153V4.598a.04.04 0 0 1 .019-.04L9.533 2.7a3.07 3.07 0 0 1 3.257.139c.474.325.843.778 1.066 1.303.223.526.289 1.103.191 1.664zM5.503 8.575 4.139 7.8a.05.05 0 0 1-.026-.037V4.049c0-.57.166-1.127.476-1.607s.752-.864 1.275-1.105a3.08 3.08 0 0 1 3.234.41l-.096.054-3.23 1.838a.53.53 0 0 0-.265.455zm.742-1.577 1.758-1 1.762 1v2l-1.755 1-1.762-1z"/></svg></div><div class="sb-item-body"><div class="sb-item-title">Setup CI pipeline</div><div class="sb-item-meta"><span>freshell</span><span>2d</span></div></div></div>
<div class="sb-item"><div class="sb-item-icon"><svg viewBox="75.73 64.08 872.25 872.25"><path d="M616.9,649.5h-209.7c0,0,0,104.7,0,104.7h-56.6c0,0,.2-104.5.2-104.5h-48.6s.2,104.5.2,104.5h-56.7c0,0,.2-104.4.2-104.4l-48.6-.7v-96.4c.1,0-104.8,0-104.8,0v-104.9s104.9,0,104.9,0v-201.6c0,0,628.9,0,628.9,0v201.6c0,0,104.9,0,104.9,0v104.9s-104.9,0-104.9,0v96.6c.1,0-56.5.4-56.5.4l.2,104.5h-48.6s.2-104.6.2-104.6h-56.6s.2,104.6.2,104.6h-48.6s.2-104.6.2-104.6ZM351.1,447.5l-.5-96.4h-48.4c0,0,0,96.6,0,96.6l48.8-.2ZM722,447.7l-.4-96.7h-56.5c0,0,0,96.8,0,96.8h56.9Z"/></svg></div><div class="sb-item-body"><div class="sb-item-title">Add pane resize shortcuts</div><div class="sb-item-meta"><span>freshell</span><span>3d</span></div></div></div>
</div>
<div class="sb-resume"><button class="sb-resume-btn"><i data-lucide="rotate-ccw" class="icon"></i> Resume session…</button></div>
<div class="sb-footer"><a href="https://github.com/danshapiro/freshell" target="_blank"><span style="color:hsl(39 92% 50%)">★</span> Star on GitHub</a></div>
</aside>

Expand Down Expand Up @@ -1685,6 +1689,7 @@ <h2>Devices</h2>
'.picker-option',
'.sv-search input',
'.sv-refresh',
'.sb-resume-btn',
];

document.addEventListener('click', (e) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,9 @@ Most current-main provider, title, pagination, timestamp, extension, lifecycle,
- PARTIAL (2026-07-18): `test/e2e-browser/specs/restore-sync05.spec.ts` :: "a live terminal pane reconnects quietly after a deliberate server restart, with no user-facing error noise" — asserts zero `role="alert"` elements, no auth-required modal, no plain-text error language, and a genuinely functional post-restart terminal, across a real `server.restart()`; green both projects.
- PARTIAL (2026-07-19, this task): same file's new test :: "a FreshCodex pane reconnects quietly after a deliberate server restart, targeting the same durable session with no user-facing error noise" — closes the CODEX/APP-BOUND restart leg the prior note left open. Reuses `restore-matrix.spec.ts`'s `fake-app-server.mjs` JSON-RPC sidecar fixture and its already-proven `freshAgent.create`/`freshAgent.attach` wire-observable pattern (TERM-02's fix) for session continuity: seeds a real FreshCodex session (genuine server-assigned session id, one live turn confirmed via the fixture's own reply), triggers a deliberate `server.restart()`, then asserts (a) the SAME "quiet" bar as the general leg (zero `role="alert"`, no auth-required modal, no plain-text error language), (b) every post-restart `freshAgent.create`/`freshAgent.attach` targets the ORIGINAL session id (never a fresh/duplicate one), and (c) the resumed pane renders real, non-blank content and settles idle. Green both projects, 2x each (legacy-chromium and rust-chromium, full-file run: 4/4 both times). MISSING: the equivalent-crash/diagnostics-retained leg and the `PW-TAURI-WIN` half of the validation remain explicitly out of scope (per the file's own doc comment) and left to dependent tickets.

- [ ] **SYNC-06 — Session resume-by-id parity: `POST /api/sessions/resolve` + `sessionResolve` feature flag.** The Node server (`server/sessions-router.ts`) resolves pasted session ids/resume commands across claude/codex/opencode/amplifier and gates the sidebar Resume button via the `sessionResolve` flag in `detectFeatureFlags()`; the Rust server intentionally omits the flag (button stays hidden) until it implements the endpoint. See `docs/plans/2026-07-29-resume-session-button.md`.
- **Playwright validation (`PW-RUST`, `PW-TAURI-WIN`):** With the flag declared, the sidebar shows the pinned Resume button; pasting a known session id resumes it in a tab (mirror `test/e2e-browser/specs/resume-button.spec.ts`).

## Final release gates

- [ ] **GATE-01 — Run the unchanged legacy browser suite against both Node and Rust.** No Rust-only skips for a user-visible feature are allowed.
Expand Down
133 changes: 133 additions & 0 deletions docs/plans/2026-07-29-resume-button-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Resume Session Button — freshell left rail

## Summary

Add a **Resume** button to the left rail (Sidebar) that is **always on screen**: it is
rendered in a pinned footer region of the rail, BELOW the infinite-scroll session list's
scroll viewport — NOT as an item inside the scrollable list. It must be visible at every
scroll position (top/middle/bottom) and in every rail presentation where the list is
visible, including the `fullWidth` mobile mode.

Implementation note (verified): the Sidebar root is `h-full flex flex-col` and the list
wrapper is the `flex flex-1 min-h-0` region — the footer is a sibling div AFTER that
wrapper. Give it a `data-testid` for tests.

Clicking it opens a Resume dialog: an agent picker (advisory — see resolution engine) and
a paste field for a resume string. freshell resolves the string to a concrete
(agent, full session id, cwd, sessionType) tuple and resumes that session in a tab.

## Supported agents

The CLI providers freshell already supports: `claude`, `codex`, `opencode`, `amplifier`
(`shared/coding-cli-defaults.ts` DEFAULT_ENABLED_CLI_PROVIDERS). The picker lists these;
detection may resolve to any of them regardless of picker state.

## Target backend

The resolve capability must live in the server that actually serves the app in the
default `dev`/`start` path — per current `package.json` that is the **Node server**
(`server/index.ts`), which has its own session indexer
(`server/coding-cli/session-indexer.ts`). The Rust server's `IndexExistenceProbe`
(`crates/freshell-server/src/existence.rs`) is exact-match-only, WS/reconcile-internal,
and is NOT this feature's API. The planner must confirm which server serves the sidebar
in supported deployments and implement there; parity in the other server is out of scope
for this feature.

**New work, stated explicitly:** a resolve endpoint (e.g. `POST /api/sessions/resolve`
or equivalent) that scans the session index across ALL providers at once for exact and
prefix matches and returns per-candidate metadata. Nothing existing does prefix search;
the existing exact-id fallbacks (claude transcript locator, opencode by-id DB query) may
be reused for exact-id misses. Prefix matching may be limited to indexed sessions —
if extending prefix search to fallback stores is non-trivial, document
"prefix only matches indexed sessions" as an accepted limitation rather than building
new store scanners.

## Extremely permissive input parsing

Accept arbitrary pasted text and extract candidate session ids. Token shapes:

- Full UUIDs (any version, any case, `8-4-4-4-12`).
- Opencode ids: `ses_` + 26 base62 chars (e.g. `ses_root0000000000000000000000`) —
first-class token shape; also accept other known `xxx_`-prefixed id families.
- Short hex tokens as id **prefixes** (amplifier surfaces short ids like `417e8345`):
require **≥8 hex chars containing at least one digit** (avoids matching English words
like "decade"/"facade"), up to 32 chars.
- Strip surrounding noise: full command lines (`codex resume <id>`,
`claude --resume <id>`, `claude -r <id>`, `amplifier --resume <id>`,
`opencode --session <id>`), quotes, backticks, whitespace/newlines, shell prompts
(`$`, `>`), flags, and ids embedded in longer strings or paths.
- If multiple candidate tokens exist: prefer prefixed ids (`ses_…`) and full UUIDs,
then the longest hex token; try candidates in that order until one resolves.

The parser is a pure, shared function (usable by client for live feedback and by tests).

## Getting the agent RIGHT (hard requirement)

A resume attempted with the wrong agent fails — the resolved agent must be correct.
Strategy: **evidence decides; hints only assist the UI.**

1. **Evidence (decisive):** one resolve call scans the session index snapshot across all
four providers simultaneously (exact match AND prefix match). No per-agent probe
ordering — a single scan answers all agents at once. On an exact-id miss against the
index, reuse the existing exact-id fallbacks (claude transcript locator, opencode
by-id) before concluding absence.
2. **Hints (advisory only, labeled unverified):** explicit agent words in the pasted
text, CLI command shapes, and id-format heuristics (codex ids are *typically* UUIDv7,
claude's v4, short-hex *suggests* amplifier — but none of this is guaranteed; codex
ids can be non-UUID, and amplifier ids are full UUIDs whose short form is a prefix).
Hints are used ONLY to pre-fill the agent picker and as the default agent for the
"resume anyway" escape hatch. The user's picker choice is itself a hint — store
evidence overrides it.
3. **Resolve responses carry full resume metadata** for each match: provider, full
session id, `cwd`, `sessionType`, title/first-message snippet, last-modified — the
existing tab-resume path requires sessionId, provider, sessionType, and cwd, not just
(agent, id).
4. **Outcomes:**
- Exactly one match → resume with that provider (even if the picker disagreed); show
a small non-blocking note ("found in codex").
- Multiple matches (across providers, or a prefix matching several sessions) → show a
disambiguation list (capped, e.g. 20, most-recent first) with the metadata above;
one click resumes.
- Zero matches with the index **ready** → clear inline error, input preserved.
Offer a "resume anyway" escape hatch: attempts a verbatim resume with the
picker-selected (or hint-default) agent, with cwd defaulting to the user's home
directory and shown/editable before launch.
- Index **warming / provider unavailable / unknown** → this is NOT "not found":
show a loading/retry state and re-resolve when the index is ready.
5. A tab is only created once a concrete (provider, full id, cwd, sessionType) tuple is
in hand (except the explicit "resume anyway" path).

## Resume in a tab

Reuse the existing tab-resume mechanics (the same path the sidebar uses when opening a
session: `openSessionTab`/`buildResumeContent` and `session-type-utils`). Follow the
sidebar's existing dedup convention: if the session is already open in a pane
(`findPaneForSession`), focus that pane instead of spawning a duplicate; otherwise open
a **new tab**, focused, running the correct agent with the FULL session id.

## Acceptance examples (must be tests)

| Pasted input | Expected |
|---|---|
| `417e8345` | prefix-match in amplifier store → resume amplifier session |
| `codex resume 019fac27-69d7-78a0-b972-b339d551042e` | codex (hint + store evidence agree) |
| `ed2afda6-a340-443e-ba60-024a1b3554b4` | no hint; resolve finds it under claude → resume claude |
| `opencode --session ses_root0000000000000000000000` | opencode (prefixed id shape) |
| bare `ses_…` id with picker set to claude | evidence wins → opencode, with a note |
| ` "claude --resume ed2afda6-…" ` with picker set to codex | evidence wins → claude, with a note |
| prefix matching multiple sessions | disambiguation list, capped, most-recent first |
| valid id, index still warming | loading/retry state, NOT "not found" |
| garbage with no id-like token | inline error, no tab created |
| session already open in a pane | focuses existing pane, no duplicate tab |

## Non-functional requirements

- Parser: pure function, table-driven unit tests including the cases above plus
adversarial noise (multi-line paste, ANSI codes, trailing punctuation, `decade`-style
hex-looking words must NOT match).
- Resolve endpoint: tested against fixture session stores/indexes for all four agents
(exact, prefix, ambiguous, missing, warming).
- UI: pinned placement verified at top/middle/bottom scroll and in `fullWidth` mobile
mode; keyboard accessible; paste-then-Enter fast path (auto-resolve on paste).
- Follow repo conventions (AGENTS.md / CLAUDE.md), match existing Sidebar patterns,
TDD where the repo requires it.
Loading
Loading