Skip to content

feat(preview): open ~/, relative and hard-wrapped paths; folder grid - #604

Open
Randalix wants to merge 8 commits into
Ark0N:masterfrom
Randalix:feat/preview-folders-upstream
Open

Randalix wants to merge 8 commits into
Ark0N:masterfrom
Randalix:feat/preview-folders-upstream

Conversation

@Randalix

Copy link
Copy Markdown
Contributor

Closes #603.

Two commits, one PR: the folder grid's links are the third stage of findFilePathLinks(), which the first commit introduces, so the second cannot land alone.

1. feat(preview): open ~/, relative and line-broken file paths

  • constants.js: RELATIVE_FILE_PATH_LINK_PATTERN (same extension list as the rooted one, pinned equal), findFilePathLinks() (rooted first, then relative; no overlap, never inside an http(s) URL), resolveLinkedFilePath() (relative → workingDir, ./.. collapsed; absolute and ~/ unchanged), terminalPathLine().
  • Both consumers use it: terminal provider (terminal-ui.js) and response viewer (app.js). Only links the linkifier created (data-linkified) are resolved on click — a rendered markdown document's own rebased links keep their old behaviour (found in review: otherwise a ../ link in a document would have gone through the attachment route instead of bouncing off the workspace route).
  • ~/ is routed to the attachment route (_isExternalPreviewPath); registerExternalAttachment expands it locally with homedir(), the remote probe script with $HOME; the guard then runs on the resolved path exactly as before.
  • Wrap join rules (terminalPathLine, link provider only — touch selection keeps terminalLogicalLine): join the next row when the previous one ends in a path token with / and no extension and the join reaches an extension; never join a row that starts a new rooted/~/ path; a 3+ row chain is kept only if a later row completes it; the broken row must be at least as long as the continuing one (keeps cd /mnt/foo + a longer next line apart).

2. feat(preview): folder grid — browse every previewable file of a folder

  • src/folder-listing.ts: listFolder() — absolute or ~/; local realpath + readdir, remote remoteListFolder() (one ssh call through runWithRemoteSshLimit; POSIX sh, NUL-separated records, cd -P for symlinks, GNU/BSD stat); guard on the folder and on every listed file; hidden entries and unsupported types left out; newest first; capped at 1000 entries. folderGrants: own registry (fld_<uuid>, same folder → same id, 32 per session, cleared with the session).
  • Routes (file-routes.ts): POST /api/sessions/:id/folders; GET …/folders/:folderId/files/:name/raw|preview|thumbnail. :name must be a bare supported file name (no /, \, ., .., NUL); then the joined path goes through resolveServableAttachmentPath. The attachment raw route's body is now the shared serveServableRaw() (unchanged behaviour: range, xlsx cap, 502 on ssh failure). Remote: office preview and thumbnail answer 400, as for attachments.
  • Remote listing distinguishes "missing" (404) from "is a file" (400 Not a folder): the client falls back to the file preview on Not a folder, so a clicked extensionless file still opens (found in review).
  • Client (panels-ui.js, index.html, styles.css): grid overlay at z 5090 (below the preview's 5100, above the response viewer's 5000), with the foldable hinge rule like every centred overlay. Tiles: images lazy from raw, PDF/Office from thumbnail (type label where the server has none), others a type label. A tile's preview renders from the folder routes (openFilePreview(..., { folderFile })), so paging never registers attachments. ‹ › and ←/→ page (wrapping); Esc closes the preview, then the grid; keys are caught in the capture phase so they never reach xterm. "Show folder" in any preview whose folder is known (not for attachment cards that only carry a bare name).

Docs

CLAUDE.md (File-path links key pattern), docs/architecture-invariants.md (two new paragraphs under File-path links), docs/remote-sessions.md (route table), docs/security-architecture.md (folder grants trust level).

Tests

  • New: test/file-path-link-forms.test.ts (18), test/routes/folder-grid-routes.test.ts (15, including the listing script on a real sh with ~, symlinks, odd names, the cap, file vs missing), test/folder-grid-ui.test.ts (14).
  • Extended: link-provider-regex (both new patterns in the linear-time check, extension lists equal), response-viewer-file-links; foldable-layout (overlay count 7 → 8); webview-loopback-links reads a 3500-character window instead of 2500 (the extra folder branch pushed the URL handler to char 2498; the order it asserts is unchanged).
  • Full npm test on the branch (rebased on current master, after 1.41.0): 10715 passed, 12 skipped, 0 failed (546 files). tsc, eslint, prettier, check:frontend-syntax, check:public-assets green.

Live verification (my instance, 2026-10-10)

  • Path forms (Playwright/Chromium against the running server, throwaway shell session): a shell printed a relative path, a ~/ path and an absolute path hard-wrapped like Claude Code (first row longer). One link each, the wrapped one across both rows and hoverable from both; click → preview, image loaded (naturalWidth 192) for all three, relative resolved to absolute, ~ through the attachment route.
  • Folder grid: POST …/folders on a 29-file style-frame folder → 29 files, 5 subfolders, newest first; raw of a tile 200 image/png 1.27 MB; ..%2F..%2Fetc%2Fpasswd → 400. Browser: 29 tiles, 28 images loaded after 2.5 s (lazy), tile → preview, → pages to the next file, Esc closes the preview (grid stays), second Esc the grid.

Known limits (deliberate)

  • Tiles load images at full size (the existing thumbnail route passes images through unscaled); lazy, but remote it is two ssh calls per visible image (bounded by the remote ssh limiter).
  • Relative paths resolve against the session's workingDir, not a directory the agent cd'd into.
  • Folder paths containing spaces are cut at the space; a folder path wrapped over lines is not joined; capitalized library names like Node.js underline as relative paths (click → not found).

No changeset.

🤖 Generated with Claude Code

Randalix and others added 3 commits October 10, 2026 21:54
Terminal and response-viewer links only knew rooted absolute paths. Agent
output is full of the other forms: Claude Code echoes an attached image as
`~/repos/x/garage.png`, scripts print `builds/captures/x.png`, and Claude
Code wraps tool output a few columns short of the edge, so a long path
arrives as `.../captures/2` + `026-10-10/x.png` and only its prefix linked.

- constants.js: RELATIVE_FILE_PATH_LINK_PATTERN + findFilePathLinks() (one
  entry point for both consumers; never inside an http(s) URL),
  resolveLinkedFilePath() (relative -> session workingDir, ~/ as is),
  terminalPathLine() (glues a path a program broke over lines; guarded so
  ordinary short lines are never joined).
- terminal-ui.js: provider uses them, no overlapping links, opens the
  resolved path. Touch selection keeps terminalLogicalLine.
- app.js: response viewer links the same forms; only its own links are
  resolved, a markdown document's rebased links stay as they were.
- Server: registerExternalAttachment accepts ~/ (local homedir(); remote
  probe expands $HOME on the host). Guards run on the resolved path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Folder paths in the terminal and the response viewer are now links
(rooted or ~/ without an extension, relative with a trailing slash); a
click opens a grid of the folder's supported files, newest first, with
subfolder chips and a parent button. A tile opens the file preview,
which pages through the folder with ‹ › / ←/→; the preview of any file
gets a "Show folder" button.

Server: POST /api/sessions/:id/folders grants + lists a folder (local
fs or one ssh round trip for a remote case) under the attachment guard;
GET …/folders/:folderId/files/:name/(raw|preview|thumbnail) serves one
file by bare name through resolveServableAttachmentPath, so browser
requests still never carry a path. Grants live in their own small
registry so a big folder cannot evict the session's attachments. The
attachment raw route's body is now the shared serveServableRaw helper.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CLAUDE.md key pattern and architecture-invariants (File-path links) get
the new path forms (`findFilePathLinks`, `~/` expanded server-side,
`terminalPathLine` join rules) and the folder grid (grant + bare-name
routes, own registry, z-order, captured keys); remote-sessions lists the
folder routes for a remote case; security-architecture states the trust
level of a folder grant.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Ark0N

Ark0N commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Thanks @Randalix, this is a really nice piece of work. It makes ~/, relative and hard-wrapped paths clickable in both the terminal and the response viewer, and adds a folder grid so a whole folder of renders can be browsed and paged without filling the attachment registry. The tests are thorough (the listing script running on a real sh is a great touch), the docs moved with the code, and every static check and the full npm test are green here.

Two things need a change before this can merge, both on the server side of the folder grid:

  1. Multi-user mode: a regular user can browse other users' spaces (src/folder-listing.ts:177, src/web/routes/file-routes.ts:2380). I checked it with a route test: with CODEMAN_MULTIUSER=1, user bob posting {"path": "<spaces>/alice/cases"} to POST /api/sessions/<bob's session>/folders gets 200 with folders: ["secret-project"], and GET …/folders/<id>/files/plan.md/raw then returns alice's file. The filesystem path picker was locked down for exactly this case (configuredFilesystemPickerRoots in file-routes.ts, and the "Filesystem path picker" note in CLAUDE.md: a non-admin gets only their own userSpacePath). Please apply the same rule here: in multi-user mode, a non-admin's folder must resolve inside their own userSpacePath(user.username) (or the session's workingDir), else 403, checked on the resolved folder in listFolder (pass the request's user in) and again on the resolved file path in resolveFolderFile, plus a route test for it next to the existing multi-user picker tests in test/routes/file-routes.test.ts. (The single-file attachment route has the same cross-user gap today; that one predates this PR and is out of scope here.)

  2. Bounded probing of user-chosen paths (src/folder-listing.ts:166). listFolder resolves the requested folder with realpathSync, then fs.stat, fs.readdir and up to 1000 fs.stat calls with no bound. CLAUDE.md's fix(cases): bound path probes for linked workspaces and session creation, so an unreachable mount cannot freeze the server #516 rule ("User-chosen paths are probed BOUNDED, never with existsSync/statSync") exists because a folder on a network mount that stopped answering freezes the whole server on a sync call and pins a threadpool worker on an unbounded async one, and /mnt/... folders are exactly what the grid is for. Please probe the folder with probePathKind(expanded, { pastCap: true }) from src/utils/bounded-path-probe.ts first, answer unknown with an error built by describeUnknownPath() (never as 404), and use the async fs.realpath in place of realpathSync. The fs.stat(filePath) pre-check in resolveFolderFile (src/web/routes/file-routes.ts:2435) can use probePathKind too.

Smaller things, fine to fold into the same push:

  • FOLDER_PATH_LINK_PATTERN (src/web/public/constants.js:2062) admits : as a boundary in front of a path but also keeps it inside the path body, so the :// lookahead rescans the whole token from every colon: 'a:'.repeat(16000) + '//' takes about 300 ms and doubling the length quadruples it (a long text node in the response viewer would freeze the tab). The same thing makes export PATH=/home/u/bin:/usr/bin link /home/u/bin:/usr/bin as one folder. Ending the folder body at : fixes both.
  • listFolder lists the local folder (readdir plus a stat per entry) before it runs the blocklist and confineToWorkspace check (src/folder-listing.ts:172-182). Running the guard on the realpath result first means a blocked or out-of-workspace folder is never read at all.
  • Paging with ←/→ through text files can show the previous file's text under the next file's title: the text branch of openFilePreview awaits its fetch (src/web/public/panels-ui.js:4211) and writes the result without checking that it is still the current preview. A load counter that drops stale responses would close it; the same applies to two quick openFolderGrid calls (panels-ui.js:4338).
  • validFolderFileName (src/folder-listing.ts:118) accepts names starting with ., so .anything.png in a granted folder can be fetched by name although the listing hides it. Refusing a leading dot keeps the routes in line with the listing.
  • The remote listing matches *.png|*.PNG only (src/remote-files.ts:307), so shot.Png shows up locally but not on a remote host.
  • Please add the folder grid (z-index: 5090) to the "Z-index layers" list in CLAUDE.md.

Once 1 and 2 are in, I am happy to merge. If you would rather I take the smaller items at merge time, say so and I will.

… smaller items

- Multi-user: a non-admin's folder must resolve inside their own userSpacePath
  or the session's workingDir (folderAllowedForUser, the path picker's rule),
  checked on the resolved folder before it is read and again on each served
  file after symlink resolution; 403 otherwise.
- listFolder probes the local folder with probePathKind({ pastCap: true })
  first (unknown -> 503 via describeUnknownPath, never 404), then async
  fs.realpath; resolveFolderFile uses probePathKind instead of fs.stat.
- The guard runs on the realpath before readdir.
- FOLDER_PATH_LINK_PATTERN ends a folder at ':' (quadratic `://` rescan on
  colon runs; PATH=/a:/b linked as one folder).
- Load counters drop stale preview / folder-grid responses; closing the grid
  invalidates a listing still in flight.
- validFolderFileName refuses a leading dot; the remote listing matches
  extensions in any case.
- Docs: folder grid in the z-index lists; multi-user rule and bounded probe in
  the invariants and security docs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Randalix

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review — all of it is in 3857fba0, including the smaller items.

1. Multi-user. New folderAllowedForUser() (src/folder-listing.ts), the picker's rule: in multi-user mode a non-admin's folder must resolve inside userSpacePath(user.username) or the session's workingDir, else 403 Folder is outside your space. It runs on the resolved folder in listFolder (the route passes getAuthUser(req)), before the folder is read, and again in resolveFolderFile on the resolved file (servable.path), so a symlink inside an own folder that points into another space is refused too (403 File is outside your space). For a remote session only the workingDir applies (the user space is a local path), checked like confineToWorkspace is there. Tests next to the picker ones in test/routes/file-routes.test.ts: bob → alice's space 403 and never read, bob → own space 200, bob's file that resolves into alice's space 403 (nothing streamed), admin 200.

2. Bounded probing. listFolder probes with probePathKind(expanded, { pastCap: true }) first: unknown → 503 with describeUnknownPath('Folder', …), absent → 404, a file → 400 Not a folder; then async fs.realpath. resolveFolderFile uses probePathKind instead of the fs.stat pre-check (unknown → 503). Tests: both 503s, and the folder is not read.

Smaller items

  • FOLDER_PATH_LINK_PATTERN: : ends the folder body (and the lookahead's class). 'a:'.repeat(16000) + '//' now takes 2 ms here (64k: 6 ms); PATH=/home/u/bin:/usr/bin links /home/u/bin only. Killer line added to link-provider-regex, plus a timing case in folder-grid-ui.
  • The guard (blocklist, confineToWorkspace, multi-user) runs on the realpath before readdir; a blocked folder is never read (test). The remote listing still guards after its one ssh call, since listing and resolving come back together; nothing of a refused listing leaves listFolder.
  • Load counters: openFilePreview drops a stale answer after every await (not only the folder text branch: the external-registration, SVG and file-content branches had the same shape), openFolderGrid renders only the newest request, and closeFolderGrid invalidates one still in flight so a late listing cannot reopen a closed grid. Tests for both.
  • validFolderFileName refuses a leading dot.
  • Remote listing matches any case (*.[pP][nN][gG]); shot.Png added to the real-sh test.
  • Folder grid (5090) added to the Z-index lists in CLAUDE.md and docs/architecture-invariants.md; the invariants and security docs describe the multi-user rule and the bounded probe.

Full npm test: 546 files, 10725 passed, 12 skipped, 0 failed. tsc, eslint, prettier (changed code), check:frontend-syntax green. Every new test fails on the previous head, except the added link-provider-regex killer line: on its own it stays inside that test's shared 500 ms budget there, and the folder-grid-ui timing case is the one that catches the old pattern.

@Ark0N

Ark0N commented Oct 11, 2026

Copy link
Copy Markdown
Owner

Thanks again @Randalix, and thanks for turning the first round around so fast. This PR makes ~/, relative and hard-wrapped paths clickable in the terminal and the response viewer, and adds a folder grid for browsing a whole folder of renders. I checked every item from the last review at 3857fba0 and all of them are in: the multi-user rule, the bounded probe, the : fix in the folder pattern, guard-before-read, the load counters, the leading-dot refusal, the case-insensitive remote glob and the z-index lists. All static checks and the full npm test (10725 passed) are green here.

Two things need a change before merge, both found by running the shipped code:

  1. The folder file routes do not check the resolved file's extension or kind (src/web/routes/file-routes.ts:2470, in resolveFolderFile). registerExternalAttachment refuses a symlink whose real target has an unsupported extension, or is not a regular file (src/attachment-registry.ts:390). The folder routes only check the requested name, and probePathKind reports 'file' for anything that is not a directory. With a route test, in a granted folder:

    • shot.png -> ../secret.bin and notes.txt -> ../history are served with 200, while POST /attachments on the same paths answers 400 "Unsupported attachment type".
    • A direct GET for a FIFO named pipe.txt, or for zero.txt -> /dev/zero, never answers. The FIFO open holds a libuv worker until a writer shows up.

    folderAllowedForUser still keeps users inside their own space, so this is not a cross-user read. It is, though, the extension allowlist CLAUDE.md calls the attachment security gate. Please refuse, right after resolveServableAttachmentPath:

    • when isSupportedAttachmentExtension(extname(servable.path)) is false (400, same message as registration);
    • when the target is not a regular file (locally (await fs.stat(servable.path)).isFile(), remotely servable.probe.kind === 'file').

    Add route tests for a symlink to an unsupported extension and a symlink to /dev/zero.

  2. The wrap join glues separate entries of a path listing (src/web/public/constants.js:2237, pathContinuation). A next row that starts a new rooted or ~/ path is refused, but a relative one is not. So an extensionless entry followed by an equal-or-shorter entry with an extension is joined: src/components + src/index.ts links as src/componentssrc/index.ts, and the real src/index.ts link is gone. Running the shipped terminalPathLine over this repo:

    • git ls-files: 8 false joins. One spans 6 rows, because .jsonl is not in the extension list.
    • find src docs scripts: 10 false joins.

    One rule that worked in a quick prototype: treat a head as a new entry when it starts with ./ or ../, or when it contains a / and repeats the broken token's first path segment. That brought the two listings to 1 and 0 false joins and kept your …/captures/2 + 026-10-10/ghost1b_gameplay.png case joining. Use whatever rule you prefer, but please add a listing-shaped regression test.

Two smaller bugs, ideally in the same push:

  • File names over 100 characters cannot be fetched (src/web/routes/file-routes.ts:2479). Fastify's router caps a path parameter at 100 characters by default (maxParamLength), so a 101-character name answers 404 "Route … not found" on raw, preview and thumbnail. The listing still shows the file (validFolderFileName allows 255). Moving the name into the query string, or raising maxParamLength on the Fastify instance, fixes it. Please add a test with a 150-character name.
  • ~/ in a Docker case resolves to the host's home (src/attachment-registry.ts:351, src/folder-listing.ts:215). Docker sessions are local for the file routes, so a ~/x.png printed inside the container opens the host's ~/x.png (the container home is a separate, seeded directory). Please answer 400 for ~/ when the session has docker set, in both the attachment POST and POST …/folders.

Nits, no action needed for merge:

  • src/folder-listing.ts:264: the 1000-entry cap is applied in readdir order, before the newest-first sort, so a very large folder may not show its newest files.
  • The overlap check in findFilePathLinks is quadratic: 20,000 relative paths in one text node take about 640 ms.
  • github.com/owner/repo/blob/master/README.md without a scheme links as a relative path.

Once 1 and 2 are in, I am happy to merge. If you would rather I take the two smaller bugs at merge time, say so and I will.

Randalix and others added 3 commits October 12, 2026 12:04
…gate; accept long names

Folder routes only looked at the requested name, so `shot.png -> ../secret.bin`
and `notes.txt -> ../history` were served (registration answers 400 for both),
and a FIFO or device behind a supported name parked a worker on an open that
never returned. After resolving, the target's extension must be supported and
it must be a regular file (locally fs.stat, remotely the probe kind).

A 101..255-character file name answered 404 "Route not found": Fastify caps a
path parameter at 100 characters. The server now sets routerOptions.maxParamLength
(MAX_ROUTE_PARAM_LENGTH, shared with the route test harness) and a wiring guard
pins it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`git ls-files`, `find` and `ls -R` print one path per row. An extensionless
entry followed by an equal-or-shorter one looked like a path broken short of
the edge, so `src/components` + `src/index.ts` linked `src/componentssrc/index.ts`
and the real link was gone. Measured with the shipped terminalPathLine over this
repo: `git ls-files` 8 false joins (one over 6 rows), `find src docs scripts` 5.

A next row is now a new entry when it opens with `./` or `../`, or has a `/` and
opens on the same top directory the broken token did. A wrapped path continues
inside a name, so the captures/2 + 026-10-10/... case still joins. After the
change: 1 and 0 (`docker/server.Dockerfile` + `docs/SPEEDRUN.md`, a broken token
with no extension that no row-shape rule can tell from a wrap).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A Docker case is a local session for the file routes, but `~` inside the
container is the container's own seeded home. Expanding `~/x.png` on the host
opened a different file than the one the agent printed. The attachment POST
and POST …/folders now answer 400 for `~/` when the session has `docker` set
(absolute paths are unaffected: the workspace is a bind mount).

Docs: the resolved-target gate, the route-parameter limit, the listing join
rule and the Docker `~/` refusal in architecture-invariants.md,
security-architecture.md and CLAUDE.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Randalix

Copy link
Copy Markdown
Contributor Author

Thanks, all four reproduce here and are fixed in 6bffc048, 6bfc1b88 and 3c08971f, including the two smaller bugs.

1. Resolved target. After resolveServableAttachmentPath, resolveFolderFile checks the extension of the resolved path (isSupportedAttachmentExtension, 400 "Unsupported attachment type") and that it is a regular file (locally fs.stat, remotely probe.kind === 'file', 400 "Attachment path is not a file"). Route tests: shot.png -> secret.bin, notes.txt -> history (no extension), a device node and a FIFO behind a supported name, the two remote cases, and a control that a symlink to a supported regular file is still served. One deviation: the device test links to /dev/null, not /dev/zero, so a missing guard ends the stream instead of filling the test's memory. The FIFO test keeps both ends open and races a 2 s timer, so a hang is reported instead of parking a worker.

2. Listing join. Your rule, in pathContinuation: a next row is a new entry when it starts with ./ or ../, or has a / and opens on the same top directory the broken token did. Measured with the shipped terminalPathLine over this checkout at 120 columns: git ls-files 8 -> 1 false joins (longest chain 6 -> 2 rows), find src docs scripts 5 -> 0. The one left is docker/server.Dockerfile followed by docs/SPEEDRUN.md: no extension, different top directory, same shape as a wrap. I don't see a row-shape rule that tells them apart, so it stays. The regression test is a 12-row listing (including ./, ../, .gitignore, .jsonl), and captures/2 + 026-10-10/... still joins.

3. Long names. I took the second option: routerOptions.maxParamLength = 4096 on both Fastify() instances, from one constant in route-helpers.ts. It is the raw, percent-encoded length, so 255 four-byte characters fit. Moving the name into the query would have changed the URL in about 25 test assertions, two client builders and the docs. The route harness uses the same constant, and a small wiring test reads server.ts because the harness builds its own instance. The test uses a 150-character name.

4. Docker. ~/ answers 400 for a Docker case in both the attachment POST and POST .../folders. Absolute paths are unchanged.

Docs moved with it (architecture-invariants, security-architecture, CLAUDE.md). Full npm test: 546 files passed, 1 skipped; 10740 tests passed, 12 skipped, 0 failed. tsc, eslint on the changed server files, prettier on the changed code and check:frontend-syntax are green. Each new behaviour test fails on 3857fba0. Your three nits are untouched.

sshd runs the command in the remote user's login shell. On Hufflepuff cd is
an alias that echoes the directory, so the listing script captured the folder
path twice and zsh aborted on the glob: every folder grid over ssh failed
with "remote host unreachable: no matches found". Wrap the script in sh -c,
as the transcript read already does.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Randalix

Copy link
Copy Markdown
Contributor Author

One more commit on the branch (5b8c738), found while running the folder grid on an SSH case: the remote listing failed on a host whose login shell makes cd print the directory (an alias such as cd=folderprompt).

r=$(cd -P "$d" && pwd -P) came back as the path twice, the glob over "$r"/* then aborted under zsh, and every remote folder answered "remote host … unreachable: no matches found". File previews on the same case were fine, since they never go through that script.

The listing script now runs under an explicit sh -c, like the transcript read already does. The new test runs the script through a bash whose cd prints the directory: the parsed dir was the path twice before, and is the plain path now.

Local runs on this commit: folder-grid-routes, remote-files, file-routes-remote and file-path-link-forms, 118 tests, plus tsc, eslint and prettier on the two touched files. I did not repeat the full suite for this one commit; CI will. Confirmed live on a Hufflepuff case: workspace, a subfolder and ~/ list again.

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.

File preview: ~/, relative and hard-wrapped paths are not clickable, and there is no way to browse a folder of renders

2 participants