Skip to content

Validation

No single command proves a repo this size is perfect. Use layered validation: static checks, package tests, a live app smoke test, and deeper e2e flows when the Docker stack is available.

Fast Local Checks

yarn generate:brand-assets
yarn --cwd app/packages/web tsc
yarn --cwd server workspace @standardnotes/auth-server build
docker compose config
docker compose -f docker-compose.single.yml config
git diff --check

Focused Web Tests

Run changed-area Jest specs directly from the web package:

cd app/packages/web
yarn test --runTestsByPath src/javascripts/Components/Preferences/Search/searchPreferences.spec.ts

Normalized Jest-Instrumented JS/TS Source Coverage

The Pages coverage badge at https://supermarsx.github.io/standard-red-notes/assets/coverage.svg reports normalized Jest-instrumented JS/TS source coverage. It is neither unit-test coverage nor whole-repository coverage. Every Jest spec selected by a package’s normal test command runs, so Jest integration and live specs may contribute covered counters.

The collector owns an explicit app/server Jest workspace inventory and fails on inventory drift, duplicate workspace names, locations or slugs, duplicate report paths, missing reports, malformed reports, and source/report mismatch. Every shard manifest carries the full scope inventory, the exact selected workspace subset, and one report/source inventory per selection. A completed selected manifest may cover only part of a scope. The final merge accepts multiple manifests for the same scope, independently enumerates eligible source files from the checkout, and requires the app/server union to contain every expected workspace and eligible source exactly once. Missing, duplicate, or unexpected workspaces; scope mismatches; incomplete selected manifests; overlapping or duplicate report paths; and missing, unexpected, or duplicate sources are fatal. For each inventory workspace it enumerates src and lib files ending in .ts, .tsx, .js, or .jsx. It excludes test/spec files and directories, declarations, and generated, build, vendor, and fixture directories. Jest’s package-specific coverage exclusions and thresholds are cleared for this descriptive report. For each workspace, the collector loads its real CJS or JSON Jest config through a unique temporary config beside the original, preserves the package settings, and overrides inherited collectCoverageFrom with an empty array. The runner validates a direct Jest package script, removes package-script coverage, config, and worker options, preserves non-owned options such as --no-cache and positional selectors, then invokes yarn exec jest with one authoritative --maxWorkers=1 and the temporary config. The temporary config is removed after success, failure, or timeout; no spawned argument contains collectCoverageFrom. Function, async, and Promise config exports fail as unsupported instead of being evaluated with changed semantics. Jest therefore transforms and reports only files executed or imported by the existing tests, and any Failed to collect coverage diagnostic is fatal. Collection and test failures remain fatal.

Source files missing from Jest’s output are materialized as zero-covered maps directly from their TS, TSX, JS, or JSX text with istanbul-lib-instrument and the required Babel parser plugins. Existing Jest maps are never replaced. After synthesis, every workspace report must contain exactly its eligible canonical source paths once. A successful Jest process that does not emit its raw coverage-final.json fails for every normal workspace. The only exceptions are the explicitly reviewed source-only/no-test inventory entries server/packages/domain-events and server/packages/predicates. The collector verifies that these entries remain in the reviewed inventory and have no package-local test/spec files, then synthesizes every eligible source as zero-covered if Jest emits no raw report. Unexpected, missing, or stale source-only flags fail inventory validation, and each source-only workspace still contributes a non-empty denominator. Parser failures name the unsupported source and fail collection. An inventory workspace with no eligible source must carry an explicit emptySourceReason; an undocumented empty workspace fails.

The current app scope is api, encryption, features, filepicker, files, mobile, models, responses, services, snjs, ui-services, utils, and web. The current server scope is analytics, api-gateway, auth, common, domain-core, domain-events, domain-events-infra, files, home-server, predicates, revisions, scheduler, security, settings, sncrypto-node, syncing-server, time, and websockets.

The source denominator excludes non-Jest app workspaces (clipper, desktop, filepicker/example, icons, releases, sncrypto-common, sncrypto-web, styles, and toast), non-Jest server workspaces (grpc and websocket-gateway), and the root mcp and openclaw workspaces.

The excluded non-Jest suites are the Playwright suites under e2e/; desktop’s AVA suite; the app and sncrypto-web Mocha/Chai browser harnesses; the websocket-gateway and OpenClaw Vitest suites; websocket-gateway’s direct Node and Playwright e2e scripts; MCP’s run-e2e.mjs; and the coverage tool’s own node:test suite. Native code, Docker health/integration behavior, backup and restore drills, and uninstrumented runtime paths are also outside this metric.

Install the three independent Yarn projects, test the coverage infrastructure, and reproduce the CI report with:

npm install --global corepack@0.35.0
corepack enable
yarn install --immutable
Push-Location app
yarn install --immutable
Pop-Location
Push-Location server
yarn install --immutable
Pop-Location
yarn test:coverage-tools
yarn coverage

collect enforces a hard timeout for each workspace and terminates its full process tree on Windows and Linux. The default is 900000 ms (15 minutes), which the app-core shard uses; the server shard also passes 900000 ms explicitly. The isolated app-web shard passes 1800000 ms (30 minutes) explicitly. With the enforced one-worker command line, the verified Web run passed in 1607.488 seconds (26m47.488s) with about 3.4 GB observed Jest working set. This current bounded result supersedes the earlier 17m44s and roughly 7.5 GB observation from the old worker configuration, so the runner must retain its 30-minute timeout. Override the default with --timeout-ms <milliseconds> or COVERAGE_WORKSPACE_TIMEOUT_MS; the CLI option takes precedence. Parallel failures are printed as an ordered workspace ledger. Every collector explicitly limits the pool to two active Jest processes and each Jest process to --maxWorkers=1, with no more than one worker child per process.

yarn coverage writes ignored reports and the JSON summary to coverage/. The tracked docs/assets/coverage.svg contains the most recently verified numeric baseline, currently 40.7% from the preceding inventory. A local full run recalculates and overwrites it, and CI regenerates it before building and publishing the Pages site.

Aggregation uses istanbul-lib-coverage. Every source path is canonicalized to one repository-relative path, and any source overlap is rejected before file coverage is added. For each metric, the reported percentage is 100 * sum(covered counters) / sum(total counters) over the validated union, rounded to one decimal place. Percentages are never averaged across reports, workspaces, shards, or scopes. A 0/0 metric is reported as n/a. The badge displays the lines result; the generated JSON summary retains statements, branches, functions, and lines.

In CI, three isolated matrix entries run in parallel after installing the root instrumentation tooling and the selected scope’s own lockfile. app-core collects the 12 expected app workspaces other than packages/web; app-web collects only packages/web; and server collects all 18 expected server workspaces. Each entry uploads its completed manifest and reports as a separate artifact. The build job downloads all three artifacts and validates their union before aggregation, badge generation, and the Jekyll build. Pull requests and manual runs execute those checks. Only a non-PR run on main uploads and deploys the Pages artifact; the deploy job alone receives pages: write and id-token: write. Repository Settings > Pages > Build and deployment > Source must be set to GitHub Actions. GitHub Camo may briefly continue showing an older numeric badge after Pages publishes the replacement image.

The workflow action majors were checked against their official repositories on 2026-07-15: checkout@v7, setup-node@v7, upload-artifact@v7, download-artifact@v8, configure-pages@v6, jekyll-build-pages@v1, upload-pages-artifact@v5, and deploy-pages@v5. All JavaScript actions in that set use Node 24; jekyll-build-pages is a Docker action, so there is no unavoidable legacy Node runner warning to document.

Live App Smoke

Start the app stack:

docker compose -f docker-compose.single.yml up -d --build

Then run the e2e app-open smoke:

cd e2e
npm install
npm run install-browser
$env:APP_URL = "http://localhost:3001"
npm test -- app-opens.spec.ts --project=chromium

README Screenshot

The README screenshot is generated from the actual running app:

$env:APP_URL = "http://localhost:3001"
npm --prefix e2e run screenshot:readme

The capture entry point opens the live app, seeds local demo notes through the real in-page application surface, and writes a deterministic 1440 × 900 docs/assets/readme-screenshot.png. Before replacing the file, it resolves every target in docs/_data/feature_screenshots.json, requires that exact control to be visible, and checks that the numbered marker points near the control’s live bounds. It fails instead of publishing a login screen, incomplete shell, stale selector, or decorative marker with no matching UI.

Every additional feature state gets its own capture entry and unique PNG in the manifest. Multiple annotated crops may intentionally share one capture only when all controls are simultaneously visible in that exact state; separate capture entries are forbidden from silently reusing the same asset.

The manifest also binds the committed PNG to its SHA-256, the historical source commit and commit timestamp, a machine-readable list of states the image does not prove, and bounded claims for each crop. The current historical capture records liveLocatorRevalidated: false: its controls are visually present, but the expanded locator manifest has not been rerun against a connected live app. Only a new successful live capture may upgrade that status.

Validate the committed image and every documentation crop without launching a browser:

yarn docs:screenshots

That gate checks the versioned manifest, unique capture assets, reproducible capture entry points, PNG and viewport dimensions, source references, SVG dimensions, accessible text, crop bounds, exact CSS/text/role locators, marker coordinates and descriptions, unique feature IDs, and contextual coverage in onboarding, client-platform, and in-app guidance. It always checks the current asset digest. With complete Git history it also checks that the recorded source commit contains the exact same PNG and timestamp; shallow source archives skip only that historical lookup. This offline gate does not claim it reran live locators. The onboarding evidence gate additionally binds each reviewed crop to its exact section, pixel-boundary disclaimer, caption, view box, bounded claims, and numbered target text so a closed or empty UI state cannot be moved beside a claim about an open or populated state.

Deeper End-to-End Gates

Use the heavier tests when changing sync, IndexedDB materialization, search, tag references, or editor persistence:

cd e2e
npm test -- encryption-data-safety.spec.ts --project=chromium
npm test -- correctness.spec.ts --project=chromium
npm test -- stress-notes.spec.ts --project=chromium
npm test -- stress-sync.spec.ts --project=chromium

encryption-data-safety.spec.ts is the focused safety gate: it registers real accounts, creates and syncs a note, verifies the HTTP API and MariaDB items, users, and any matching revision rows do not contain note plaintext, confirms a fresh client can decrypt the item, checks cross-account isolation, and verifies delete tombstones clear encrypted payload fields.

These require a healthy local app/server and take materially longer than the smoke tests.

Operations Drills

Use these after database, Redis, Docker, signup-safety, rate-limit, or production-config changes:

$env:APP_URL = "http://localhost:3001"
npm --prefix e2e run test:ops-load
node scripts/verify-backup-restore.mjs

test:ops-load drives real browser clients against the running Docker stack, pushes encrypted notes, pulls them from parallel clients, churns Redis with parallel SET/GET/INCR workers, and checks MariaDB persistence. Scale it with OPS_LOAD_NOTES, OPS_LOAD_CLIENTS, OPS_REDIS_WORKERS, and OPS_REDIS_OPS_PER_WORKER.

verify-backup-restore.mjs proves the MariaDB logical backup path by restoring the dump into a temporary database and comparing table lists, row counts, and checksums before dropping the temporary database.