Skip to content

README, docs and examples glow-up + react-rx-best-practices agent skill - #485

Open
stipsan wants to merge 26 commits into
currentfrom
cursor/readme-docs-skill-glowup-050f
Open

README, docs and examples glow-up + react-rx-best-practices agent skill#485
stipsan wants to merge 26 commits into
currentfrom
cursor/readme-docs-skill-glowup-050f

Conversation

@stipsan

@stipsan stipsan commented Aug 3, 2026

Copy link
Copy Markdown
Member

Description

A coordinated glow-up of the project's three public surfaces, plus a new installable agent skill and a forked showcase app.

Closes #38
Closes #56

Agent skill (.agents/skills/react-rx-best-practices/)

A best-practices skill (explicitly not a version-migration skill) for writing and reviewing React components that consume observables. Organized around idioms and anti-patterns: the hand-rolled useState+useEffect+.subscribe() bridge, hook selection with per-hook "when not to use" lists, referential stability (including React Compiler detection and the lazy-initialValue footgun), deriving state in the stream, preferring explicit Subjects over useObservableEvent, and the cases where a manual subscription is the right design. Recipes distilled from sanity-io/sanity#13788/#13799/#13814/#13828 and sanity-io/plugins#1820.

Authoritative source in .agents/skills/ with a .claude/skills symlink, installable via npx skills add sanity-io/react-rx --skill react-rx-best-practices. Advertised in the README, the docs landing page, and a dedicated llms.txt section.

The async-react fork (async-react/ workspace)

The final demo from Rick Hanlon's React Conf 2025 Async React talk, forked from rickhanlonii/async-react via git subtree (full history preserved) and migrated to react-rx + RxJS in place:

  • src/data/index.js: the cache-of-promises becomes cached revalidate-driven streams. getLessons$(tab, search) is read through useObservablePromise + use(), so revalidations stream fresh data into visible lists in place and can never re-trigger a Suspense fallback. prefetchLessons() is preloadObservablePromise raced against 1s, preserving the talk's login semantics. refreshLessons() resolves when fresh data lands so action pending states cover mutation + refetch.
  • src/app/Home.jsx reads the stream in the non-suspending parent and passes the promise to LessonList's use().
  • Everything else stays upstream: router (plus a small base-path normalization so it runs embedded from a subpath), design components, <ViewTransition> animations, network debugger, styling. Excluded from oxlint/oxfmt/knip to stay close to upstream.

The docs page embeds the real Vite build of this workspace (built in the website's predev/prebuild, served from public/, with an SPA rewrite for /async-react-demo/login and a Lessons / Login flow switcher). Sandpack could not run it: its package manager hard-fails Radix/react-aria peer ranges against react@experimental, and the Vite Tailwind plugin doesn't exist in-browser. The page's "Copy page" / llms-full.txt still export the actual workspace sources via the example manifest's new sourceDir support.

README

Leads with what react-rx is for (live streams, time, resilient fetching), a before/after of the hand-rolled bridge, concurrent-React features (identity-coherent deferral, <Activity>, SSR, React Compiler-tested suite), a named Suspense section (answers #56), real-world use cases, an honest comparison table vs observable-hooks / @react-rxjs/core / DIY, a Used in production section (answers #38: Sanity Studio + adoption PRs, the plugin ecosystem, navikt/aksel), and the skill install.

Docs

  • New Comparison page: the bindings table plus an async-state landscape table (Zustand, Jotai, XState, TanStack Query) framed as complementary tools.
  • Guide restructured and copy-paste oriented: mental model, which-hook guidance with "When not to use", a "Working with events" section, and a Patterns section. Conceptual teaching moved into runnable example pages.
  • API reference: every hook gets a "When not to use" section; useObservableEvent documents the preferred explicit-Subject alternative (no @deprecated yet).
  • Copy pass for scannability: no em dashes anywhere (prose, sandbox code comments, demo UI strings), short sentences, bullets with bold lead-ins, no jargon like "load-bearing".

Examples

  • Basics as a runnable guided tour: First steps mirrors the RxJS overview (click counting: hand-wired bridge vs react-rx, flow, values, with streams hoisted to module scope so the diff is the lesson); Basic state mirrors the useState reference examples; Timers & time ago covers Dan Abramov's useInterval vs a switchMap+scan stream, a distinctUntilChanged TimeAgo with visible render counters, and an SSR simulation hydrating with zero mismatches. Superseded examples removed with redirects.
  • Real-world examples: llm-chat (concurrent conversations behind <Activity mode="hidden">, hover preload, in-place token streaming), resilient-fetch (retry/backoff, offline pause/resume, keep last-good data).
  • Rewritten todo-app, search, form-data, suspense, fetch onto explicit Subject handlers; fixed a latent form-data bug (mapswitchMap, the "Saving…/Saved!" states never rendered).
  • Shared sandbox styling: Pico CSS via Sandpack externalResources + narrow-pane tuning; no horizontal scrollbars at the 66/34 layout; styled-components and bezier-easing dropped.
  • Navigation curated with separators (Basics / Real-world / React integration) and prose on every page.

Bugs found and fixed along the way

  • Lazy initialValue loop: a function initialValue returning a fresh object per call loops useSyncExternalStore before the first async emission. Fixed by memoizing; documented in the guide and the skill.
  • SSR-demo rerun hydration mismatch (root-caused with runtime instrumentation): root.unmount() inside an effect cleanup is deferred by React 19 mid-commit, and the deferred commit wiped the container the new run had just filled. Fixed with a per-run keyed container + microtask-deferred unmount.
  • shareReplay({refCount: true}) on a shared clock replaced with plain share() to avoid stale-tick replay.

What to review

  • The fork's migration diff (async-react/src/data/index.js, src/app/Home.jsx, router base normalization) against upstream
  • Comparison-table claims (verified against competitor docs / npm peer ranges)
  • The useObservableEvent steer-away tone (docs-level only, no source deprecation)

Testing

  • pnpm lint, pnpm knip, pnpm test (278 passed, 4 expected-fail by design), pnpm build all green
  • The forked app verified standalone (vite dev) and embedded on the docs page: upstream styling intact, <ViewTransition> animations play, optimistic toggle + in-place revalidation, login flow with prefetch race, network debugger sliders, zero console errors
  • /llms.txt + /llms-full.txt include the skill section and the fork's migrated sources; old example URLs 307-redirect

Demos

LLM chat: token streaming, hover preload skipping the fallback, and hidden-<Activity> chats revealing fully caught up:

llm_chat_streaming_activity_preload_demo.mp4

Offline-resilient fetch: polling pauses offline and resumes immediately on reconnect:

resilient_fetch_offline_pause_resume_demo.mp4

Time ago: distinctUntilChanged render counters, minute-old labels go quiet:

time_ago_distinct_until_changed_render_counts_demo.mp4

New comparison page with both tables

form-data example showing the Saved! state that the fixed switchMap now reaches

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

rickhanlonii and others added 15 commits October 15, 2025 16:39
Fix delay unit lebel from seconds to milliseconds
Fix view transitions from blocking interactions for static list items
A best-practices skill (not a migration skill) for writing and reviewing
React components that consume RxJS observables. Organized around idioms
and anti-patterns: hand-rolled useState+useEffect+subscribe bridges,
hook selection with per-hook "when not to use" lists, referential
stability (incl. React Compiler detection), deriving state in the
stream, preferring explicit Subjects over useObservableEvent, and the
cases where a manual subscription is the right design.

Authoritative source in .agents/skills/ with a .claude/skills symlink,
matching the existing skill layout, so it is installable via
`npx skills add sanity-io/react-rx --skill react-rx-best-practices`.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
- Lead with what react-rx is for: live streams, time, resilient data
  fetching, and how streams become React state that cooperates with
  concurrent rendering
- Add a before/after of the hand-rolled subscription bridge
- Feature list now covers the concurrent-React story: deferred
  identity-coherent updates, <Activity> pre-rendering, SSR, React
  Compiler-tested suite
- Add a named Suspense section explaining useObservablePromise + use()
  and why useObservable is safe to suspend on (closes #56 material)
- Add real-world use cases linking to the docs examples
- Add an honest comparison table vs observable-hooks, @react-rxjs/core
  and DIY useEffect, plus a link to the cross-paradigm comparison page
- Add a Used in production section: Sanity Studio (with adoption PRs),
  the plugin ecosystem, and navikt/aksel (closes #38 material)
- Document the react-rx-best-practices agent skill install
- Add npm weekly downloads badge

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
- index.md: mirror the README pitch — concurrent-React features,
  real-world use cases linking into examples, used-in-production, and
  an Agent skill section (flows into llms-full.txt)
- New comparison page: RxJS-bindings table (observable-hooks,
  @react-rxjs/core, DIY) plus an async-state landscape table (Zustand,
  Jotai, XState, TanStack Query) framed as complementary tools
- guide.md: new mental-model intro, expanded which-hook guidance, a
  'Working with events' section teaching the explicit Subject pattern,
  and a Patterns section (retry via BehaviorSubject, errors as boundary
  or value, referential stability incl. React Compiler)
- reference.mdx: every hook now has a 'When not to use' section;
  useObservableEvent documents the preferred explicit-Subject
  alternative; doc snippets no longer model useObservableEvent
- llms.txt: dedicated Agent skill section with install command,
  registry link, and in-repo source path

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Inject Pico CSS (fluid class-less variant) into every preview iframe
via Sandpack's externalResources — it never touches the sandbox
bundler and keeps example sources free of styling imports and class
names; semantic HTML looks designed automatically, with built-in
light/dark.

A hidden /index.tsx entry renders the example's default export and
imports a hidden /styles.css with narrow-pane tuning: the preview pane
is ~34% of the docs column (~280-360px), so the type scale and spacing
shrink and pre/code/table get overflow guardrails.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Nobody uses observables for bouncing-ball animations in production —
replace that example with the scenarios library consumers actually
have:

- llm-chat: a mock LLM chat where three conversations stream tokens
  concurrently. The mock (llm.ts) is clearly separated from the
  userland code (chat.ts, App.tsx). Visited chats stay mounted behind
  <Activity mode="hidden"> and reveal fully caught up; hovering an
  unvisited chat warms it with preloadObservablePromise; reads go
  through useObservablePromise + use() so tokens stream in place
  without re-suspending
- relative-time: every "42s ago" label derives from one shared timer
  — one interval and one subscription for the whole list, no effects
- resilient-fetch: polling that retries with exponential backoff,
  pauses while offline, resumes immediately on reconnect, and keeps
  the last good value visible throughout

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
- todo-app, search, form-data, suspense, fetch: events now push into
  Subjects from plain handlers instead of useObservableEvent, matching
  the pattern the docs recommend
- form-data: fix the save stream never flattening its inner observable
  (map -> switchMap), which meant the Saving…/Saved! states never
  rendered — and delete the '@todo investigate' cast that bug hid
- suspense: the two comparison panels stack vertically so they fit the
  ~34%-wide preview pane without a horizontal scrollbar
- Drop styled-components from todo-app/form-data in favor of semantic
  HTML styled by the shared Pico base; remove styled-components and
  bezier-easing from the website package and the styledComponents
  compiler flag from next.config.ts

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
- Register the previously-orphaned pages (search, fetch, form-data,
  event-handlers) in the sidebar and group everything with separators:
  Basics / Real-world / React integration
- Every example page now explains what it demonstrates, which hooks it
  uses and why, and what to try — in the style of the suspense page
- Update the suspense page copy for the stacked panel layout

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-rx Ready Ready Preview Aug 4, 2026 10:33am

Request Review

@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 5e7d838

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Per review: teaching content belongs in tinkerable sandboxes, not
static guide snippets — and nobody uses react-rx to render a top-level
of() as-is.

- /examples/simple now mirrors the RxJS overview (rxjs.dev/guide/overview)
  inside React components: counting clicks with a hand-wired
  React+RxJS bridge vs react-rx (scan), controlling flow (throttleTime),
  and transforming values (map + scan over pointer positions)
- New /examples/basic-state mirrors the useState reference examples:
  text field (string), checkbox (boolean, module BehaviorSubject), and
  form (two variables) — teaching the useSyncObservable-for-inputs split
- New /examples/timers: Dan Abramov's canonical useInterval hook
  contrasted with a switchMap+scan interval stream (delay and running
  are stream inputs; count survives swaps); a TimeAgo whose stream emits
  {value, unit} data (formatting stays in JSX via Intl.RelativeTimeFormat)
  and re-renders once a minute after the first minute thanks to
  distinctUntilChanged — with visible render counters; and an SSR
  simulation (renderToString → delayed hydrateRoot) that hydrates with
  zero mismatches because the initial value derives from the serialized
  server clock
- Remove superseded examples and pages (hello-world, simple counter,
  reactive-state, tick, fizz-buzz, sync, event-handlers, relative-time)
  with redirects for the old URLs; slim the guide's mental-model section
  and point it at the runnable tour

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
…go examples

Until a stream's first emission, a function initialValue is invoked on
every snapshot read. The time-ago examples returned a fresh parts
object per call, so useSyncExternalStore saw a changed snapshot on
every check and hit 'Maximum update depth exceeded' (timer(0, ...)
emits asynchronously, so the initialValue path stayed active long
enough to loop). Memoize the initial parts and pass them as plain
values, and document the footgun in the guide's stability section and
the skill's referential-stability reference.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Long-lived sandbox previews left the demos showing '20 minutes ago',
with the interesting moments (the flip to minutes, the hydration
sequence) long past. Both demos can now re-seed themselves: time-ago
rebuilds its messages (remounting the labels resets the render
counters), and the SSR simulation regenerates its payload and reruns
the renderToString -> delayed hydrateRoot cycle.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
shareReplay({refCount: true}) keeps its buffer after the source resets,
so the next subscriber after an idle period receives a stale tick
synchronously. In the SSR demo that stale emission replaced the
deterministic serialized-clock initial value on re-runs, producing a
hydration mismatch. Plain share() has no replay: every fresh subscribe
renders the initialValue until the next real tick, keeping server and
hydration renders byte-identical on every run.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Root-caused with runtime instrumentation: the rerun's effect cleanup
called root.unmount() synchronously while React was committing the
rerun. React 19 defers such unmounts, and the deferred commit then
fired against the shared container the new run had just filled with
fresh static HTML — wiping it (the card vanished for the whole static
phase) and leaving hydrateRoot to hydrate an empty container, which
produced exactly one recoverable hydration error per rerun.

The demo now renders each run into its own keyed container and defers
the previous root's unmount by one microtask, so it tears down against
the old, detached element. Verified in-browser: reruns keep the static
label visible for the full phase and report zero mismatches.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Rick Hanlon's Async React talk demo (rickhanlonii/async-react),
rebuilt on react-rx + RxJS under the React integration section. The
React side keeps the talk's patterns verbatim — transition router,
action-prop design components with useTransition/useOptimistic, CSS
animation-delay pending indicators so fast networks never flash
loading states — while the data layer becomes streams:

- lessons$(tab, search) are revalidate-driven cached observables read
  through useObservablePromise + use(), so revalidation after a
  mutation updates visible lists in place and can never re-trigger a
  Suspense fallback
- prefetchLessons() is preloadObservablePromise raced against a 1s
  timeout, matching the talk's login prefetch semantics
- returning to a seen tab gets stale-while-revalidate from shareReplay
  + startWith
- the network debugger is streams end-to-end: BehaviorSubject delay
  knobs and a scan-based request log

ViewTransition animations are omitted (experimental-only API; this
runs on stable React), noted in the prose.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Per review: with clicks$ and count$ at top level, the useState/useMemo
wrappers disappear and the visible difference between the two sandboxes
is exactly the point being taught — the useEffect bridge vs one
useObservable line. Applied to all four click sandboxes for
consistency, with a note in the prose pointing at Basic state for the
per-component useState(() => new Subject()) style.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
@stipsan
stipsan marked this pull request as ready for review August 4, 2026 06:24
Copilot AI review requested due to automatic review settings August 4, 2026 06:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR delivers a coordinated overhaul of the project’s public-facing surfaces (README + docs + runnable examples) and adds a new installable agent skill for reviewing/writing React components that consume RxJS observables with react-rx.

Changes:

  • Adds a new .agents/skills/react-rx-best-practices/ skill and wires it into the docs/LLMs surfaces.
  • Restructures and expands the docs (new Comparison page, updated Guide/Reference, curated Examples navigation) and refreshes many example implementations (including new “real-world” demos).
  • Updates the website sandbox system (shared Pico CSS styling) and removes unused styling dependencies (styled-components, bezier-easing) plus adds redirects for moved example pages.

Reviewed changes

Copilot reviewed 93 out of 94 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
website/src/examples/use-interval/index.tsx Adds Sandpack entry for the use-interval timers example.
website/src/examples/use-interval/App.tsx Implements Dan Abramov’s canonical useInterval hook example.
website/src/examples/todo-app/TodoApp.example.tsx Refactors todo example toward explicit Subject event inputs + derived list stream.
website/src/examples/time-ago/timeAgo.ts Adds shared clock + timeAgoParts$ derivation for “time ago” labels.
website/src/examples/time-ago/index.tsx Points the route to the new time-ago Sandpack example.
website/src/examples/time-ago/App.tsx Adds the “smarter time-ago” demo UI and memoized initial value handling.
website/src/examples/time-ago-ssr/timeAgo.ts Adds SSR version of the shared clock + timeAgoParts$.
website/src/examples/time-ago-ssr/Message.tsx Adds SSR-safe initial value derivation from serialized server time.
website/src/examples/time-ago-ssr/index.tsx Adds Sandpack entry for the SSR simulation example.
website/src/examples/time-ago-ssr/App.tsx Implements SSR → delayed hydration simulation with mismatch counting and reruns.
website/src/examples/tick/TickExample.tsx Removes superseded tick example entry point.
website/src/examples/tick/TickerWithSubTick.tsx Removes superseded subtick demo component.
website/src/examples/tick/Ticker.tsx Removes superseded delayed tick demo component.
website/src/examples/text-field/index.tsx Points the route to the new text-field Sandpack example.
website/src/examples/text-field/App.tsx Adds controlled text input example using useSyncObservable.
website/src/examples/sync/Sync.example.tsx Removes superseded “sync rendering” example implementation.
website/src/examples/suspense/SuspenseExample.tsx Refactors away from useObservableEvent, improves structure and busy semantics for fallbacks.
website/src/examples/simple/Counter.example.tsx Removes superseded simple counter example implementation.
website/src/examples/search/SearchExample.tsx Refactors search example to explicit Subject event input and clarifies cancellation behavior.
website/src/examples/resilient-fetch/index.tsx Adds Sandpack entry for the offline-resilient polling example.
website/src/examples/resilient-fetch/App.tsx Implements polling with pause/resume, backoff retry, and last-good snapshot retention.
website/src/examples/resilient-fetch/api.ts Adds mock flaky price API and simulated connectivity stream.
website/src/examples/reactive-state/ReactiveStateExample.tsx Removes superseded reactive-state example implementation.
website/src/examples/manifests.ts Updates Sandpack manifests: adds new examples, removes old ones and dropped dependencies.
website/src/examples/llm-chat/llm.ts Adds mock streaming LLM token source observable.
website/src/examples/llm-chat/index.tsx Points the route to the new llm-chat Sandpack example.
website/src/examples/llm-chat/chat.ts Adds cached conversation stream per chat id with token folding + replay.
website/src/examples/llm-chat/App.tsx Implements multi-chat UI with useObservablePromise + <Activity> + hover preloading.
website/src/examples/interval-observable/index.tsx Adds Sandpack entry for interval-as-stream example.
website/src/examples/interval-observable/App.tsx Implements interval demo with stream inputs (BehaviorSubjects) and derived count$.
website/src/examples/hello-world/HelloWorldExample.tsx Removes superseded hello-world example implementation.
website/src/examples/form/index.tsx Points the route to the new form Sandpack example.
website/src/examples/form/App.tsx Adds basic multi-field state example demonstrating useSyncObservable vs useObservable.
website/src/examples/form-data/FormDataExample.tsx Refactors to explicit subjects and fixes submit stream flattening (switching to async write).
website/src/examples/fizz-buzz/FizzBuzzExample.tsx Removes superseded fizz-buzz example implementation.
website/src/examples/fetch/FetchExample.tsx Refactors to explicit Subject event input and removes useObservableEvent usage.
website/src/examples/event-handlers/EventHandlersExample.tsx Removes superseded event-handlers example implementation.
website/src/examples/clicks-values/index.tsx Adds Sandpack entry for “transform values” clicks example.
website/src/examples/clicks-values/App.tsx Implements clicks-to-sum example with throttling and value mapping.
website/src/examples/clicks-throttle/index.tsx Adds Sandpack entry for throttled clicks example.
website/src/examples/clicks-throttle/App.tsx Implements throttled click counter stream.
website/src/examples/clicks-count/index.tsx Updates example route to new click counter example.
website/src/examples/clicks-count/App.tsx Implements click counter using useObservable (no manual subscription bridge).
website/src/examples/clicks-bridge/index.tsx Updates example route to manual bridge example.
website/src/examples/clicks-bridge/App.tsx Implements the “hand-rolled useEffect+subscribe bridge” anti-pattern example.
website/src/examples/checkbox/index.tsx Points the route to the new checkbox Sandpack example.
website/src/examples/checkbox/App.tsx Adds module-scoped BehaviorSubject checkbox example with useSyncObservable.
website/src/examples/async-react/server.ts Adds mock backend for Async React demo.
website/src/examples/async-react/router.tsx Adds transition-based router state + context.
website/src/examples/async-react/NetworkDebugger.tsx Adds network debugger UI driven by streams.
website/src/examples/async-react/Login.tsx Adds login screen with action + prefetch behavior.
website/src/examples/async-react/index.tsx Points the route to the new async-react Sandpack example.
website/src/examples/async-react/Home.tsx Adds the main “lessons” UI reading data via useObservablePromise and actions that revalidate.
website/src/examples/async-react/design.tsx Adds demo “design system” components using transitions/optimistic UI via action props.
website/src/examples/async-react/demo.css Adds shimmer/skeleton/network debugger styling for the Async React demo.
website/src/examples/async-react/App.tsx Wires Async React demo screens with router + debugger.
website/src/examples/async-react/api.ts Implements stream-based data layer: request logging, per-key lessons$, revalidation, prefetching.
website/src/examples/animation/AnimationExample.tsx Removes superseded animation example and its styled-components usage.
website/src/content/reference.mdx Expands API reference with “When not to use” guidance and steers away from useObservableEvent.
website/src/content/index.md Rewrites docs landing page to emphasize concurrent React features, examples, production usage, and skill.
website/src/content/guide.md Restructures guide around mental model, hook selection, events-as-subjects, and key patterns.
website/src/content/examples/todo-app.mdx Adds explanatory prose for the todo example.
website/src/content/examples/timers.mdx Adds new “Timers & time ago” page tying together interval + time-ago + SSR demo.
website/src/content/examples/sync.mdx Removes superseded sync example page.
website/src/content/examples/suspense.mdx Updates Suspense/deferred-values page copy to match new examples/layout.
website/src/content/examples/simple.mdx Replaces old “simple/hello world” content with the new “First steps” guided tour.
website/src/content/examples/search.mdx Adds explanatory prose for search example semantics (switchMap cancellation).
website/src/content/examples/resilient-fetch.mdx Adds explanatory prose for offline pause/resume + retry/backoff polling example.
website/src/content/examples/llm-chat.mdx Adds new LLM streaming demo page with explanation of useObservablePromise, <Activity>, preload.
website/src/content/examples/form-data.mdx Adds explanatory prose for form state + submit state stream example.
website/src/content/examples/fetch.mdx Adds explanatory prose for fetch-on-click example and pointers to related demos.
website/src/content/examples/event-handlers.mdx Removes superseded event-handlers example page.
website/src/content/examples/errors.mdx Adds explanation for error semantics (Error Boundary vs error-as-value).
website/src/content/examples/counters.mdx Removes superseded “advanced counters” page.
website/src/content/examples/context.mdx Adds explanatory prose for context example.
website/src/content/examples/basic-state.mdx Adds new “Basic state” page (text field, checkbox, form) aligned with React docs.
website/src/content/examples/async-react.mdx Adds Async React demo page and explanation of stream-based data layer.
website/src/content/examples/animation.mdx Removes superseded animation example page.
website/src/content/examples/_meta.ts Curates examples navigation into Basics / Real-world / React integration sections.
website/src/content/comparison.md Adds new comparison page: other RxJS bindings + state-library landscape framing.
website/src/content/_meta.ts Adds comparison to top-level docs navigation.
website/src/components/Sandpack.tsx Adds shared Pico CSS + narrow-pane styles and updates Sandpack entrypoint wiring.
website/src/app/llms.txt/route.ts Adds agent skill section to llms.txt.
website/package.json Drops styled-components and bezier-easing from website deps.
website/next.config.ts Removes styled-components compiler flag and adds redirects for moved/removed example pages.
pnpm-lock.yaml Updates lockfile to reflect removed deps.
packages/react-rx/README.md Major README refresh: purpose, Suspense section, comparisons, production usage, and skill install.
AGENTS.md Documents skill usage guidance for agents and reviewers.
.agents/skills/react-rx-best-practices/SKILL.md Adds new installable skill definition + best practices content.
.agents/skills/react-rx-best-practices/references/when-subscribe-is-right.md Adds reference doc covering when manual subscriptions should remain.
.agents/skills/react-rx-best-practices/references/stream-state-recipes.md Adds reference doc with recurring refactor/stream-state recipes.
.agents/skills/react-rx-best-practices/references/referential-stability.md Adds reference doc on observable identity and stable construction patterns.
.agents/skills/react-rx-best-practices/references/hook-selection.md Adds decision guide for hook selection and “when not to use” lists.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +63 to +70
parts$ = now$.pipe(
map((now) => toTimeAgoParts(now - sentAt)),
distinctUntilChanged(
(a, b) =>
a.value === b.value &&
a.unit === b.unit,
),
)
Comment on lines +63 to +70
parts$ = now$.pipe(
map((now) => toTimeAgoParts(now - sentAt)),
distinctUntilChanged(
(a, b) =>
a.value === b.value &&
a.unit === b.unit,
),
)
Comment on lines +27 to +31
map(([, text]) => text.trim()),
filter((text) => text.length > 0),
map((text) => ({text, id: Date.now()})),
scan(
(items: TodoItem[], item) =>
A copy pass over the README and the docs site (skills untouched):

- No em dashes anywhere: prose, code comments, and UI strings in the
  example sandboxes
- Shorter sentences, one idea each, with bold lead-ins on bullets
- Long paragraphs broken into lists (guide semantics/options sections,
  example pages, comparison prose)
- Jargon removed: 'load-bearing' is now plain language, 'chrome' is
  'UI', symbol-only table cells spell out No/Manual instead of a dash
- Table cells and links unchanged in meaning; all heading anchors kept

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
…8c6'

git-subtree-dir: async-react
git-subtree-mainline: 8910032
git-subtree-split: c971b2f
Comment thread website/src/examples/async-react/api.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 97 out of 98 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Suppressed comments (2)

website/src/examples/resilient-fetch/App.tsx:120

  • The click handler toggles online$ based on online read via useObservable. Since useObservable can lag (deferred updates), this is a same-event read-back and may toggle from a stale value under concurrent rendering. Read from the BehaviorSubject directly (or switch this read to useSyncObservable).
    website/src/examples/async-react/design.tsx:70
  • CompleteButton stays clickable while its transition is pending. That allows multiple rapid clicks to queue concurrent action() calls and flip optimistic state based on a potentially stale closure, producing inconsistent final state (and duplicate requests). Disable the button (and mark it busy) while pending, like Button does.

cursoragent and others added 2 commits August 4, 2026 09:52
…to react-rx

The subtree merge in the previous commit brings in
github.com/rickhanlonii/async-react with full history. This commit
turns it into a pnpm workspace and performs the migration:

- src/data/index.js: the cache-of-promises becomes cached
  revalidate-driven rxjs streams. getLessons$(tab, search) is read as a
  use()-compatible promise, so revalidations stream fresh data into
  visible lists in place and can never re-trigger a Suspense fallback.
  prefetchLessons() is preloadObservablePromise raced against 1s,
  preserving the talk's login semantics. refreshLessons() resolves when
  fresh data lands so actions stay pending through mutation + refetch
- src/app/Home.jsx: reads the stream via useObservablePromise in the
  non-suspending parent and passes the promise to LessonList's use();
  completeAction awaits refreshLessons instead of router.refresh
- Everything else stays upstream: router, design components,
  ViewTransition animations, network debugger, styling
- Workspace plumbing: pnpm-workspace entry, react-rx/rxjs deps
  (react-scripts removed, unused CRA leftover), vite resolve.dedupe for
  react so workspace-source react-rx uses the app's experimental React,
  excluded from oxlint/oxfmt/knip to stay close to upstream, yarn.lock
  dropped in favor of the monorepo lockfile

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
Sandpack's in-browser bundler can't run the fork: its package manager
hard-fails Radix/react-aria peer ranges against react@experimental
(0.0.0-experimental versions), and the Vite Tailwind plugin doesn't
exist there either. Instead of stripping ViewTransition and faking the
styling, the docs embed the real thing:

- website/scripts/build-async-react-demo.mjs builds the workspace app
  with a /async-react-demo/ base and copies dist into public/ (wired
  into the website's predev/prebuild; output gitignored)
- the example page renders an iframe with a Lessons / Login flow
  switcher and links to the workspace sources; an SPA rewrite serves
  index.html for client-side routes like /async-react-demo/login
- the fork's router learns to strip Vite's base from the initial and
  navigated pathnames, so the app works from a subpath (no behavior
  change when served from /)
- the example manifest keeps reading the workspace sources (new
  sourceDir field on ExampleManifest), so Copy page and /llms-full.txt
  still export the real migrated files
- the superseded hand-written sandbox port of the demo is removed

Verified in the browser: the embed boots with upstream styling intact,
ViewTransition animations play (list items animate out on completion),
the network debugger works, and the login flow runs through the
switcher.

Co-authored-by: Cody Olsen <stipsan@users.noreply.github.com>
@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn Medium
Install-time scripts: npm @parcel/watcher during install

Install script: install

Source: node scripts/build-from-source.js

From: pnpm-lock.yamlnpm/@tailwindcss/cli@4.3.3npm/@parcel/watcher@2.5.1

ℹ Read more on: This package | This alert | What is an install script?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not be running non-essential scripts during install and there are often solutions to problems people solve with install scripts that can be run at publish time instead.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@parcel/watcher@2.5.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Install-time scripts: npm esbuild during postinstall

Install script: postinstall

Source: node install.js

From: pnpm-lock.yamlnpm/vite@7.3.6npm/vite@8.2.0npm/esbuild@0.28.1

ℹ Read more on: This package | This alert | What is an install script?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not be running non-essential scripts during install and there are often solutions to problems people solve with install scripts that can be run at publish time instead.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/esbuild@0.28.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5e7d838. Configure here.


function navigate(url) {
window.navigation.navigate(url);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Login navigation drops Vite base

High Severity

normalizePathname strips import.meta.env.BASE_URL for route matching, but navigate (and History setParams) write bare paths like /. When the docs embed builds with --base=/async-react-demo/, login then leaves the SPA subpath—the iframe loads the site root under the Navigation API, and History mode rewrites the address bar out of the demo.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5e7d838. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 147 out of 150 changed files in this pull request and generated no new comments.

Suppressed comments (7)

async-react/src/main.jsx:48

  • Layout is passed a heading prop, but Layout only accepts {children} and never renders heading. This is dead code and can confuse future edits (it looks like the heading should show up, but it can’t).
    async-react/src/design/TabList.jsx:3
  • Import path contains a double slash (@/components//ui/tabs). While it may resolve in some environments, it’s easy to break tooling (and looks like a typo).
    async-react/src/design/PendingButton.jsx:1
  • Import path contains a double slash (@/components//ui/button). This looks accidental and can break tools that don’t normalize paths the same way.
    async-react/server.js:6
  • server.js imports @/data/fake-data.js, but Node (via nodemon server.js) won't understand the Vite @ alias. This makes the optional backend server script fail to start with a module resolution error.
const port = 8080;

import * as fakeData from "@/data/fake-data.js";

async-react/src/design/EmptyList.jsx:11

  • Import path contains a double slash (@/components//ui/item). This looks accidental and can break tools that don’t normalize paths the same way.
    async-react/src/design/Fallback.jsx:7
  • Import path contains a double slash (@/components//ui/item). This looks accidental and can break tools that don’t normalize paths the same way.
    async-react/src/data/index.js:49
  • refreshLessons() waits for settled$ to emit, but settled$ is only notified on successful fetches. If /lessons errors (real server mode, network failure, etc), refreshLessons() will never resolve and any Action awaiting it will stay pending indefinitely.

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.

Since suspense is mentioned in keywords, how does this library utilize this feature? Question about usages

7 participants