From 12ac28de4199a8b1cd0ae82ceb0c7cd53d37bb00 Mon Sep 17 00:00:00 2001 From: Anders Hafreager Date: Thu, 1 Oct 2026 19:40:09 +0200 Subject: [PATCH 1/3] Example-library smoke test; fix the examples broken on current LAMMPS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `npm run test:examples` (src/examples.smoke.ts + vitest.examples.config.mts), which runs every example in public/examples/examples.json against the real atomify wasm build the way the app does: the same LammpsAdapter, the same -k on/-sf kk startup args, the same KOKKOS serial-style and group-inspection preprocessing, #/bond directives, per-step snapshot calls, and the final post-run snapshot. Any LAMMPS error, ERROR output line, or snapshot exception fails the test. Not part of `npm test` (it takes minutes and needs the real module). Fixed examples: - deposit_atom, deposit_molecule, pour_2d, pour_2d_molecule, pour_3d: `compute_modify ... dynamic yes extra 0` -> `dynamic/dof yes extra/dof 0`. The old keywords were removed from LAMMPS, which made the deposit examples error out. - benzene: a lone apostrophe in a `print "..."` line ("I'm") is an unmatched quote error once LAMMPS re-parses the print text; reworded. - pour/granular examples also need lammps.js's fix pour KOKKOS patch (next lammps.js release). Group-inspection marker: match __ATOMIFY_GROUPS_READY__ as a line suffix (splitGroupReady). Output that lacks a trailing newline — the new LAMMPS's ReaxFF "Changed valency_val" warning — otherwise glues onto the marker, the include file is never written, and the run fails. Co-Authored-By: Claude Opus 5.5 --- package.json | 1 + .../lammps/deposit_atom/in.deposit.atom | 2 +- .../deposit_molecule/in.deposit.molecule | 2 +- public/examples/lammps/pour_2d/in.pour.2d | 2 +- .../pour_2d_molecule/in.pour.2d.molecule | 2 +- public/examples/lammps/pour_3d/in.pour | 2 +- .../examples/moltemplate/benzene/run.in.npt | 2 +- src/examples.smoke.ts | 324 ++++++++++++++++++ src/utils/lammpsGroups.test.ts | 20 ++ src/utils/lammpsGroups.ts | 17 + src/wasm/lammps.worker.ts | 10 +- vitest.examples.config.mts | 15 + 12 files changed, 389 insertions(+), 10 deletions(-) create mode 100644 src/examples.smoke.ts create mode 100644 vitest.examples.config.mts diff --git a/package.json b/package.json index c782a786..311d7b3e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "test": "vitest", "test:run": "vitest run", "test:ui": "vitest --ui", + "test:examples": "vitest run --config vitest.examples.config.mts", "e2e": "playwright test", "e2e:headed": "playwright test --headed", "typecheck": "tsc --noEmit", diff --git a/public/examples/lammps/deposit_atom/in.deposit.atom b/public/examples/lammps/deposit_atom/in.deposit.atom index c040a7be..1d10352f 100644 --- a/public/examples/lammps/deposit_atom/in.deposit.atom +++ b/public/examples/lammps/deposit_atom/in.deposit.atom @@ -25,7 +25,7 @@ region mobile block 0 5 0 5 2 INF group mobile region mobile compute add addatoms temp -compute_modify add dynamic yes extra 0 +compute_modify add dynamic/dof yes extra/dof 0 fix 1 addatoms nve fix 2 mobile langevin 1.0 1.0 0.1 587283 diff --git a/public/examples/lammps/deposit_molecule/in.deposit.molecule b/public/examples/lammps/deposit_molecule/in.deposit.molecule index 9e74fada..0b3fbb60 100644 --- a/public/examples/lammps/deposit_molecule/in.deposit.molecule +++ b/public/examples/lammps/deposit_molecule/in.deposit.molecule @@ -28,7 +28,7 @@ region mobile block 0 5 0 5 2 INF group mobile region mobile compute add addatoms temp -compute_modify add dynamic yes extra 0 +compute_modify add dynamic/dof yes extra/dof 0 fix 1 addatoms nve fix 2 mobile langevin 0.1 0.1 0.1 587283 diff --git a/public/examples/lammps/pour_2d/in.pour.2d b/public/examples/lammps/pour_2d/in.pour.2d index 1e959a41..dce356a6 100644 --- a/public/examples/lammps/pour_2d/in.pour.2d +++ b/public/examples/lammps/pour_2d/in.pour.2d @@ -42,7 +42,7 @@ compute 1 all erotate/sphere thermo_style custom step atoms ke c_1 vol thermo 1000 thermo_modify lost ignore norm no -compute_modify thermo_temp dynamic yes +compute_modify thermo_temp dynamic/dof yes #dump id all atom 250 dump.pour diff --git a/public/examples/lammps/pour_2d_molecule/in.pour.2d.molecule b/public/examples/lammps/pour_2d_molecule/in.pour.2d.molecule index 970ed8fb..db8b12f5 100644 --- a/public/examples/lammps/pour_2d_molecule/in.pour.2d.molecule +++ b/public/examples/lammps/pour_2d_molecule/in.pour.2d.molecule @@ -49,7 +49,7 @@ compute 1 all erotate/sphere compute Tsphere all temp/sphere thermo_style custom step atoms ke c_1 vol thermo_modify lost ignore norm no temp Tsphere -compute_modify Tsphere dynamic yes +compute_modify Tsphere dynamic/dof yes thermo 1000 diff --git a/public/examples/lammps/pour_3d/in.pour b/public/examples/lammps/pour_3d/in.pour index aef0c079..f50e29d5 100644 --- a/public/examples/lammps/pour_3d/in.pour +++ b/public/examples/lammps/pour_3d/in.pour @@ -35,7 +35,7 @@ compute 1 all erotate/sphere thermo_style custom step atoms ke c_1 vol thermo 1000 thermo_modify lost ignore norm no -compute_modify thermo_temp dynamic yes +compute_modify thermo_temp dynamic/dof yes #dump id all atom 1000 dump.pour diff --git a/public/examples/moltemplate/benzene/run.in.npt b/public/examples/moltemplate/benzene/run.in.npt index 86615bac..6bc5fac1 100644 --- a/public/examples/moltemplate/benzene/run.in.npt +++ b/public/examples/moltemplate/benzene/run.in.npt @@ -50,7 +50,7 @@ unfix fxnve print "---------------------------------------------------------------------------" print "Optional: use short high pressure run to get rid of small bubbles." -print " (In case there are any. I'm not certain there are." +print " (In case there are any. I am not certain there are." print " Later we will restore ordinary pressure.)" print "---------------------------------------------------------------------------" fix fxlan all langevin 298.0 298.0 5000 123456 # temp: 298 K diff --git a/src/examples.smoke.ts b/src/examples.smoke.ts new file mode 100644 index 00000000..d139db2f --- /dev/null +++ b/src/examples.smoke.ts @@ -0,0 +1,324 @@ +// @vitest-environment node +/** + * Example-library smoke test: runs every simulation in + * public/examples/examples.json against the real atomify wasm build + * (lammps.js/wasm-atomify), the way the app does — same LammpsAdapter, same + * startup args, same script preprocessing (KOKKOS serial-style wrapping + + * group inspection), same #/bond directives, same per-step snapshot calls — + * and fails on any LAMMPS error or snapshot exception. + * + * Slow (minutes) and needs the real ~50 MB module, so it is not part of + * `npm test`. Run it with `npm run test:examples` (vitest.examples.config.mts), + * e.g. after bumping lammps.js. Knobs (env vars): + * EXAMPLES_MAX_SECONDS wall-clock budget per example before it is stopped + * like the Stop button does (default 120); errors + * after that point go unnoticed + * EXAMPLES_MAX_RUN cap literal `run N` lengths so every command in long + * scripts is reached within the budget (default 1000; + * 0 disables). Keep it generous: scripts that ramp a + * parameter over a run (fix adapt + ramp()) blow up + * when the run is cut too short (micelle at 200). + * EXAMPLES_FILTER only run examples whose id contains this string + * EXAMPLES_DEBUG echo LAMMPS output live and keep all of it in errors + */ +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +import createModule from "lammps.js/wasm-atomify"; +import type { LAMMPSWeb } from "lammps.js"; + +import { LammpsAdapter } from "./wasm/lammpsAdapter"; +import type { AtomifyWasmModule } from "./wasm/types"; +import { + prepareScriptForSerialStyles, + scriptOptsIntoKokkos, +} from "./utils/kokkos"; +import { + GROUP_INFO_FILE, + GROUP_VARIABLE_FILE, + parseLammpsGroups, + prepareGroupInspection, + splitGroupReady, +} from "./utils/lammpsGroups"; +import { parseBond } from "./utils/parsers"; + +interface ExampleFile { + fileName: string; + url: string; +} +interface Example { + id: string; + title: string; + inputScript: string; + files: ExampleFile[]; +} + +const EXAMPLES_DIR = join(process.cwd(), "public", "examples"); +const MAX_SECONDS = Number(process.env.EXAMPLES_MAX_SECONDS ?? 120); +const MAX_RUN = Number(process.env.EXAMPLES_MAX_RUN ?? 1000); +const FILTER = process.env.EXAMPLES_FILTER ?? ""; +// Beyond the budget the Stop path needs a synced step to act on; a script +// stuck in one long command never reaches one, so give up on it eventually. +const HARD_TIMEOUT_MS = (MAX_SECONDS + 90) * 1000; + +const examples: Example[] = ( + JSON.parse(readFileSync(join(EXAMPLES_DIR, "examples.json"), "utf8")) as { + examples: Example[]; + } +).examples.filter((example) => example.id.includes(FILTER)); + +function capRunLengths(content: string): string { + if (!MAX_RUN) return content; + return content.replace( + /^([ \t]*run[ \t]+)(\d+)\b/gm, + (line, prefix: string, steps: string) => + Number(steps) > MAX_RUN ? `${prefix}${MAX_RUN}` : line, + ); +} + +/** Same transforms as the store's syncFilesWasm. */ +function prepareFile( + content: string, + isMainScript: boolean, + kokkosOptIn: boolean, +): string { + const styled = kokkosOptIn + ? content + : prepareScriptForSerialStyles(content, { isMainScript }); + return prepareGroupInspection(capRunLengths(styled)); +} + +interface Engine { + module: AtomifyWasmModule; + native: LAMMPSWeb; + adapter: LammpsAdapter; + output: string[]; + stepErrors: string[]; + hasRun: boolean; +} + +async function loadEngine(): Promise { + const output: string[] = []; + const stepErrors: string[] = []; + let engine: Engine | undefined; + const printLine = (...args: unknown[]) => { + let text = args.join(" "); + if (process.env.EXAMPLES_DEBUG) console.log("LMP>", text); + const marker = splitGroupReady(text); + if (marker.ready && engine) { + const info = engine.module.FS.readFile(GROUP_INFO_FILE, { + encoding: "utf8", + }); + engine.module.FS.writeFile( + GROUP_VARIABLE_FILE, + engine.adapter.prepareGroupVariables(parseLammpsGroups(String(info))), + ); + if (!marker.before) return; + text = marker.before; + } + engine?.adapter.observeOutput(text); + output.push(text); + }; + const module = (await createModule({ + print: printLine, + printErr: printLine, + })) as unknown as AtomifyWasmModule; + const native = new module.LAMMPSWeb(); + const adapter = new LammpsAdapter(module, native); + adapter.start(); + engine = { module, native, adapter, output, stepErrors, hasRun: false }; + const live = engine; + // Mirrors the worker's streamStep: everything it reads per synced step. + adapter.setStepListener(() => { + try { + const particles = native.syncParticlesWrapped(); + native.syncBondsWrapped(); + native.syncSimulationBox(); + live.adapter.snapshotModifiers(null, particles.count); + native.getCurrentStep(); + native.getRunMode(); + native.getRunStepsDone(); + native.getRunStepsTotal(); + native.getMemoryUsage(); + native.getThermo("spcpu"); + native.getThermo("cpuremain"); + native.getWalls(); + } catch (error) { + if (live.stepErrors.length < 5) { + live.stepErrors.push( + error instanceof Error ? error.message : String(error), + ); + } + } + }); + return engine; +} + +function mkdirp(module: AtomifyWasmModule, path: string) { + let current = ""; + for (const part of path.split("/").filter(Boolean)) { + current += `/${part}`; + if (!module.FS.analyzePath(current).exists) module.FS.mkdir(current); + } +} + +interface ExampleResult { + error: string; + stopped: boolean; + steps: number; + seconds: number; +} + +async function runExample( + engine: Engine, + example: Example, +): Promise { + const { module, native, adapter } = engine; + const dir = `/examples/${example.id}`; + mkdirp(module, dir); + module.FS.chdir(dir); + native.clearBondDistances(); + native.setBuildNeighborlist(false); + + const files = example.files.map((file) => ({ + fileName: file.fileName, + content: readFileSync(join(EXAMPLES_DIR, file.url), "utf8"), + })); + const inputContent = + files.find((file) => file.fileName === example.inputScript)?.content ?? ""; + const kokkosOptIn = scriptOptsIntoKokkos(inputContent); + for (const file of files) { + if (!file.content) continue; + module.FS.writeFile( + `${dir}/${file.fileName}`, + prepareFile( + file.content, + file.fileName === example.inputScript, + kokkosOptIn, + ), + ); + } + + if (engine.hasRun) adapter.reset(); + engine.hasRun = true; + for (const raw of inputContent.split("\n")) { + const line = raw.trim(); + if (!line.startsWith("#/")) continue; + const bond = parseBond(line.substring(2)); + if (bond) { + native.setBondDistance(bond.atomType1, bond.atomType2, bond.distance); + native.setBuildNeighborlist(true); + } + } + + engine.output.length = 0; + engine.stepErrors.length = 0; + const started = Date.now(); + let stopped = false; + const budget = setTimeout(() => { + stopped = true; + adapter.cancel(); + }, MAX_SECONDS * 1000); + let hardTimer: ReturnType | undefined; + const hardTimeout = new Promise<"hung">((resolve) => { + hardTimer = setTimeout(() => resolve("hung"), HARD_TIMEOUT_MS); + }); + const outcome = await Promise.race([ + adapter + .runFile(`${dir}/${example.inputScript}`) + .then(() => "done" as const), + hardTimeout, + ]); + clearTimeout(budget); + clearTimeout(hardTimer); + const seconds = (Date.now() - started) / 1000; + if (outcome === "hung") { + return { + error: `did not finish within ${HARD_TIMEOUT_MS / 1000}s (no synced step to stop on)`, + stopped, + steps: 0, + seconds, + }; + } + + if (stopped) { + // A stopped run resolves runFile after a grace period while the wasm may + // still be unwinding (calls would return asyncify promises), so don't + // touch this engine again: report it and let the caller load a fresh one. + return { error: "", stopped, steps: -1, seconds }; + } + let error = adapter.getErrorMessage(); + if (error.includes("Atomify::canceled")) error = ""; + if (!error) { + // The worker's final post-run snapshot. + try { + const particles = native.syncParticlesWrapped(); + adapter.snapshotModifiers(null, particles.count); + } catch (snapshotError) { + error = `final snapshot: ${ + snapshotError instanceof Error + ? snapshotError.message + : String(snapshotError) + }`; + } + } + if (!error && engine.stepErrors.length) { + error = `step snapshot: ${engine.stepErrors[0]}`; + } + if (!error) { + const lammpsError = engine.output.find((line) => /^ERROR/.test(line)); + if (lammpsError) error = lammpsError; + } + if (error) { + const tail = engine.output + .slice(process.env.EXAMPLES_DEBUG ? 0 : -8) + .join("\n"); + error = `${error}\n--- last output ---\n${tail}`; + } + return { error, stopped, steps: native.getCurrentStep(), seconds }; +} + +describe.sequential("example library", () => { + let engine: Engine; + const failures: string[] = []; + + beforeAll(async () => { + engine = await loadEngine(); + }, 180_000); + + afterAll(() => { + if (failures.length) { + console.log(`\nFailing examples:\n${failures.join("\n")}`); + } + }); + + it.each(examples.map((example) => [example.id, example] as const))( + "%s", + async (_id, example) => { + let result: ExampleResult; + try { + result = await runExample(engine, example); + } catch (error) { + result = { + error: error instanceof Error ? error.message : String(error), + stopped: false, + steps: 0, + seconds: 0, + }; + } + console.log( + `${example.id}: ${result.error ? "FAILED" : "ok"} ` + + `(${result.seconds.toFixed(1)}s, step ${result.steps}` + + `${result.stopped ? ", stopped at budget" : ""})`, + ); + if (result.error) failures.push(example.id); + if (result.error || result.stopped) { + // A crashed, hung or stopped module poisons later runs; start fresh. + engine = await loadEngine(); + } + expect(result.error).toBe(""); + }, + HARD_TIMEOUT_MS + 120_000, + ); +}); diff --git a/src/utils/lammpsGroups.test.ts b/src/utils/lammpsGroups.test.ts index 46909a44..e262d912 100644 --- a/src/utils/lammpsGroups.test.ts +++ b/src/utils/lammpsGroups.test.ts @@ -3,6 +3,7 @@ import { GROUP_READY, parseLammpsGroups, prepareGroupInspection, + splitGroupReady, } from "./lammpsGroups"; it("reads static and dynamic names from the runtime registry", () => { @@ -23,3 +24,22 @@ it("instruments direct, continued, and conditional runs without touching comment "Atoms # atomic\n1 1 2 3 4\n", ); }); + +it("finds the ready marker alone or glued to unterminated output", () => { + expect(splitGroupReady(`${GROUP_READY}\n`)).toEqual({ + ready: true, + before: "", + }); + expect( + splitGroupReady( + `WARNING: Changed valency_val to valency_boc for X${GROUP_READY}`, + ), + ).toEqual({ + ready: true, + before: "WARNING: Changed valency_val to valency_boc for X", + }); + expect(splitGroupReady("Step Temp")).toEqual({ + ready: false, + before: "Step Temp", + }); +}); diff --git a/src/utils/lammpsGroups.ts b/src/utils/lammpsGroups.ts index 0be36a77..01f61be0 100644 --- a/src/utils/lammpsGroups.ts +++ b/src/utils/lammpsGroups.ts @@ -13,6 +13,23 @@ export function prepareGroupInspection(content: string): string { ); } +/** + * Detect the GROUP_READY marker in one line of engine output. The marker can + * arrive glued to the end of the previous output when that output lacked a + * trailing newline, so match it as a suffix and hand back any text before it. + */ +export function splitGroupReady(text: string): { + ready: boolean; + before: string; +} { + const trimmed = text.trimEnd(); + if (!trimmed.endsWith(GROUP_READY)) return { ready: false, before: text }; + return { + ready: true, + before: trimmed.slice(0, -GROUP_READY.length).trimEnd(), + }; +} + /** Parse the engine's actual group registry, including variable-expanded IDs. */ export function parseLammpsGroups(info: string): string[] { return ( diff --git a/src/wasm/lammps.worker.ts b/src/wasm/lammps.worker.ts index 12ef5a48..e860c7eb 100644 --- a/src/wasm/lammps.worker.ts +++ b/src/wasm/lammps.worker.ts @@ -2,8 +2,8 @@ import { GROUP_INFO_FILE, GROUP_VARIABLE_FILE, - GROUP_READY, parseLammpsGroups, + splitGroupReady, } from "../utils/lammpsGroups"; /** * LAMMPS Web Worker entry. @@ -261,8 +261,9 @@ async function load() { return; } const printLine = (...args: unknown[]) => { - const text = args.join(" "); - if (text.trim() === GROUP_READY && module && adapter) { + let text = args.join(" "); + const marker = splitGroupReady(text); + if (marker.ready && module && adapter) { // Pure JS/MEMFS work inside the print callback; no nested native calls. // LAMMPS executes this generated include after print returns, before run. const info = module.FS.readFile(GROUP_INFO_FILE, { encoding: "utf8" }); @@ -270,7 +271,8 @@ async function load() { GROUP_VARIABLE_FILE, adapter.prepareGroupVariables(parseLammpsGroups(String(info))), ); - return; + if (!marker.before) return; + text = marker.before; } adapter?.observeOutput(text); post({ type: "printed", text }); diff --git a/vitest.examples.config.mts b/vitest.examples.config.mts new file mode 100644 index 00000000..efc33388 --- /dev/null +++ b/vitest.examples.config.mts @@ -0,0 +1,15 @@ +import { defineConfig } from "vitest/config"; + +// Runs the example-library smoke test (src/examples.smoke.ts) against the real +// atomify wasm build. Separate from vitest.config.mts: it needs a plain node +// environment (pthreads via worker_threads), no wasm stub, and minutes of time. +export default defineConfig({ + test: { + environment: "node", + include: ["src/examples.smoke.ts"], + pool: "forks", + fileParallelism: false, + testTimeout: 600_000, + hookTimeout: 300_000, + }, +}); From d8630ffdf40f44e7748aa4c7159a32e9f3429c03 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 09:05:47 +0000 Subject: [PATCH 2/3] Fix example smoke test types after engine refactor Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019ZnTFmiih3xq95b7ioCPY7 --- src/examples.smoke.ts | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/examples.smoke.ts b/src/examples.smoke.ts index d139db2f..06412ff0 100644 --- a/src/examples.smoke.ts +++ b/src/examples.smoke.ts @@ -26,10 +26,9 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import createModule from "lammps.js/wasm-atomify"; -import type { LAMMPSWeb } from "lammps.js"; +import type { LAMMPSWeb, LammpsModule } from "lammps.js"; import { LammpsAdapter } from "./wasm/lammpsAdapter"; -import type { AtomifyWasmModule } from "./wasm/types"; import { prepareScriptForSerialStyles, scriptOptsIntoKokkos, @@ -90,7 +89,7 @@ function prepareFile( } interface Engine { - module: AtomifyWasmModule; + module: LammpsModule; native: LAMMPSWeb; adapter: LammpsAdapter; output: string[]; @@ -101,33 +100,44 @@ interface Engine { async function loadEngine(): Promise { const output: string[] = []; const stepErrors: string[] = []; - let engine: Engine | undefined; + // Set once the module exists; the print callback reads it. + const ref: { engine?: Engine } = {}; const printLine = (...args: unknown[]) => { let text = args.join(" "); if (process.env.EXAMPLES_DEBUG) console.log("LMP>", text); const marker = splitGroupReady(text); - if (marker.ready && engine) { - const info = engine.module.FS.readFile(GROUP_INFO_FILE, { + if (marker.ready && ref.engine) { + const info = ref.engine.module.FS.readFile(GROUP_INFO_FILE, { encoding: "utf8", }); - engine.module.FS.writeFile( + ref.engine.module.FS.writeFile( GROUP_VARIABLE_FILE, - engine.adapter.prepareGroupVariables(parseLammpsGroups(String(info))), + ref.engine.adapter.prepareGroupVariables( + parseLammpsGroups(String(info)), + ), ); if (!marker.before) return; text = marker.before; } - engine?.adapter.observeOutput(text); + ref.engine?.adapter.observeOutput(text); output.push(text); }; const module = (await createModule({ print: printLine, printErr: printLine, - })) as unknown as AtomifyWasmModule; + })) as unknown as LammpsModule; const native = new module.LAMMPSWeb(); const adapter = new LammpsAdapter(module, native); adapter.start(); - engine = { module, native, adapter, output, stepErrors, hasRun: false }; + const engine: Engine = { + module, + native, + adapter, + output, + stepErrors, + hasRun: false, + }; + ref.engine = engine; const live = engine; // Mirrors the worker's streamStep: everything it reads per synced step. adapter.setStepListener(() => { @@ -155,7 +165,7 @@ async function loadEngine(): Promise { return engine; } -function mkdirp(module: AtomifyWasmModule, path: string) { +function mkdirp(module: LammpsModule, path: string) { let current = ""; for (const part of path.split("/").filter(Boolean)) { current += `/${part}`; From 8b98e4628aa73ee6015de1d7ec9749712e2fd385 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 09:07:15 +0000 Subject: [PATCH 3/3] Use plain describe in example smoke test (vitest 5 drops describe.sequential) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019ZnTFmiih3xq95b7ioCPY7 --- src/examples.smoke.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples.smoke.ts b/src/examples.smoke.ts index 06412ff0..66f9ac44 100644 --- a/src/examples.smoke.ts +++ b/src/examples.smoke.ts @@ -289,7 +289,7 @@ async function runExample( return { error, stopped, steps: native.getCurrentStep(), seconds }; } -describe.sequential("example library", () => { +describe("example library", () => { let engine: Engine; const failures: string[] = [];