Skip to content

Verification and local development

Install Node 24.14.0 (.nvmrc; tests run from their TypeScript sources through Node’s type stripping), Git, tar and zip. uv runs ONAB from its published wheel (/srv/artifacts/onab/latest on the construction machine, or EVK_ONAB_WHEEL). npm ci installs integrity-pinned development tools from the npm registry; nothing is vendored (ADR 0009).

Terminal window
npm ci --ignore-scripts
npm run verify
npm run onab:doctor
npm run onab:verify
npm run onab:trial

Native verify is the fast gate: Biome formatting and lint (biome.jsonc) plus Markdown/HTML whitespace, EVK’s own source rules, strict types, requirement links, boundaries, audited dependencies, unit/property tests, the demo build, API docs, documentation samples and clean packed-consumer installation.

npm run onab:verify runs the pinned ONAB 0.6.0 (ADR 0009). It first builds what ONAB reads: the packed library, docs, demo pages, extensions, the site’s content and types, and the ops environment. It then checks the requirement contract and runs each project’s prescribed tools: Biome, tsc, TypeDoc, one node --test over every *.test.ts file (unit, HTTP browser, isolated-document, Chromium and Firefox extension suites) with JUnit proof, the site’s tests, and ruff, basedpyright, pytest and pdoc for ops. build/onab/run.json records every command’s outcome and each requirement’s proof; it is the list of automated gates. Browser-driving test files take turns through a cross-process slot (EVK_BROWSER_SLOTS, default 1; integration/src/ts/browser-slots.ts), because ONAB offers no concurrency setting and parallel software-GL browsers time out on a busy machine. Nothing claims browser or physical acceptance that was not run. Unit tests are Node built-in tests with seeded property schedules.

To run one suite by hand: npm run suite:browser, suite:memory, suite:extensions or suite:firefox. They are named suite:*, not test:*, because ONAB’s test run already covers those files and would run any test:<suite> script a second time.

For browser execution, provide an installed Chromium/Chrome executable. Do not assume the shipped Playwright development copy downloaded a browser.

Terminal window
EVK_BROWSER_EXECUTABLE=/usr/bin/chromium npm run suite:browser

The HTTP browser suite shares 20 inherited React search scenarios and 24 new reading/DOM-map scenarios with the isolated-document suite, and retains six legacy scenarios (50 total). It starts its own localhost server, checks actual interactions and writes screenshots/metadata under build/reports. A missing executable fails explicitly; no automatic skipped success. Narrow Chromium emulation is not a real iPhone, Android or assistive-technology test. Native iOS/Android, Firefox/Safari, watch-center viewing distance and actual backend checks remain later/environment-specific obligations.

Terminal window
npm run dev

Open the printed localhost address. The app uses only synthetic data and no production backend. Normal, failure, partial, race and context scenarios use real core/controller paths. In the race scenario, the final query must remain archive after every delayed request finishes. The page’s search box is kept mounted across input changes. /reading.html exercises reading/evidence and the real static DOM map. /gallery.html exercises independently controlled React instances; /legacy.html retains the initial native-DOM application.

npm run format writes deterministic formatting. Other verify commands do not modify tracked source. Generated artifacts/evidence are ignored by Git but retained in the separate release-evidence archive. Delete stale build outputs before changing test source layouts. Current timestamps/commit/tool conditions are facts; manual review is a distinct pending record.

The ordinary HTTP browser suite remains required. During construction, the managed browser rejected localhost navigation with ERR_BLOCKED_BY_ADMINISTRATOR. No policy was disabled. EVK_BROWSER_EXECUTABLE=/usr/bin/chromium npm run suite:memory supplies an additional isolated-document suite: it loads the built modules into a blank browser document after the real TypeScript compiler transforms their module format. It exercises the same core, adapter, component and scenario source. It does not test HTTP serving, ESM asset URLs, HTTP CSP, storage origins or physical devices. Its separately named results cannot stand in for the blocked ordinary browser suite.

npm run check:consumer builds and installs the actual core/React/DOM-map tarball into a fresh temporary application. It checks ESM/TypeScript consumption and SSR-safe imports, rather than monorepo source aliases. Temporary consumer manifests live outside EVK to avoid accidental ONAB project discovery.

The same built application bundle runs in both browser routes, on genuine React 19.3 from the registry. Memory-route reports record runtime errors and network requests; zero hidden requests is asserted. The tests are real Node test bodies with Playwright interactions, not test names substituted for implementation. Browser touch emulation, synthetic composition events, forced colors and narrow viewports remain narrower than real devices or assistive technology.

The packed-consumer gate first installs EVK without React and proves core independence. Then, for React 18.3.1 and 19.3.0 in turn, each in a clean project, it installs genuine React and @types/react and checks four things: component exports and element composition, SSR-safe imports, compilation against the real declarations, and that EVK pulls in no renderer. It finally adds react-dom and server-renders with renderToString, failing on any React warning. Client hydration runs in the browser suites on React 19 only.

The unit/property suite contains 77 tests: all 55 inherited tests plus 18 text/offset/anchor tests and four resource/identity-race tests. The browser suite’s 24 new cases include the full reading loop; overlaps and native source copy/link behavior; exact/re-anchored/ambiguous/unavailable outcomes; profiles, image opt-in/failure and withdrawal; pagination; modal/desktop focus; profile races; inaccessible/malformed current sources; and seven real DOM-map cases.

The explicitly permitted image-failure fixture is locally intercepted by the test runner. Other unexpected network requests and runtime exceptions fail the isolated reading suite. This does not certify browser HTTP security policy. The existing served demo keeps a strict self-only CSP; hosts permitting external images must set their own compatible resource/CSP policy.

The reusable offset converter lazily builds one code-point index per exact text operation, avoiding repeated whole-text conversions for a batch of annotations. Maximum-size smoothness, graph rendering and incremental mutation performance remain separately measured later requirements.

The graph unit suites exercise the private model, solver and worker runtime, not only exported type shapes. Shared graph scenarios run actual WebGL2, pointer actions, native Worker computation and existing evidence UI. The isolated-document runner replaces only the worker transport URL with a Blob containing the exact built script. Normal HTTP execution remains a separate command and required acceptance route.

On this environment, Xvfb with EVK_GRAPH_SOFTWARE_GL=1 was necessary to initialize software ANGLE. No browser policies or security-lowering flags are disabled. Run xvfb-run -a env EVK_BROWSER_EXECUTABLE=/usr/bin/chromium EVK_GRAPH_SOFTWARE_GL=1 npm run suite:memory. Without a working GL backend the rendering tests should fail rather than silently skipping to a fake canvas. The explicitly injected unavailable-graphics test separately verifies fallback navigation.

Run npm run bench:graph for CPU physics and bench:graph:browser with the same browser environment for frame, picking, query-probe and resource measurements. The probe reports the full supplied topology and framebuffer policy. It is not a benchmark of remote retrieval, physical touch latency, or the uninstalled Sigma/G6 stacks. Retain unmet targets and identify their hardware/software conditions.

integration/src/ts/highlighter-scenarios.ts holds 28 shared scenarios that drive highlighter-harness.ts, an adversarial host page with real links, form controls, host click handlers (one of which calls preventDefault), private and editable regions, open and closed shadow roots, and same-origin and sandboxed frames. The scenarios run twice:

  • npm run suite:browser serves the built pages behind the dev server’s strict CSP. The highlight stylesheet is blocked, so this route proves the overlay renderer and the policy-detection path. Three more page scenarios drive the React sidebar (/highlighter.html, including the candidate chooser and keyboard-only actions) and the non-React navigator (/highlighter-standalone.html).
  • npm run suite:memory loads the same bundle into a policy-free blank document. That exercises native CSS Custom Highlights, including a foreign CSS.highlights entry that must survive detach.

Both routes also remove highlightsFromPoint and the caret APIs to force the caret and geometry fallback paths, check the page listener inventory through the DevTools protocol after detach, record every network request (none is allowed), run a seeded random-mutation property test (40 seeds) that compares the live index with a fresh static canonical map after each repair, and keep four regression scenarios for independent-review findings (changes during the first build, a version declared right after a change, hidden ancestors, forged ownership markers, shadow-root styles and links, and layout shifts above the root).

The 250,000-character / 5,000-annotation stress scenario asserts correctness only: all annotations painted, one unit rebuilt per one-paragraph edit, the exact hit. It writes timings to build/reports/highlighter-{browser,memory}/stress-*.json. Those timings are recorded, not gated; ADR 0005 lists the known gaps, including about 14 s of re-anchoring after a live edit in this fully ambiguous fixture. Chromium touch emulation and headless layout are not real devices, screen readers, Firefox or Safari.

Extensions, workspace and presentation (0.6)

Section titled “Extensions, workspace and presentation (0.6)”
  • npm run suite:extensions builds build/extensions/, then loads the shipped Chromium extension unpacked into Chrome for Testing (persistent profile, headless). The ten Chromium scenarios cover:

    • the packaged-manifest review;
    • page activation → panel search → page emphasis;
    • profile → graph → relation evidence;
    • frame sessions;
    • navigation retirement and stale panel messages;
    • tab isolation;
    • forged and malformed port messages;
    • a real service-worker stop and reconnect (DevTools protocol ServiceWorker.stopAllWorkers);
    • denied access on localhost;
    • panel reopen.

    The panel is opened as panel.html?tab=<id>, because the native side-panel chrome and the keyboard shortcut cannot be automated. An eleventh check reviews the Firefox package structurally. Firefox browser behavior is not run here: there is no Firefox binary, only a snap stub.

  • presentation.browser.test.ts drives two real windows (operator and audience) over BroadcastChannel with the dev server’s CSP. It covers publish, private inspection, present selection, hold/release, graph pins/camera/scale, the frozen audience graph, reset, forged/foreign/replayed messages, and audience/operator reloads.

  • workspace-surfaces.browser.test.ts covers:

    • desktop separator keyboard resizing with minimums;
    • query and physics invariance;
    • collapse;
    • view tabs with reading-position preservation;
    • a search-only host;
    • two independent instances;
    • opt-in layout memory;
    • phone portrait and landscape with touch emulation (sheet, focus return, full-screen graph, visualViewport sizing);
    • the surfaces hub.
  • Node unit tests add core presentation/envelope cases and extension-router.test.ts (router rules with fake ports; fixture-anchor consistency).

Real devices and the physical watch-center room are not automated: see docs/checklists/.

Integration, packaging and assurance (0.7)

Section titled “Integration, packaging and assurance (0.7)”

npm run ci runs every gate below in order and records each one separately as passed, failed, blocked (a named missing prerequisite) or not-run. It writes build/reports/ci/manifest.json and summary.md:

  • the exact commit, and whether the tree was clean for the whole run;
  • lock and vendor hashes;
  • Node, npm, browser and Python versions, and CPU;
  • each command, its log, duration and test counts.

Physical and manual obligations are listed too, as not-run or blocked on the owner, and never count as passed. Exit status is 1 on any failure, 2 if a required gate was blocked, otherwise 0. --only=<ids> runs a subset and reports partial-run. --bench adds the benchmarks, which record measurements but never gate.

Terminal window
export EVK_BROWSER_EXECUTABLE=~/.cache/ms-playwright/chromium-1243/chrome-linux64/chrome # Chrome for Testing
export EVK_FIREFOX_EXECUTABLE=/path/to/firefox/firefox # Firefox 128+
EVK_GRAPH_SOFTWARE_GL=1 npm run ci # headless Linux: browser-driving gates run under xvfb-run automatically
GateCommandWhat it checks
onab-verifynpm run onab:verifyThe single automated gate (gates.json). ONAB 0.6.0 checks the requirement contract, then runs Biome, tsc, TypeDoc, every *.test.ts with proof (unit, HTTP browser including the three suites below, isolated document, Chromium and Firefox extensions), the site’s checks, ops’ ruff, basedpyright, pytest and pdoc, and test:checks (below). Record: build/onab/run.json
test:checks (inside onab-verify)npm run test:checksEVK’s own checks: whitespace, EVK source rules, requirement links, boundaries, licenses, API docs and doc samples, packed consumers on React 18.3 and 19 including server rendering, the packed tarball in a browser without React, ops mypy, inventory, SBOM, and last the secret scan
onab-doctor, onab-trialnpm run onab:doctor, onab:trialONAB 0.6.0 diagnosis and field-trial evidence
bench-*npm run bench:graph, bench:graph:browser, bench:highlighterRecorded measurements for this machine, never gates (npm run ci -- --bench)

npm run verify stays the fast developer check (formatting, lint, types, unit tests, docs samples and packed consumers). The release runs the same onab:verify in CI, with pinned Chrome for Testing and Firefox (ops/src/py/evk_ops/browsers.py). The manual and physical obligations are listed in gates.json and reported by every run, never counted as passed.

The three HTTP suites new in 0.7:

  • accessibility.browser.test.ts runs axe-core (WCAG 2.1 A/AA) on every surface after exercising it. Findings go to build/reports/accessibility/axe-findings.json.
  • privacy.browser.test.ts instruments network, storage and cookie APIs on every surface. It fails on any cross-origin or programmatic request, and on any storage write the user did not opt into.
  • journeys.browser.test.ts runs owner journeys 1 and 2 end to end. Journey 3 (operator and audience) runs in presentation.browser.test.ts. See docs/guides/journeys.md for the journey-to-test map.

axe-core is a development-only test dependency (MPL-2.0, allowed only for test-only lock entries and never referenced by runtime source). Automated accessibility and privacy checks do not replace screen readers or real devices: see docs/checklists/.

Flaky loads on machines with network interface churn. Chromium cancels in-flight loads with net::ERR_NETWORK_CHANGED when interfaces change, even for 127.0.0.1. During construction, Docker veth churn caused about 1 failed page load in 60. Such a failure is reported as a failure. Rerun the gate on a quieter machine rather than adding retries.