Mutation results are lost on reload and not shared between renderers, so a completed mutation can be run again

Open
#1,542 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
18/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
react, typescript
Domain
api, frontend

Research direction

The design touches the Renderer and the query manager that createQueryManager builds, in lang-core and react-lang, and the existing initialState and onStateUpdate form-state path is the model to follow. Read how mutation status is stored per statement and where a running call settles. The issue asks for new mutationResults and onMutationResult props with specific race rules, so agree the API and those semantics with the maintainers before coding. Done means the rules are covered by tests and the existing lang-core and react-lang suites still pass.

Written by the indexing model from the issue text.

Description

Is your feature request related to a problem? Please describe.

Mutation results live only inside the Renderer that ran them. When a thread is reloaded, or the same message is shown in a second tab or on another device, every mutation reads as idle again. Take the order card from the actions docs:

approveOrder = Mutation("approve_order", {orderId: "ORD-1042"})
status = approveOrder.status == "success" ? Callout("success", "Approved", "The order is approved.") : null
approveBtn = Button("Approve", Action([@Run(approveOrder), @ToAssistant("I approved order ORD-1042")]), "primary")

The user approves the order. After a reload the "Approved" callout is gone and the "Approve" button is live again, so the order can be approved a second time. Nothing in the API lets the host say "this mutation already succeeded".

Form state has exactly this mechanism: initialState restores it and onStateUpdate reports it (Conversation history › Form values on earlier turns). Mutation results have no equivalent.

Describe the solution you'd like

The same pair for mutation results, on Renderer and on the query manager:

  • mutationResults?: Record<string, MutationResult>: results to restore or mirror by statement ID. The tool is not called; approveOrder.status reads as supplied.
  • onMutationResult?: (statementId, result) => void: reports local loading, success and error so the host can persist or broadcast them. Imported results are not echoed back.
  • QueryManager.setMutationResult(statementId, result) and a createQueryManager(toolProvider, { onMutationResult }) option, so vue-lang, svelte-lang and angular-lang can do the same.

Semantics that matter for correctness:

  • A call running in this renderer settles last. A result arriving from outside does not release its double-submit guard or overwrite it.
  • A restored loading is display only. It never blocks a retry, otherwise a crash mid-call locks the button forever.
  • A result supplied while the response is still streaming applies when its statement registers.
  • An unchanged supplied value never replays over a newer local result, so the host can pass the same persisted map on every render.
  • Payloads are treated as immutable; the host serialises them.

Describe alternatives you've considered

  • Re-running the mutation on load: wrong for anything with side effects.
  • Rewriting the response text before rendering so the callout is static: loses the live binding and does not stop the button.
  • Keeping the status outside OpenUI in a host-rendered overlay: duplicates what the generated UI already expresses.

Additional context

A working implementation with tests, README examples and a changeset is on my fork: https://github.com/fsalomon/openui/commit/c617e72 (lang-core + react-lang, additive, no change for hosts that do not pass the new props). It merges cleanly onto release/1.1; lang-core and react-lang tests and tsc pass there. I will open the PR once the direction is confirmed here.

Dominant language
TypeScript
Stars
10k
Forks
698
Avg merge
1d 6h
Merged PRs (30d)
148

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from thesysdev/openui

All issues in thesysdev/openui

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.