Query() caching with stale-while-revalidate (TanStack Query style): avoid loaders on every return visit

Open
#746 2 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@iambharathpadhu is already working on this.

Since Jul 9, 2026.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
typescript

Research direction

Start by reading the runtime and toolProvider layer mentioned in the issue, then compare the runtime-level cache and middleware approaches with the maintainers before choosing a scope. The request includes caching, invalidation, persistence, and optional retry and optimistic updates, so agree on which behavior to implement first. Done should mean the agreed behavior works for repeated queries and is covered by tests.

Written by the indexing model from the issue text.

Description

question

Hi folks 👋 I've been using OpenUI for a while now and it's genuinely great, but there's one gap that hurts the UX of everything we build with it: there's no caching between Query() and our tools, so users see loading placeholders every single time they navigate away and come back to a page, even when we fetched that exact data seconds ago.

What I'd love to see is TanStack Query style caching with stale-while-revalidate as the default behavior: when a user returns to a page, render the cached result instantly, then re-fetch in the background and update in place. No spinner, no default-value placeholder flash, and the data still ends up fresh.

The existing pieces are a solid foundation. Reactive $variable changes re-fetch dependent queries, the optional refresh-interval argument covers polling, and @Run gives manual control. What's missing is the cache layer itself:

  • Stale-while-revalidate rendering: serve the last known result for a given tool + args immediately, revalidate in the background. This is the big one for us.
  • Stale-time / cache-time control: let us say how long a result is considered fresh, so repeat visits within that window don't hit the backend at all.
  • Request deduplication: multiple components querying the same tool with the same args should share one in-flight request.
  • Declarative invalidation: today the post-mutation pattern is @Run(createResult), @Run(tickets), which means the LLM has to know every query a mutation affects. Tag-based invalidation (mutation invalidates "tickets", every query tagged "tickets" refetches) would be more robust to generation errors.
  • Cache persistence: opt-in persistence to localStorage/IndexedDB, so even a full reload or a returning session starts from cached data instead of spinners.
  • (Stretch) Retry with backoff and optimistic updates on mutations.

Since OpenUI already separates generation from execution, this fits naturally in the runtime/toolProvider layer: zero new tokens from the model, and it could even ship as a toolProvider middleware without touching OpenUI Lang itself.

With this, Queries and Mutations in OpenUI would honestly be a killer feature. I'm very open to contributing the implementation if the maintainers can weigh in on the preferred design (runtime-level cache vs. toolProvider middleware). Thank you!

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.