Files
Niko1221 0b3299a019 apps/host: the owner's UI and the Node App Host
The UI source from the 14 October handoff (src/, public/, index.html, vite and
tsconfig, tests, audit-fixes, design docs) and the App Host (server/*.mjs): model
import and monitor routes, API-provider catalog and streaming (OpenAI chat, OpenAI Responses,
the Messages API), serving, tunnel, tasks, plus the host routes for Time-skip
(server/timeskip.mjs) and the tuning plan/reports. src/lib/time-skip.ts renders a real
job's own record.

The UI design is unchanged. The rebuild routed #/tuning to a new TunePanel; that is
reverted: src/screens/ModelRun.tsx and src/main.tsx are the handoff's again (#/tuning
goes where the handoff sends it), and src/components/TunePanel.tsx and
src/styles/tuning.css, which only that route used, are not carried over. Left out of
git on purpose: dist/ (build output), node_modules, and docs/evidence + docs/checkpoints
(170 MB of screenshots, kept in StrataSplit\evidence-archive).

From the owner's rebuild branch strata/rebuild-2026-10.
2026-10-15 10:24:09 +02:00

56 lines
2.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Runs every recorded flow and reports which ones fail.
*
* node scripts/flows.mjs all flows
* node scripts/flows.mjs chat rail only these
*
* Why this exists: two flows silently stopped testing anything — one waited for
* a button the completed setup screen correctly no longer shows, the other kept
* a CSS class that had been renamed. Both looked like regressions when they were
* finally run, and neither had been exercised in weeks. A suite nobody runs is
* not a suite, so the whole set is one command now.
*/
import { execFile } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { promisify } from 'node:util';
const run = promisify(execFile);
const ROOT = new URL('..', import.meta.url).pathname;
const source = readFileSync(`${ROOT}scripts/flow.mjs`, 'utf8');
const all = [...new Set([...source.matchAll(/flow === '([a-z]+)'/g)].map((m) => m[1]))].sort();
const wanted = process.argv.slice(2).filter((a) => !a.startsWith('-'));
const flows = wanted.length ? wanted.filter((f) => all.includes(f)) : all;
if (wanted.length && flows.length !== wanted.length) {
const unknown = wanted.filter((f) => !all.includes(f));
console.error(`Unknown flow(s): ${unknown.join(', ')}`);
console.error(`Available: ${all.join(', ')}`);
process.exit(2);
}
const failed = [];
for (const flow of flows) {
process.stdout.write(`${flow.padEnd(16)} `);
try {
const { stdout } = await run('node', ['scripts/flow.mjs', flow], { cwd: ROOT, timeout: 180_000 });
const last = stdout.trim().split('\n').filter(Boolean).pop() ?? '';
console.log(last.slice(0, 78));
} catch (err) {
const out = `${err.stdout ?? ''}${err.stderr ?? ''}`;
const reason = /Timeout .*exceeded/.test(out)
? 'timed out waiting for something that is not there'
: (out.trim().split('\n').filter(Boolean).pop() ?? 'failed').slice(0, 78);
console.log(`FAIL — ${reason}`);
failed.push(flow);
}
}
console.log(
failed.length
? `\nflows: ${failed.length} of ${flows.length} failed — ${failed.join(', ')}`
: `\nflows: all ${flows.length} passed`,
);
process.exit(failed.length ? 1 : 0);