Summary
Two intentional deviations from issue #2779's acceptance criteria — shipped as follow-up work to keep PR #3017 reviewable while parent issue #1535 closes.
Problem
#3017 landed ArtifactCard + Tauri download command + backend ArtifactReady/ArtifactFailed events, but two of the original AC items from #2779 were intentionally deferred:
-
AC#3 — native save-file dialog is not wired. The tauri-plugin-dialog crate currently conflicts with the workspace's pinned schemars version, so the renderer can't open a SaveDialog to let the user pick a target path. The shipped behaviour copies the artifact into ~/Downloads/ and reveals it in the OS file manager, which works but is not what AC#3 specified.
-
AC#1 — ArtifactCard in-progress state only renders once the Rust core publishes DomainEvent::ArtifactReady/Failed. There is no spinner during the actual generate_presentation tool execution, so users see no UI between hitting send and the card appearing. The ChatToolCallEvent already fires when the tool dispatches; the card should subscribe to it and render an in-progress state keyed on the future artifact_id.
Spotted by @graycyrus in #3017 (review).
Solution (optional)
For AC#3 — save-file dialog:
- Upgrade
tauri-plugin-dialog to a version compatible with the workspace's schemars, OR vendor a minimal save-file-dialog command in the Tauri shell that bypasses the plugin entirely (rfd crate is a candidate — already used by the openhuman shell elsewhere).
- Wire
ArtifactCard's "Download" button to invoke the dialog command; fall back to the current Downloads+reveal pattern when the user cancels or the dialog is unavailable on the platform.
For AC#1 — in-progress state:
- Extend
ChatToolCallEvent to carry the artifact_id when the dispatching tool is generate_presentation (or surface it via a sibling ArtifactPending domain event from create_artifact at tool start).
- Subscribe
ArtifactCard to the new event so it renders a "Generating..." skeleton between dispatch and ArtifactReady.
- Failure path:
ArtifactFailed already flips the card to retry-hint; the in-progress skeleton just needs to hand off cleanly.
Acceptance criteria
Related
Additional follow-up — Retry button wiring (added 2026-06-02)
Spotted by @graycyrus on PR #3017: ArtifactCard's onRetry prop is optional (declared at ArtifactCard.tsx:33) but never passed at the call site in Conversations.tsx:2162. That means on a failed artifact today the Retry button does not render — the card is a no-op surface for the failed state.
Real retry needs one of:
- Cheap path: a
removeArtifact({threadId, artifactId}) reducer + onRetry={(id) => dispatch(removeArtifact({threadId, id}))} at the call site. Clears the failed card so the user can re-prompt the agent. Not strictly "retry" semantically (it doesn't re-run the original tool call), but unblocks the user.
- Full path: persist the originating
ChatToolCallEvent args + add a retryArtifactGeneration(threadId, artifactId) action that re-dispatches the same tool call. Requires storing tool-call args keyed on artifact_id at dispatch time, plus an RPC re-dispatch path.
Either is out of scope for #3017's merge gate but should land before parent #1535 closes so the failed-artifact UX isn't a dead end.
Updated acceptance criteria
Summary
Two intentional deviations from issue #2779's acceptance criteria — shipped as follow-up work to keep PR #3017 reviewable while parent issue #1535 closes.
Problem
#3017 landed
ArtifactCard+ Tauri download command + backendArtifactReady/ArtifactFailedevents, but two of the original AC items from #2779 were intentionally deferred:AC#3 — native save-file dialog is not wired. The
tauri-plugin-dialogcrate currently conflicts with the workspace's pinnedschemarsversion, so the renderer can't open aSaveDialogto let the user pick a target path. The shipped behaviour copies the artifact into~/Downloads/and reveals it in the OS file manager, which works but is not what AC#3 specified.AC#1 — ArtifactCard in-progress state only renders once the Rust core publishes
DomainEvent::ArtifactReady/Failed. There is no spinner during the actualgenerate_presentationtool execution, so users see no UI between hitting send and the card appearing. TheChatToolCallEventalready fires when the tool dispatches; the card should subscribe to it and render an in-progress state keyed on the future artifact_id.Spotted by @graycyrus in #3017 (review).
Solution (optional)
For AC#3 — save-file dialog:
tauri-plugin-dialogto a version compatible with the workspace'sschemars, OR vendor a minimal save-file-dialog command in the Tauri shell that bypasses the plugin entirely (rfdcrate is a candidate — already used by the openhuman shell elsewhere).ArtifactCard's "Download" button to invoke the dialog command; fall back to the current Downloads+reveal pattern when the user cancels or the dialog is unavailable on the platform.For AC#1 — in-progress state:
ChatToolCallEventto carry the artifact_id when the dispatching tool isgenerate_presentation(or surface it via a siblingArtifactPendingdomain event fromcreate_artifactat tool start).ArtifactCardto the new event so it renders a "Generating..." skeleton between dispatch andArtifactReady.ArtifactFailedalready flips the card to retry-hint; the in-progress skeleton just needs to hand off cleanly.Acceptance criteria
Save Aspicker with the suggested filename pre-filled; user-chosen path receives the file; cancel is a no-op.generate_presentationis dispatched, beforeArtifactReadyarrives..github/workflows/pr-ci.yml).Related
Additional follow-up — Retry button wiring (added 2026-06-02)
Spotted by @graycyrus on PR #3017:
ArtifactCard'sonRetryprop is optional (declared atArtifactCard.tsx:33) but never passed at the call site inConversations.tsx:2162. That means on a failed artifact today the Retry button does not render — the card is a no-op surface for the failed state.Real retry needs one of:
removeArtifact({threadId, artifactId})reducer +onRetry={(id) => dispatch(removeArtifact({threadId, id}))}at the call site. Clears the failed card so the user can re-prompt the agent. Not strictly "retry" semantically (it doesn't re-run the original tool call), but unblocks the user.ChatToolCallEventargs + add aretryArtifactGeneration(threadId, artifactId)action that re-dispatches the same tool call. Requires storing tool-call args keyed on artifact_id at dispatch time, plus an RPC re-dispatch path.Either is out of scope for #3017's merge gate but should land before parent #1535 closes so the failed-artifact UX isn't a dead end.
Updated acceptance criteria
removeArtifactor full-path re-dispatch.