Skip to content

web: open the file an answer names, from the answer - #180

Merged
adityak74 merged 1 commit into
mainfrom
feat/web-clickable-file-links
Sep 5, 2026
Merged

web: open the file an answer names, from the answer#180
adityak74 merged 1 commit into
mainfrom
feat/web-clickable-file-links

Conversation

@adityak74

Copy link
Copy Markdown
Contributor

An answer that ends "the PDF is at k8s-load-balancing.pdf (51 KB)"
already says where the file is. Until now the only way to see it was to
open the Files pane and find that same name in the list by hand.

What this does

web/src/file-links.ts exports one function. It takes a root element,
the set of known artifact paths and a callback, and turns every
reference in that subtree that names a listed file into a button.
Pressing it opens the artifact pane on that file, through the same two
calls investigate_done already uses.

main.ts runs the pass when an assistant message is drawn, on the
reopen path in openSession and on the live path where a streamed
message finishes, and again whenever a listing arrives: an answer can
name a file before the listing has caught up with it. Running it again
is a no-op on what it already did, because the upgrade replaces the code
span rather than decorating it, so there is nothing left for a second
pass to find.

The listing is the only thing that decides what is a file

A code span the listing does not know stays a code span, so ClusterIP
and NodePort in an answer about Kubernetes are still text. A name the
model wrote can only ever reach a file the workspace has already
reported, which is the containment as well as the accuracy: no reference
in an answer leads out of the workspace. A link carrying a scheme is
never captured either, or an external URL whose last segment happened to
match a listed name would stop reaching the site it named.

An exact match is tried first, after a leading ./ is stripped, then
the basename and only when exactly one listed file carries it. Two files
with that basename resolve to nothing and the reference stays plain
text: opening one of them would be a guess, and a guess there opens the
wrong document while looking like it worked.

What is untouched

The pane's own display rules. This decides which path to open and
nothing else, so a .svg or an .html still goes into the sandbox
frame and a .pdf into the viewer frame, exactly as the note at the top
of artifact-view.ts says. Nothing here fetches or inlines a file.

Nothing assembles markup either. The name goes onto the button through
textContent and into dataset, and reaches a URL only through the
existing artifactUrl, which encodes it. The upgraded reference is a
real button, so the keyboard reaches it and a screen reader says what
it is; it keeps the monospace look, gains an underline and a pointer,
and carries a title saying it opens in the file pane.

No .rs file changed and no server change was needed.

Checks

From web/: npm run check, npm test (419 pass, 11 of them new) and
npm run build, all clean.

The 11 new cases cover a listed name becoming a button and opening on
click, an unlisted name left alone, a basename resolving to a full path,
an ambiguous basename left alone, ./name.md, a markdown link opening
the pane instead of navigating, an external link never captured, a
second pass wrapping nothing twice, a name carrying markup landing as
text with no element built from it, and an empty listing changing
nothing.

https://claude.ai/code/session_01PG2vuux5tMsjnZBLWJzMyp

An answer that ends "the PDF is at `report.pdf`" already says where the
file is, and the only way to see it was to open the Files pane and find
that name in the list by hand.

`web/src/file-links.ts` turns an inline code span, or a relative
markdown link, that names a listed file into a button, and pressing it
opens that file in the artifact pane through the same two calls an
investigate run's draft goes through. `main.ts` runs the pass when an
answer is drawn, on the replay path and the live one, and again whenever
a listing arrives, since an answer can name a file before the listing
has caught up.

The listing is the only thing that decides what is a file. A code span
it does not know stays a code span, so `ClusterIP` in an answer about
Kubernetes is still text, and a name the model wrote can only ever reach
a file the workspace has already reported. An exact match is tried
first, then the basename and only when exactly one listed file carries
it: two files with that name resolve to nothing, because a guess there
opens the wrong document while looking like it worked. A link with a
scheme is never captured either.

The pane's own display rules are untouched. This decides which path to
open and nothing else, so a `.svg` or a `.html` still goes into the
sandbox frame and a `.pdf` into the viewer frame. Nothing here fetches a
file and nothing assembles markup: the name lands through `textContent`
and `dataset`, and reaches a URL only through `artifactUrl`. Replacing
the code span with the button rather than decorating it is what makes a
second pass a no-op.

`npm run check`, `npm test` (419 pass, 11 new) and `npm run build` from
`web/`. No Rust changed.

Claude-Session: https://claude.ai/code/session_01PG2vuux5tMsjnZBLWJzMyp
@adityak74
adityak74 merged commit 72b2854 into main Sep 5, 2026
7 checks passed
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