Phase 2 of file peek. Phase 1 (#55) put the verbs on the wire and in the daemon; this phase puts a client in front of them and makes the feature real: hover a path in terminal output, it underlines if it exists, click it, read the file. Plain text only, zero new dependencies. Design: docs/superpowers/specs/2026-08-11-file-peek-design.md, sections "The browser" and "The viewer". Contract: spec/protocol.md.
Where it stands
The branch feat/file-peek-ui (on origin) holds the first two of eleven planned tasks: the paths.ts candidate matcher and the wrapped-line assembly, both with tests, plus the phase 2 plan doc docs/superpowers/plans/2026-08-11-file-peek-phase-2-clicking-and-reading.md. Start by rebasing that branch onto main and rereading the plan's own corrections (its last commit is "Correct the plan where execution proved it wrong").
Scope
Client methods. FlueClient gains stat(id, paths), read(id, path), and cancel(ref), with promises correlated by reqId, chunk routing by ref for binary frame 0x02, and the abandon dance for a viewer closed before its file answer lands (mirror attach's abandon in web/src/client/client.ts).
The detectLinks seam. web/src/emulator/types.ts gains detectLinks(detector: LinkDetector | null): void where a detector is {find(line), verify(candidates), open(candidate)}. xterm.ts implements it with registerLinkProvider. Nothing above the seam learns that xterm exists.
Wrapped lines. The link provider walks isWrapped backward and forward, assembles the logical line, matches against that, and maps ranges back to screen coordinates. A 90-character path on an 80-column phone spans two buffer rows; getting this wrong ships a feature that silently fails on the device it matters most on.
Candidates. web/src/lib/paths.ts: absolute (/x, ~/x), relative with a separator (./x, ../x, a/b), and bare filenames with an extension (CLAUDE.md). Trailing .,;:)]}"' stripped, optional :line[:col] captured. Generous matching on purpose; verification decides the underline.
Verification on hover. One stat per hovered line carrying every candidate on it. Cache per session and raw text: 30 s for a hit, 2 s for a miss. The short negative lifetime is deliberate: an agent says it is writing src/foo.ts, you hover a moment before it exists, and a long negative cache leaves the path dead for the session.
The viewer. A Radix dialog over the session in web/src/files/. Windowed rendering (monospace means fixed line height, so arithmetic, not a library), chunks paint as they arrive, header shows name, resolved directory, size, and a copy button, truncation is stated, and a :line suffix scrolls to the line and marks it. Plain text; Shiki and images are phase 3.
Tests
paths.test.ts: candidates, punctuation stripping, :line:col, wrapped-line assembly.
client.test.ts: stat, read, and cancel promises; chunk routing by ref; abandon before file.
- Viewer: windowing, the truncation notice.
- e2e (
web/e2e): one relayed read of a real file. The 1 MiB cap and chunk pacing only exist for real over the relay, so that is the only place they are genuinely proven.
- New web sources feed the Tailwind prose scanner through
styles.build.test.ts; see the notes at the top of web/src/styles.css before writing comments.
Commands: cd web && pnpm vitest run && pnpm run lint, go test ./... untouched but run anyway.
Phase 2 of file peek. Phase 1 (#55) put the verbs on the wire and in the daemon; this phase puts a client in front of them and makes the feature real: hover a path in terminal output, it underlines if it exists, click it, read the file. Plain text only, zero new dependencies. Design:
docs/superpowers/specs/2026-08-11-file-peek-design.md, sections "The browser" and "The viewer". Contract:spec/protocol.md.Where it stands
The branch
feat/file-peek-ui(on origin) holds the first two of eleven planned tasks: thepaths.tscandidate matcher and the wrapped-line assembly, both with tests, plus the phase 2 plan docdocs/superpowers/plans/2026-08-11-file-peek-phase-2-clicking-and-reading.md. Start by rebasing that branch ontomainand rereading the plan's own corrections (its last commit is "Correct the plan where execution proved it wrong").Scope
Client methods.
FlueClientgainsstat(id, paths),read(id, path), andcancel(ref), with promises correlated byreqId, chunk routing byreffor binary frame0x02, and the abandon dance for a viewer closed before itsfileanswer lands (mirrorattach'sabandoninweb/src/client/client.ts).The
detectLinksseam.web/src/emulator/types.tsgainsdetectLinks(detector: LinkDetector | null): voidwhere a detector is{find(line), verify(candidates), open(candidate)}.xterm.tsimplements it withregisterLinkProvider. Nothing above the seam learns that xterm exists.Wrapped lines. The link provider walks
isWrappedbackward and forward, assembles the logical line, matches against that, and maps ranges back to screen coordinates. A 90-character path on an 80-column phone spans two buffer rows; getting this wrong ships a feature that silently fails on the device it matters most on.Candidates.
web/src/lib/paths.ts: absolute (/x,~/x), relative with a separator (./x,../x,a/b), and bare filenames with an extension (CLAUDE.md). Trailing.,;:)]}"'stripped, optional:line[:col]captured. Generous matching on purpose; verification decides the underline.Verification on hover. One
statper hovered line carrying every candidate on it. Cache per session and raw text: 30 s for a hit, 2 s for a miss. The short negative lifetime is deliberate: an agent says it is writingsrc/foo.ts, you hover a moment before it exists, and a long negative cache leaves the path dead for the session.The viewer. A Radix dialog over the session in
web/src/files/. Windowed rendering (monospace means fixed line height, so arithmetic, not a library), chunks paint as they arrive, header shows name, resolved directory, size, and a copy button, truncation is stated, and a:linesuffix scrolls to the line and marks it. Plain text; Shiki and images are phase 3.Tests
paths.test.ts: candidates, punctuation stripping,:line:col, wrapped-line assembly.client.test.ts: stat, read, and cancel promises; chunk routing by ref; abandon beforefile.web/e2e): one relayed read of a real file. The 1 MiB cap and chunk pacing only exist for real over the relay, so that is the only place they are genuinely proven.styles.build.test.ts; see the notes at the top ofweb/src/styles.cssbefore writing comments.Commands:
cd web && pnpm vitest run && pnpm run lint,go test ./...untouched but run anyway.