Skip to content

File peek can open the file in a browser tab - #122

Open
karngyan wants to merge 3 commits into
mainfrom
file-peek-open-in-tab
Open

File peek can open the file in a browser tab#122
karngyan wants to merge 3 commits into
mainfrom
file-peek-open-in-tab

Conversation

@karngyan

Copy link
Copy Markdown
Contributor

What

Clicking a file path in the terminal opens the peek modal. This adds a way out of the modal: an Open in new tab button in its header that opens the same file as a full page in a new browser tab, while the modal stays open.

How

  • web/src/files/contents.tsx: the body of the file viewer (streaming read, cache handshake, loading and error states, text, image and markdown rendering) is now a reusable FileContents component, with the shared FileHeader title row beside it. FileViewer keeps only its dialog chrome and renders FileContents inside, so the modal behaves exactly as before.
  • web/src/router.tsx and web/src/routes/file-peek.tsx: a new full-bleed route at /d/$deviceId/s/$sessionId/file, a sibling of the terminal route and outside the app shell. The file is named by a required path search param the route validates; percent-encoding rides the router's own search serialisation. The client is resolved through the fleet the way the terminal route resolves its own, the body waits for the first socket open because a window.open tab starts cold, and the browser tab is titled with the file's basename while the page is mounted.
  • web/src/files/viewer.tsx: the Open in new tab icon button in the modal header. The address is built with the router's buildLocation, so the search param encoding matches what the route parses, and it opens with window.open(href, '_blank', 'noopener').

The route sits under the /d/$deviceId prefix rather than a bare /s/$sessionId/file because sessions are machine scoped: the fleet resolves a client by device id, and a route without that segment could not reach a remote machine's session.

Tests

Each piece was written test first:

  • contents.test.tsx: FileContents streams and paints chunks, turns refusals into words, and hands the header slot the resolved name, directory and size.
  • file-peek.test.tsx: the route reads the named file over the right machine's socket once it opens, sets and restores document.title, refuses an address with no path, and is registered outside the shell.
  • viewer.test.tsx: the button opens the correct URL in a new tab (spied window.open) and the modal stays open; the 25 existing viewer tests are unchanged and passing.

pnpm vitest run: 90 files, 1847 tests, all passing. pnpm run lint (tsc) clean.

🤖 Generated with Claude Code

karngyan and others added 3 commits August 19, 2026 22:11
The streaming read, the cache handshake and the body rendering move from
FileViewer into files/contents.tsx, together with the shared FileHeader
title row. The viewer keeps only its dialog chrome and renders
FileContents inside, so the modal behaves exactly as before while the
body becomes mountable outside a dialog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/d/$deviceId/s/$sessionId/file renders FileContents full-page, beside
the terminal route and outside the shell, with the file named by a
required path search param the route validates. The client is resolved
through the fleet the way the terminal resolves its own, the body waits
for the first socket open because a window.open tab starts cold, and the
browser tab is titled with the file's basename while the page is up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An open-in-new-tab button in the viewer's title row builds the file
page's address through the router's buildLocation, so the path rides the
route's own search serialisation, and opens it with window.open while
the dialog stays up. The machine segment comes off the tab's address the
way the scratch terminal reads it, with local as the fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant