Does adding GenClass break your app? This page answers that for 7 small, idiomatic apps (React 19 + Vite 8; Next.js 16 App Router (SSR); Vue 3 + Vite 8; SvelteKit 3 (Svelte 5, SSR, adapter-node); Angular 22 (SSR + hydration, zoneless); Solid 1.9 + Vite 8; Plain HTML, CDN script tag) and 15 data layers, each installed with the one line from the README and driven through the same eight scenarios in headless Chromium, 10 seeds each, in every mode. It is generated by a script from a run on 2026-10-10; nothing here is edited by hand except the notes marked Analysis, and every ✗ is listed with its details below.
Summary
- 350 of 351 (app × data layer × scenario × mode) cells are ✓: GenClass in that mode left the app as correct as it was without GenClass, on every seed. Observe mode (the default) must leave the final page and server state identical to a run without GenClass.
- Bugs introduced: 0 across 3510 mode runs. Non-passive actions on correct apps (scenarios c to h, guard + heal): 0 in 1760 runs.
- Bugs fixed by an action: 10 (10 × heal, HttpClient withXhr() (XMLHttpRequest) + signals (Angular 22 (SSR + hydration, zoneless)), scenario b, request:block). The two scenarios with a latent bug (a stale typeahead, a double submit) showed it in 250 of 290 runs without GenClass; guard left 249 and heal 240 of the same runs buggy. GenClass acts only when its model is confident, and with the shipped model that is rare: it reports far more than it fixes (see Model quality). In 4 more runs a bug of the app did not show with GenClass although GenClass took no action: GenClass changed the timing of a race in the app (listed at the end of the page).
- Observe-mode findings on correct apps: 319 in 880 runs (scenarios c to h). 270 of them report the failure a scenario injects on purpose (the HTTP 500 in d, going offline in e), which the app handles correctly; 49 are false findings (request:overload in g ×45, request:duplicate in g ×4). Findings change nothing in observe mode; they are listed per cell in What GenClass reported and did.
Versions: @genclass/runtime packed from this repository at commit d04eff3 (version string 0.1.0-beta.4, never published: the code released as 0.2.0 plus the "Model ready" log fix, which shipped together as 0.2.1), model genclass-runtime-r17 2.0.0-rc4t (the default model, WASM, balanced profile), headless Chromium 153.0.8010.12, Node v24.21.0. Framework versions are in the first table.
Frameworks
Each app is built for production and served the way it would be in production (static files, next start, the SvelteKit Node adapter, Angular's SSR server). Boots clean: the model loads, fetch is instrumented, no console errors or GenClass warnings, no request leaves the machine. Kill switch: with ?genclass=off nothing is installed (WebSocket, EventSource and timers are the browser's own, no model worker) and the app works. Devtools: the overlay mounts. CSP: the app still boots clean (model ready, no violation) under a Content-Security-Policy that adds only what the README asks for, sent with the HTML page (page) or with every response, scripts and the model worker included, as many production servers do (every response). SSR: the page is rendered on the server with GenClass installed, and importing the one line on the server returns an inert runtime (server fetch untouched, no globals added).
| app | where the one line goes | versions | builds | boots clean | kill switch | devtools | CSP (page) | CSP (every response) | SSR inert | hydration errors |
|---|---|---|---|---|---|---|---|---|---|---|
| React 19 + Vite 8 | the one import in src/main.tsx | @genclass/runtime 0.1.0-beta.4, react 19.3.0, react-dom 19.3.0, vite 8.3.4, @tanstack/react-query 5.104.1, zustand 5.0.15, @reduxjs/toolkit 2.13.0, react-redux 9.3.0, @apollo/client 4.3.3, graphql 17.0.2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | – | – |
| Next.js 16 App Router (SSR) | instrumentation-client.ts | @genclass/runtime 0.1.0-beta.4, next 16.4.0, react 19.3.0, react-dom 19.3.0, swr 2.5.1 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
| Vue 3 + Vite 8 | the one import in src/main.ts | @genclass/runtime 0.1.0-beta.4, vue 3.5.43, pinia 4.0.3, vite 8.3.4 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | – | – |
| SvelteKit 3 (Svelte 5, SSR, adapter-node) | src/hooks.client.ts | @genclass/runtime 0.1.0-beta.4, svelte 5.57.2, @sveltejs/kit 3.0.1, @sveltejs/adapter-node 6.0.0, vite 8.3.4 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
| Angular 22 (SSR + hydration, zoneless) | the one import in src/main.ts | @genclass/runtime 0.1.0-beta.4, @angular/core 22.2.2, @angular/ssr 22.2.2, rxjs 7.8.2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 0 |
| Solid 1.9 + Vite 8 | the one import in src/index.tsx | @genclass/runtime 0.1.0-beta.4, solid-js 1.9.17, vite 8.3.4, vite-plugin-solid 2.11.14 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | – | – |
| Plain HTML, CDN script tag | the script tag, first in | @genclass/runtime 0.1.0-beta.4 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | – | – |
CSP used (bundled apps): default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; worker-src 'self' blob:; connect-src 'self' ws: https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:. Script-tag app: default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://cdn.jsdelivr.net; worker-src 'self' blob:; connect-src 'self' ws: https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:.
Never-worse matrix
Each cell shows observe · guard · heal for one data layer and one scenario. ✓ means: on every seed, that mode left the app at least as correct as the same run without GenClass (observe: identical final DOM and server state; guard and heal: no bug introduced, and identical wherever the run without GenClass was correct; a seed runs twice without GenClass, and either run counts), with no new console errors and the model loaded. Click a ✗ for the details.
| app | data layer | a | b | c | d | e | f | g | h |
|---|---|---|---|---|---|---|---|---|---|
| React 19 + Vite 8 | fetch + useState / useEffect | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| React 19 + Vite 8 | TanStack Query 5 | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| React 19 + Vite 8 | Zustand 5 (devtools middleware) | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| React 19 + Vite 8 | Zustand 5 (devtools middleware, enabled: true) |
✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| React 19 + Vite 8 | Redux Toolkit 2 + RTK Query | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| React 19 + Vite 8 | Apollo Client 4 (mock GraphQL) | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| Next.js 16 App Router (SSR) | fetch + useState / useEffect (client components) | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| Next.js 16 App Router (SSR) | SWR 2 | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✗ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| Vue 3 + Vite 8 | Pinia (setup stores) | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| SvelteKit 3 (Svelte 5, SSR, adapter-node) | Svelte stores (writable) | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| Angular 22 (SSR + hydration, zoneless) | HttpClient (default FetchBackend) + signals | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| Angular 22 (SSR + hydration, zoneless) | HttpClient withXhr() (XMLHttpRequest) + signals | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| Solid 1.9 + Vite 8 | signals + createResource | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| Plain HTML, CDN script tag | fetch + DOM | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
| Plain HTML, CDN script tag | WebSocket + EventSource + DOM | ✓ · ✓ · ✓ | – | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ | – | – | ✓ · ✓ · ✓ | ✓ · ✓ · ✓ |
Scenarios
Every app implements the same eight screens against the same seeded mock backend. Latency is drawn from the seed per request, so every mode of a seed sees the same network.
| scenario | what the runner does | the app is | |
|---|---|---|---|
| a | typeahead, out-of-order responses | type a 5-letter prefix at 70 ms/key; shorter prefixes answer later than the full one | written the common, naive way: it can show the bug |
| b | double submit of a create | double-click the submit button of a create form (POST takes 350-650 ms) | written the common, naive way: it can show the bug |
| c | two concurrent requests, different parts of the UI | one click loads two panels concurrently (80-600 ms each, random order) | correct: GenClass must not change the outcome |
| d | optimistic update, confirmation, rollback on failure | toggle todo 1 (server confirms) and 120 ms later todo 2 (server answers 500: must roll back) | correct: GenClass must not change the outcome |
| e | offline, then reconnect and sync | go offline, add two notes (queued), go online: the app syncs its outbox | correct: GenClass must not change the outcome |
| f | repeated deliberate actions (+1 five times) | click +1 five times, 180 ms apart; each POST takes 150-500 ms (identical requests overlap) | correct: GenClass must not change the outcome |
| g | polling and fan-out | click Start: six detail GETs at once (50-400 ms) and a status poll every 250 ms until tick 8 | correct: GenClass must not change the outcome |
| h | clean traffic | list loads, type a 3-letter search at 250 ms/key, create one item with one click (20-80 ms) | correct: GenClass must not change the outcome |
Automatic state discovery
With the one line, GenClass looks for the app's state on its own (React, Redux, Zustand through the Redux DevTools API). Found state lets it compare a late response with newer data already on screen. Per data layer: what runtime.stores() reported at the end of each GenClass run.
| app | data layer | the layer's state | found | stores seen (source) |
|---|---|---|---|---|
| React 19 + Vite 8 | fetch + useState / useEffect | React state | ✓ React state (240/240 runs) | compat (react), compat_2 (react), tick (react) |
| React 19 + Vite 8 | TanStack Query 5 | TanStack Query cache | not covered (React component state found in 240/240 runs) | compat (react), compat_2 (react), tick (react) |
| React 19 + Vite 8 | Zustand 5 (devtools middleware) | the Zustand stores (devtools connect) |
✗ the Zustand stores (devtools connect) in 0/240 runs (why) |
compat (react), compat_2 (react), tick (react) |
| React 19 + Vite 8 | Zustand 5 (devtools middleware, enabled: true) |
the Zustand stores (devtools connect) |
✓ the Zustand stores (devtools connect) (240/240 runs) |
search (devtools), items (devtools), panels (devtools), todos (devtools), notes (devtools), counter (devtools), … (10) |
| React 19 + Vite 8 | Redux Toolkit 2 + RTK Query | the Redux store | ✓ the Redux store (240/240 runs) | redux (redux, adapter), compat (react), compat_2 (react), tick (react), Ta (react), Ta_2 (react), … (7) |
| React 19 + Vite 8 | Apollo Client 4 (mock GraphQL) | Apollo cache | not covered (React component state found in 240/240 runs) | compat (react), compat_2 (react), gd (react), tick (react), gd_2 (react), gd_3 (react) |
| Next.js 16 App Router (SSR) | fetch + useState / useEffect (client components) | React state | ✓ React state (240/240 runs) | compat (react), compat_2 (react), tick (react) |
| Next.js 16 App Router (SSR) | SWR 2 | SWR cache | not covered (React component state found in 240/240 runs) | compat (react), compat_2 (react), eM (react), tick (react), eM_2 (react), eM_3 (react) |
| Vue 3 + Vite 8 | Pinia (setup stores) | Pinia stores | not covered | none |
| SvelteKit 3 (Svelte 5, SSR, adapter-node) | Svelte stores (writable) | Svelte stores | not covered | none |
| Angular 22 (SSR + hydration, zoneless) | HttpClient (default FetchBackend) + signals | Angular signals | not covered | none |
| Angular 22 (SSR + hydration, zoneless) | HttpClient withXhr() (XMLHttpRequest) + signals | Angular signals | not covered | none |
| Solid 1.9 + Vite 8 | signals + createResource | Solid signals | not covered | none |
| Plain HTML, CDN script tag | fetch + DOM | plain objects / the DOM | not covered | none |
| Plain HTML, CDN script tag | WebSocket + EventSource + DOM | plain objects / the DOM | not covered | none |
Not covered means the README does not claim discovery for that kind of state: GenClass then sees the network and user input, and decisions that depend on the app's state are not made. With TanStack Query, SWR and Apollo the cache itself is not a store, but the React component state GenClass finds includes what each component reads from it (the useQuery / useSWR result: data and status), observed only. Production builds minify component names, so React stores are named after an element the component renders (compat is the page root's id).
What GenClass reported and did
Per cell: user-visible bugs without GenClass (off, out of the seeds run) and in each mode, then observe detections / guard actions / heal actions summed over seeds. A bug is what a user would see (stale results, a duplicate on the server, a lost note); oracles read only the page and the mock server's state, never GenClass. Fixed: buggy without GenClass, correct in the mode (same seed). Introduced: correct without GenClass, buggy in the mode. In guard and heal GenClass may hold a response for a moment while its model decides (observe never does); on a seed that was already buggy this can change which stale answer ends up on screen without fixing it (listed at the end of the page).
| app | data layer | a | b | c | d | e | f | g | h |
|---|---|---|---|---|---|---|---|---|---|
| React 19 + Vite 8 | state | bugs off 10/10, g 10, h 10 2 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 |
| React 19 + Vite 8 | tanstack | bugs off 0/10, g 0, h 0 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 |
| React 19 + Vite 8 | zustand | bugs off 10/10, g 10, h 10 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 |
| React 19 + Vite 8 | zustand-on | bugs off 10/10, g 10, h 10 5 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 8 / 0 / 0 | 0 / 0 / 0 |
| React 19 + Vite 8 | redux | bugs off 10/10, g 9 (fixed 1), h 10 1 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 |
| React 19 + Vite 8 | apollo | bugs off 0/10, g 0, h 0 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 0 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 4 / 0 / 0 | 0 / 0 / 0 |
| Next.js 16 App Router (SSR) | state | bugs off 10/10, g 10, h 10 2 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 |
| Next.js 16 App Router (SSR) | swr | bugs off 0/10, g 0, h 0 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | bugs off 1/10, g 0 (fixed 1), h 0 (fixed 1) 10 / 0 / 0 |
10 / 0 / 0 | 0 / 0 / 0 | 1 / 0 / 0 | 0 / 0 / 0 |
| Vue 3 + Vite 8 | pinia | bugs off 10/10, g 10, h 10 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 2 / 0 / 0 | 0 / 0 / 0 |
| SvelteKit 3 (Svelte 5, SSR, adapter-node) | stores | bugs off 10/10, g 10, h 10 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 |
| Angular 22 (SSR + hydration, zoneless) | fetch | bugs off 10/10, g 10, h 10 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 |
| Angular 22 (SSR + hydration, zoneless) | xhr | bugs off 10/10, g 10, h 10 0 / 0 / 0 |
bugs off 10/10, g 10, h 0 (fixed 10) 10 / 0 / 10 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 | 0 / 0 / 0 |
| Solid 1.9 + Vite 8 | signals | bugs off 0/10, g 0, h 0 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 2 / 0 / 0 | 0 / 0 / 0 |
| Plain HTML, CDN script tag | fetch | bugs off 10/10, g 10, h 10 0 / 0 / 0 |
bugs off 10/10, g 10, h 10 10 / 0 / 0 |
0 / 0 / 0 | 10 / 0 / 0 | 10 / 0 / 0 | 0 / 0 / 0 | 2 / 0 / 0 | 0 / 0 / 0 |
| Plain HTML, CDN script tag | push | bugs off 10/10, g 10, h 10 0 / 0 / 0 |
– | 0 / 0 / 0 | 10 / 0 / 0 | – | – | 0 / 0 / 0 | 0 / 0 / 0 |
Every detection and action (trigger:diagnosis, trigger:action)
- react-vite / state / a / observe: detections delivery:stale ×2 - react-vite / state / b / observe: detections request:duplicate ×10 - react-vite / state / b / guard: detections request:duplicate ×10 - react-vite / state / b / heal: detections request:duplicate ×10 - react-vite / state / d / observe: detections failure:transient ×10 - react-vite / state / d / guard: detections failure:transient ×10 - react-vite / state / d / heal: detections failure:transient ×10 - react-vite / state / e / observe: detections failure:failing ×10 - react-vite / state / e / guard: detections failure:failing ×10 - react-vite / state / e / heal: detections failure:failing ×10 - react-vite / state / g / observe: detections request:overload ×10 - react-vite / state / g / guard: detections request:overload ×10 - react-vite / state / g / heal: detections request:overload ×10 - react-vite / tanstack / b / observe: detections request:duplicate ×10 - react-vite / tanstack / b / guard: detections request:duplicate ×10 - react-vite / tanstack / b / heal: detections request:duplicate ×10 - react-vite / tanstack / d / observe: detections failure:transient ×10 - react-vite / tanstack / d / guard: detections failure:transient ×10 - react-vite / tanstack / d / heal: detections failure:transient ×10 - react-vite / zustand / b / observe: detections request:duplicate ×10 - react-vite / zustand / b / guard: detections request:duplicate ×10 - react-vite / zustand / b / heal: detections request:duplicate ×10 - react-vite / zustand / d / observe: detections failure:transient ×10 - react-vite / zustand / d / guard: detections failure:transient ×10 - react-vite / zustand / d / heal: detections failure:transient ×10 - react-vite / zustand / e / observe: detections failure:failing ×10 - react-vite / zustand / e / guard: detections failure:failing ×10 - react-vite / zustand / e / heal: detections failure:failing ×10 - react-vite / zustand / g / observe: detections request:overload ×10 - react-vite / zustand / g / guard: detections request:overload ×8 - react-vite / zustand / g / heal: detections request:overload ×7 - react-vite / zustand-on / a / observe: detections delivery:stale ×5 - react-vite / zustand-on / a / guard: detections delivery:stale ×3 - react-vite / zustand-on / a / heal: detections delivery:stale ×3 - react-vite / zustand-on / b / observe: detections request:duplicate ×10 - react-vite / zustand-on / b / guard: detections request:duplicate ×10 - react-vite / zustand-on / b / heal: detections request:duplicate ×10 - react-vite / zustand-on / d / observe: detections failure:transient ×10 - react-vite / zustand-on / d / guard: detections failure:transient ×10 - react-vite / zustand-on / d / heal: detections failure:transient ×10 - react-vite / zustand-on / e / observe: detections failure:failing ×10 - react-vite / zustand-on / e / guard: detections failure:failing ×10 - react-vite / zustand-on / e / heal: detections failure:failing ×10 - react-vite / zustand-on / g / observe: detections request:overload ×8 - react-vite / zustand-on / g / guard: detections request:overload ×3 - react-vite / zustand-on / g / heal: detections request:overload ×3 - react-vite / redux / a / observe: detections delivery:stale ×1 - react-vite / redux / a / guard: detections mutation:stale ×11 - react-vite / redux / a / heal: detections mutation:stale ×12 - react-vite / redux / b / observe: detections request:duplicate ×10 - react-vite / redux / b / guard: detections request:duplicate ×10 - react-vite / redux / b / heal: detections request:duplicate ×10 - react-vite / redux / d / observe: detections failure:transient ×10 - react-vite / redux / d / guard: detections failure:transient ×10 - react-vite / redux / d / heal: detections failure:transient ×10 - react-vite / redux / e / observe: detections failure:failing ×10 - react-vite / redux / e / guard: detections failure:failing ×10 - react-vite / redux / e / heal: detections failure:failing ×10 - react-vite / apollo / b / observe: detections request:duplicate ×10 - react-vite / apollo / b / guard: detections request:duplicate ×10 - react-vite / apollo / b / heal: detections request:duplicate ×10 - react-vite / apollo / e / observe: detections failure:failing ×10 - react-vite / apollo / e / guard: detections failure:failing ×10 - react-vite / apollo / e / heal: detections failure:failing ×10 - react-vite / apollo / g / observe: detections request:duplicate ×4 - react-vite / apollo / g / guard: detections request:duplicate ×5 - react-vite / apollo / g / heal: detections request:duplicate ×5 - next-app / state / a / observe: detections delivery:stale ×2 - next-app / state / b / observe: detections request:duplicate ×10 - next-app / state / b / guard: detections request:duplicate ×10 - next-app / state / b / heal: detections request:duplicate ×10 - next-app / state / d / observe: detections failure:transient ×10 - next-app / state / d / guard: detections failure:transient ×10 - next-app / state / d / heal: detections failure:transient ×10 - next-app / state / e / observe: detections failure:failing ×10 - next-app / state / e / guard: detections failure:failing ×10 - next-app / state / e / heal: detections failure:failing ×10 - next-app / state / g / observe: detections request:overload ×10 - next-app / state / g / guard: detections request:overload ×10 - next-app / state / g / heal: detections request:overload ×10 - next-app / swr / b / observe: detections request:duplicate ×10 - next-app / swr / b / guard: detections request:duplicate ×10 - next-app / swr / b / heal: detections request:duplicate ×10 - next-app / swr / d / observe: detections failure:transient ×10 - next-app / swr / d / guard: detections failure:transient ×10 - next-app / swr / d / heal: detections failure:transient ×10 - next-app / swr / e / observe: detections failure:failing ×10 - next-app / swr / e / guard: detections failure:failing ×10 - next-app / swr / e / heal: detections failure:failing ×10 - next-app / swr / g / observe: detections request:overload ×1 - next-app / swr / g / heal: detections request:overload ×1 - vue-vite / pinia / b / observe: detections request:duplicate ×10 - vue-vite / pinia / b / guard: detections request:duplicate ×10 - vue-vite / pinia / b / heal: detections request:duplicate ×10 - vue-vite / pinia / d / observe: detections failure:transient ×10 - vue-vite / pinia / d / guard: detections failure:transient ×10 - vue-vite / pinia / d / heal: detections failure:transient ×10 - vue-vite / pinia / e / observe: detections failure:failing ×10 - vue-vite / pinia / e / guard: detections failure:failing ×10 - vue-vite / pinia / e / heal: detections failure:failing ×10 - vue-vite / pinia / g / observe: detections request:overload ×2 - sveltekit / stores / b / observe: detections request:duplicate ×10 - sveltekit / stores / b / guard: detections request:duplicate ×10 - sveltekit / stores / b / heal: detections request:duplicate ×10 - sveltekit / stores / d / observe: detections failure:transient ×10 - sveltekit / stores / d / guard: detections failure:transient ×10 - sveltekit / stores / d / heal: detections failure:transient ×10 - sveltekit / stores / e / observe: detections failure:failing ×10 - sveltekit / stores / e / guard: detections failure:failing ×10 - sveltekit / stores / e / heal: detections failure:failing ×10 - angular / fetch / b / observe: detections request:duplicate ×10 - angular / fetch / b / guard: detections request:duplicate ×10 - angular / fetch / b / heal: detections request:duplicate ×10 - angular / fetch / d / observe: detections failure:transient ×10 - angular / fetch / d / guard: detections failure:transient ×10 - angular / fetch / d / heal: detections failure:transient ×10 - angular / fetch / e / observe: detections failure:failing ×10 - angular / fetch / e / guard: detections failure:failing ×10 - angular / fetch / e / heal: detections failure:failing ×10 - angular / xhr / b / observe: detections request:duplicate ×10 - angular / xhr / b / guard: detections request:duplicate ×10 - angular / xhr / b / heal: detections request:duplicate ×10; actions request:block ×10 - angular / xhr / d / observe: detections failure:transient ×10 - angular / xhr / d / guard: detections failure:transient ×10 - angular / xhr / d / heal: detections failure:transient ×10 - angular / xhr / e / observe: detections failure:failing ×10 - angular / xhr / e / guard: detections failure:failing ×10 - angular / xhr / e / heal: detections failure:failing ×10 - solid-vite / signals / b / observe: detections request:duplicate ×10 - solid-vite / signals / b / guard: detections request:duplicate ×10 - solid-vite / signals / b / heal: detections request:duplicate ×10 - solid-vite / signals / d / observe: detections failure:transient ×10 - solid-vite / signals / d / guard: detections failure:transient ×10 - solid-vite / signals / d / heal: detections failure:transient ×10 - solid-vite / signals / e / observe: detections failure:failing ×10 - solid-vite / signals / e / guard: detections failure:failing ×10 - solid-vite / signals / e / heal: detections failure:failing ×10 - solid-vite / signals / g / observe: detections request:overload ×2 - plain-html / fetch / b / observe: detections request:duplicate ×10 - plain-html / fetch / b / guard: detections request:duplicate ×10 - plain-html / fetch / b / heal: detections request:duplicate ×10 - plain-html / fetch / d / observe: detections failure:transient ×10 - plain-html / fetch / d / guard: detections failure:transient ×10 - plain-html / fetch / d / heal: detections failure:transient ×10 - plain-html / fetch / e / observe: detections failure:failing ×10 - plain-html / fetch / e / guard: detections failure:failing ×10 - plain-html / fetch / e / heal: detections failure:failing ×10 - plain-html / fetch / g / observe: detections request:overload ×2 - plain-html / push / d / observe: detections failure:transient ×10 - plain-html / push / d / guard: detections failure:transient ×10 - plain-html / push / d / heal: detections failure:transient ×10Determinism control
Every seed also ran twice without GenClass. 1170 of 1170 pairs (100%) ended identical (final DOM and server state), so a difference between a mode and off is almost always a real difference.
Details for every ✗
discovery-react-vite-zustand
React 19 + Vite 8 / Zustand 5 (devtools middleware): the Zustand stores (devtools connect) was found in 0 of 240 runs. Stores seen: compat (react), compat_2 (react), tick (react).
Analysis (written after the run): Zustand turns its devtools middleware off in production builds (it connects only when enabled ?? import.meta.env.MODE !== "production"), so a production build never calls the Redux DevTools API that GenClass listens on, and its stores are not found. The same app with devtools(..., { enabled: true }) (the enabled: true row) is found in every run. Nothing changes for the app either way (every scenario cell is ✓); GenClass then sees the network only. The README now says so; alternatively wrap the store with genclass from @genclass/runtime/zustand.
next-app-swr-d-observe
Next.js 16 App Router (SSR) / SWR 2 / scenario d (optimistic update, confirmation, rollback on failure) / observe: 10 runs, 0 incomplete, 0 model not ready, 0 bugs introduced, 1 not identical to the runs without GenClass, 0 with new console errors.
- seed 7: differs from off: DOM todo: off ["Write tests: open","Ship release: open","Fix flaky CI: done"] vs ["Write tests: done","Ship release: open","Fix flaky CI: done"]; actions: none
Analysis (written after the run): A race in the app, not an action: SWR's rollbackOnError with two overlapping optimistic mutations (toggle 1 succeeds, toggle 2 fails 120 ms later) sometimes leaves toggle 1 unchecked on screen although the server has it checked. On seed 7 that happened in both runs without GenClass (and in 1 of 2 in an earlier full run the same day), and in none of the 3 runs with GenClass (observe, guard, heal; 6 of 6 across both full runs). GenClass took no action in any of them (one transient failure detection, passive decisions only); most likely its own main-thread work (observing the requests, deciding about the HTTP 500 in the background) shifts the timing of SWR's rollback against its revalidation (not traced further). So observe mode is not byte-identical to no GenClass on a racy app: it can change which side of an app's own race wins, here towards the correct outcome. No other observe cell differed in this run.
Fixes and changed outcomes on buggy seeds
- react-vite / state / a / guard: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - react-vite / state / a / guard: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - react-vite / state / a / guard: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - react-vite / state / a / heal: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - react-vite / state / a / heal: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - react-vite / state / a / heal: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - react-vite / zustand / a / guard: seed 3: still buggy, but differs from off: stale results: shows [Manchester, Manila] (2) for "manch", want 1 - react-vite / zustand / a / guard: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - react-vite / zustand / a / guard: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - react-vite / zustand / a / guard: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - react-vite / zustand / a / heal: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - react-vite / zustand / a / heal: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - react-vite / zustand / a / heal: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - react-vite / zustand-on / a / guard: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - react-vite / zustand-on / a / guard: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - react-vite / zustand-on / a / guard: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - react-vite / zustand-on / a / heal: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - react-vite / zustand-on / a / heal: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - react-vite / zustand-on / a / heal: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - react-vite / redux / a / guard: seed 3: still buggy, but differs from off: stale results: shows [Manchester, Manila] (2) for "manch", want 1 - react-vite / redux / a / guard: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - react-vite / redux / a / guard: seed 9: fixed (no action recorded); off: stale results: shows [Bangkok, Barcelona, Beijing, ...] (8) for "barce", want 1 - react-vite / redux / a / guard: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - react-vite / redux / a / heal: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - react-vite / redux / a / heal: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - react-vite / redux / a / heal: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - next-app / state / a / guard: seed 2: still buggy, but differs from off: stale results: shows [Beijing, Belgrade, Berlin, ...] (4) for "berli", want 1 - next-app / state / a / guard: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - next-app / state / a / guard: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - next-app / state / a / guard: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - next-app / state / a / heal: seed 5: still buggy, but differs from off: stale results: shows [Lima, Lisbon, Liverpool] (3) for "lisbo", want 1 - next-app / state / a / heal: seed 9: still buggy, but differs from off: stale results: shows [Bangkok, Barcelona] (2) for "barce", want 1 - next-app / state / a / heal: seed 10: still buggy, but differs from off: stale results: shows [Seattle, Seoul, Seville] (3) for "seatt", want 1 - next-app / swr / b / guard: seed 1: still buggy, but differs from off: server has 2 "Report 1" - next-app / swr / b / guard: seed 2: still buggy, but differs from off: server has 2 "Report 2" - next-app / swr / b / guard: seed 7: still buggy, but differs from off: server has 2 "Report 7" - next-app / swr / b / heal: seed 1: still buggy, but differs from off: server has 2 "Report 1" - next-app / swr / b / heal: seed 2: still buggy, but differs from off: server has 2 "Report 2" - next-app / swr / b / heal: seed 7: still buggy, but differs from off: server has 2 "Report 7" - next-app / swr / d / observe: seed 7: fixed (no action recorded); off: shows [Write tests: open; Ship release: open; Fix flaky CI: done], server [Write tests: done; Ship release: open; Fix flaky CI: done] - next-app / swr / d / guard: seed 7: fixed (no action recorded); off: shows [Write tests: open; Ship release: open; Fix flaky CI: done], server [Write tests: done; Ship release: open; Fix flaky CI: done] - next-app / swr / d / heal: seed 7: fixed (no action recorded); off: shows [Write tests: open; Ship release: open; Fix flaky CI: done], server [Write tests: done; Ship release: open; Fix flaky CI: done] - angular / xhr / b / heal: seed 1: fixed (request:block); off: server has 2 "Report 1" - angular / xhr / b / heal: seed 2: fixed (request:block); off: server has 2 "Report 2" - angular / xhr / b / heal: seed 3: fixed (request:block); off: server has 2 "Report 3" - angular / xhr / b / heal: seed 4: fixed (request:block); off: server has 2 "Report 4" - angular / xhr / b / heal: seed 5: fixed (request:block); off: server has 2 "Report 5" - angular / xhr / b / heal: seed 6: fixed (request:block); off: server has 2 "Report 6"Method
- Apps. One small app per framework under
compat/apps/, written idiomatically for each data layer (no GenClass API anywhere: only the one line). They install@genclass/runtimefrom a tarball packed from this repository (npm pack), so the matrix tests the code as it would be published. - Backend. One seeded mock server for every app (
compat/harness/backend.mjs): REST, a GraphQL endpoint, a WebSocket and EventSource streams on the app's own origin. It injects latency and failures per scenario; nothing in it knows about GenClass. - A trial. A fresh browser context loads the page (
?genclass=offfor the run without GenClass; otherwise the mode throughwindow.GENCLASS_CONFIG, telemetry off), waits until the model is ready, drives the scenario with real (trusted) keyboard and mouse input, waits until nothing is in flight and the page has been quiet for 0.9 s, then reads the page and the server's state. - Pairing. Every mode run is compared with the run without GenClass on the same seed. 10 seeds per cell, plus a second run without GenClass per seed as a determinism control.
- Isolation. Every request to another host is blocked and counted; the model files and ONNX Runtime are served from a local copy of the files the runtime would fetch from jsDelivr, and the script-tag app gets the packed tarball in place of the CDN.
- Not tuned. The runtime and the model are not changed or tuned for these apps, and the apps are not used for training.
- Limits. Small apps, one machine, headless Chromium only (no Firefox or Safari), WASM inference (no GPU), production builds only. Each cell has few seeds; read single differences as signals to investigate, not rates.
Reproduce
On Linux with Node ≥ 22.22.3 (Angular 22's minimum) and about 4 GB free:
git clone https://github.com/genclass-dev/GenClass-lib && cd GenClass-lib
npm ci && (cd compat && npm install && npx playwright install --with-deps chromium)
node packages/runtime/bin/genclass-runtime.mjs fetch-model .cache-model/runtime-model-0.2.0 --variant q8 --ort wasm
COMPAT_MODEL_DIR=$PWD/.cache-model/runtime-model-0.2.0 npm --prefix compat run compat -- --seeds 10
This rebuilds the runtime, packs it, installs it into every app, builds and serves each app, runs every trial and writes compat/results/<date>.json and this page. --apps, --layers, --scenarios, --modes and --workers narrow a run. This page was generated from 2026-10-10.json (5850 trials, 0 did not complete; 16 parallel browsers).