Skip to content

feat(artifacts): render a shared conversation's artifacts for its recipient - #973

Merged
philmerrell merged 1 commit into
developfrom
feature/shared-conversation-artifacts-ui
Sep 6, 2026
Merged

philmerrell merged 1 commit into
developfrom
feature/shared-conversation-artifacts-ui

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

PR-2 of the pair. #971 made the artifacts reachable; this makes them visible. A recipient now sees artifact cards anchored under the same turns the owner sees them under, and opens them read-only.

Frontend only. Depends on #971 for the endpoint — until that merges, the payload carries no artifacts and this renders nothing, which is the current behaviour.

A separate recipient component, not a mode of the owner's

ArtifactCardComponent opens the docked panel and carries download, share, rename and delete. ArtifactPanelComponent adds a version picker and a code view. Every one of those is keyed on something a conversation-share recipient does not have — an owned artifact, or an artifact share id. Bending either into a "read-only mode" means a component whose every action is conditional on a flag, and the failure mode of a missed condition is a visible button that 403s.

So the card and dialog are their own components. What is shared is the layer that should be: ArtifactViewerComponent, which is purely presentational and already served the owner panel and the standalone recipient page through two different mint endpoints. This is the third, and it needed no change to that component — which is the sign the split was drawn in the right place.

Artifacts arrive as an input, not through the state service

MessageListComponent reads artifacts from ArtifactStateService — the owner's live session state, populated by SSE events and owner-scoped hydration, neither of which a recipient has. Feeding it recipient rows would put another user's artifacts into the signal the real session view reads.

So the shared view passes them down, and the list groups them with the same index-anchoring the owner path uses, including the orphan fallback: an artifact with no usable anchor lands in the end strip rather than disappearing. That matters here more than anywhere, since silently disappearing artifacts is the exact failure this whole feature exists to fix.

No code view, deliberately

The source endpoint is keyed on an artifact share id, which a conversation share doesn't have. The toggle is absent rather than present and permanently failing — adding it is a backend change, not a UI one. I built it with the toggle first, then removed it rather than ship a control that can only ever error.

A refused artifact and a missing one get one message: "not part of this share" and "you may not open this share" are the same fact to a recipient, and distinguishing them would describe what the owner has.

Tests

2367 passed (+26). 20 new: the card, the dialog (including the sandbox isolation this third mint path must not weaken, and the superseded-mint race), and the message-list anchoring.

That last one is worth calling out — MessageListComponent had no component spec at all, so the orphan-fallback branch the owner path also depends on was untested until now. It's covered for both.

A verification correction worth recording

Browser-verified at 1080px in both themes — but getting light mode right took two attempts. This app drives dark mode with a .dark class on <html> and honours prefers-color-scheme. The Browser pane's colorScheme emulation only moves the second, so with the class still set the "light" screenshot was the app still fully in dark mode. Cards looked broken; they weren't.

Correct check is both levers: clear the class and emulate light. Once done, cards are white with dark text as intended (verified against computed styles, not just the screenshot).

🤖 Generated with Claude Code

…ipient

PR-2 of the pair. #971 made the artifacts reachable; this makes them
visible. A recipient now sees artifact cards anchored under the same
turns the owner sees them under, and opens them read-only.

## A separate recipient component, not a mode of the owner's

`ArtifactCardComponent` opens the docked panel and carries download,
share, rename and delete; `ArtifactPanelComponent` adds a version picker
and a code view. Every one of those is keyed on something a
conversation-share recipient does not have — an owned artifact, or an
artifact share id. Bending either into a "read-only mode" would mean a
component whose every action is conditional on a flag, and the failure
mode of a missed condition is a visible button that 403s.

So the card and dialog are their own thing. What IS shared is the layer
that should be: `ArtifactViewerComponent`, which is purely presentational
and already served the owner panel and the standalone recipient page
through two mint endpoints. This is the third, and it needed no change
to that component — which is the sign the split was drawn in the right
place.

## Artifacts arrive as an input, not through the state service

`MessageListComponent` reads artifacts from `ArtifactStateService`, which
is the OWNER's live session state: populated by SSE events and
owner-scoped hydration, neither of which a recipient has. Feeding it
recipient rows would put another user's artifacts into the signal the
real session view reads. So the shared view passes them down, and the
list groups them with the same index-anchoring logic — including the
orphan fallback, so an artifact with no usable anchor lands in the end
strip rather than disappearing, which is the exact failure this whole
feature exists to fix.

## No code view

The source endpoint is keyed on an artifact share id, which a
conversation share does not have. The toggle is therefore absent rather
than present and permanently failing. Adding it is a backend change, not
a UI one.

A refused artifact and a missing one get one message: "not part of this
share" and "you may not open this share" are the same fact to a
recipient, and telling them apart would describe what the owner has.

SPA suite: 2367 passed (+26). 20 new tests: card, dialog (including the
sandbox isolation the third mint path must not weaken, and the
superseded-mint race), and the message-list anchoring — which had no
component spec at all before, so the orphan-fallback branch the owner
path also relies on is now covered.

Verified in the browser against the compiled stylesheet at 1080px in
both themes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@philmerrell
philmerrell merged commit d7459e2 into develop Sep 6, 2026
4 checks passed
@philmerrell
philmerrell deleted the feature/shared-conversation-artifacts-ui branch September 6, 2026 01:11
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