When the attribution engine is on — captureArtifact from @solidjs/diagnostics, or the performance tracks @solidjs/vite-plugin 3.0.0-next.46 enables by default under vite dev — an optimistic store write made inside an action that fails is not reverted when the action settles. The same code reverts correctly with attribution off, and reverts under attribution on rc.9.
createOptimistic (signal) is not affected. The source kind does not matter (Promise and AsyncIterable both reproduce).
Steps (full test below):
createOptimisticStore(() => source, [], { key: 'id' }) with one row { id: 'a', done: false }.
- An
action(function* () { setList(l => { l[0].done = true }); try { yield Promise.reject(new Error('x')) } catch {} }).
- Run it inside
captureArtifact(...), flush, and read the row after settle.
Expected: done === false (the overlay drops at settle; RFC 06).
Actual (rc.10, attribution on): done === true.
Results of the test below:
| Case |
rc.9 |
rc.10 |
| store, Promise source, attribution off |
pass |
pass |
store, Promise source, captureArtifact |
pass |
fail (a:true) |
| store, AsyncIterable source, attribution off |
pass |
pass |
store, AsyncIterable source, captureArtifact |
pass |
fail (a:true) |
createOptimistic(false), attribution off |
pass |
pass |
createOptimistic(false), captureArtifact |
pass |
pass |
Related symptoms seen in the same scenario family (larger test, not minimized):
- an
OPTIMISTIC_REVERTED finding reporting "showed 1; it settled to 1" (same value) under expectNoDiagnostics;
- an unhandled rejection
TypeError: Cannot read properties of undefined (reading '_host') during the run.
Observation changing runtime semantics also means dev servers with the default performance tracks behave differently from production for optimistic stores.
Environment:
- solid-js, @solidjs/web, @solidjs/signals, @solidjs/diagnostics: 2.0.0-rc.10 (compared with 2.0.0-rc.9)
- @solidjs/vite-plugin 3.0.0-next.46 (rc.9 comparison: 3.0.0-next.44)
- vitest via vite-plus 0.3.3, jsdom; Node v26.10.0; Bun 1.4.2; macOS arm64
Reproduction
import { captureArtifact } from '@solidjs/diagnostics'
import { action, createOptimistic, createOptimisticStore, createRoot, createEffect, flush } from 'solid-js'
import { expect, it } from 'vitest'
const tick = () => new Promise((r) => setTimeout(r, 0))
async function settle() { for (let i = 0; i < 4; i++) { await tick(); flush() } }
type Row = { id: string; done: boolean }
function liveSource(initial: Row[]): AsyncIterable<Row[]> {
return { async *[Symbol.asyncIterator]() { yield initial; await new Promise(() => {}) } }
}
for (const source of ['promise', 'async-iterable'] as const)
for (const observed of [false, true]) {
it(`failed action reverts: source=${source} captureArtifact=${observed}`, async () => {
const body = async () => {
const seen: string[] = []
let run!: () => Promise<unknown>
const dispose = createRoot((d) => {
const init = [{ id: 'a', done: false }]
const [list, setList] = createOptimisticStore<Row[]>(() => source === 'promise' ? Promise.resolve(init) : liveSource(init), [], { key: 'id' })
createEffect(() => list.map((r) => `${r.id}:${r.done}`).join(' '), (v) => { seen.push(v) })
const act = action(function* () {
setList((l) => { if (l[0]) l[0].done = true })
try { yield Promise.reject(new Error('x')) } catch {}
})
run = () => act()
return d
})
await settle(); await run(); await settle()
dispose()
return seen.at(-1)
}
let last: string | undefined
if (observed) await captureArtifact(async () => { last = await body() }, { scenario: 'probe' })
else last = await body()
expect(last).toBe('a:false')
})
}
for (const observed of [false, true])
it(`createOptimistic(false) reverts after caught failure: captureArtifact=${observed}`, async () => {
const body = async () => {
let read!: () => boolean, run!: () => Promise<unknown>
const dispose = createRoot((d) => {
const [busy, setBusy] = createOptimistic(false)
read = busy
createEffect(() => busy(), () => {})
const act = action(function* () { setBusy(true); try { yield Promise.reject(new Error('x')) } catch {} })
run = () => act()
return d
})
await settle(); await run(); await settle()
const v = read(); dispose(); return v
}
let last: boolean | undefined
if (observed) await captureArtifact(async () => { last = await body() }, { scenario: 'probe2' })
else last = await body()
expect(last).toBe(false)
})
When the attribution engine is on —
captureArtifactfrom@solidjs/diagnostics, or the performance tracks@solidjs/vite-plugin3.0.0-next.46 enables by default undervite dev— an optimistic store write made inside an action that fails is not reverted when the action settles. The same code reverts correctly with attribution off, and reverts under attribution on rc.9.createOptimistic(signal) is not affected. The source kind does not matter (Promise and AsyncIterable both reproduce).Steps (full test below):
createOptimisticStore(() => source, [], { key: 'id' })with one row{ id: 'a', done: false }.action(function* () { setList(l => { l[0].done = true }); try { yield Promise.reject(new Error('x')) } catch {} }).captureArtifact(...), flush, and read the row after settle.Expected:
done === false(the overlay drops at settle; RFC 06).Actual (rc.10, attribution on):
done === true.Results of the test below:
captureArtifacta:true)captureArtifacta:true)createOptimistic(false), attribution offcreateOptimistic(false),captureArtifactRelated symptoms seen in the same scenario family (larger test, not minimized):
OPTIMISTIC_REVERTEDfinding reporting "showed 1; it settled to 1" (same value) underexpectNoDiagnostics;TypeError: Cannot read properties of undefined (reading '_host')during the run.Observation changing runtime semantics also means dev servers with the default performance tracks behave differently from production for optimistic stores.
Environment:
Reproduction