Technical Reference
Developer resources
The project's design documents, specs, and logs live in the repository under docs/.
- Redesign plan Stage-by-stage implementation plan and current status
- Project vision Target audience, core use cases, design principles
- Feature specs Per-feature specs with acceptance criteria
- WASM API contract TypeScript ↔ libdedx interface
- Testing strategy Vitest unit tests, Playwright E2E, WASM mocking
- Deployment GitHub Pages pipeline and CI
- AI changelog Log of all AI-assisted development sessions
Running E2E tests locally
Playwright serves the built app with pnpm preview, so build first:
pnpm build
pnpm test:e2e pnpm build runs node scripts/deploy.cjs via prebuild,
so static/deploy.json is generated automatically — no extra manual step needed.
Dependency management & security policy
- Prefer direct upgrades. If
pnpm auditor Dependabot flags a vulnerability in a transitive dependency, upgrade the direct dependency that pulls it in. Do not add overrides as a first move. - Minimal overrides. Only use overrides as a last resort if no compatible upstream release exists.
- Workspace-level configuration. Define overrides in the root
pnpm-workspace.yamlunder theoverrides:key (notpackage.json'spnpm.overrides) for workspace-wide consistency. - Document overrides. Every override must have a YAML comment directly above it referencing the GHSA ID it mitigates and linking to the upstream issue.
Shareable URL contract
Every calculator and plot view is fully described by its query string, so a URL can be copied,
bookmarked, and shared to reproduce the exact state. The query string follows a formal ABNF-style grammar, implemented as
a Peggy parser that produces an abstract syntax tree. The current schema is v3 (urlv=3), which joins list values with ~ instead of , so messenger and email auto-linkifiers don't truncate a shared link at the first comma. Older urlv=2 links (comma-separated) still load and are rewritten to the ~ form; links using the retired v1 format are no longer parsed and instead show an
"unsupported link" notice with a one-click "load defaults" action.
Grammar (v3, abridged)
query = [pair *("&" [pair])]
pair = extdata-pair / energies-pair / lookups-pair / series-pair
/ mat-elements-pair / entity-list-pair / scalar-pair / unknown-pair
; structured, list-bearing params (list-sep = "~" / "," — see below)
energies-pair = "energies=" energy-item *(list-sep energy-item)
energy-item = number [":" energy-unit-token]
lookups-pair = ("lookups=" / "ivalues=") lookup-item *(list-sep lookup-item)
series-pair = "series=" series-item *(list-sep series-item)
series-item = entity-id "." entity-id "." entity-id ; program.particle.material
mat-elements-pair = "mat_elements=" mat-element *(list-sep mat-element)
mat-element = digits ":" number ; Z:count
entity-list-pair = ("programs=" / "particles=" / "materials=") entity-id *(list-sep entity-id)
extdata-pair = "extdata=" label ":" url ; split on the first ':'
; list-item separator: "~" is canonical (v3+, issue #672); legacy "," accepted on read
list-sep = "~" / ","
; single-valued params (value kept verbatim, then percent-decoded)
scalar-pair = scalar-key "=" value
scalar-key = "urlv" / "mode" / "particle" / "material" / "program"
/ "eunit" / "uanchor" / "across" / "qshow" / "imode" / "iunit"
/ "istpbranch" / "stp_unit" / "xscale" / "yscale" / …
; forward-compatible catch-all (dropped by the resolver, never re-emitted)
unknown-pair = key ["=" value]
; lexical
entity-id = digits / ("ext:" label ":" id)
number = digits ["." digits] [("e"/"E") ["+"/"-"] digits]
digits = 1*DIGIT How a URL is processed
- Tokenize (
parseQuery): split on raw&/=, percent-decode each component, and build the AST. Pure syntax — no defaults or validation. - Resolve (
resolveCalculatorState/resolvePlotState): duplicate keys resolve last-wins; defaults are applied; advanced-only params are ignored in basic mode; values are validated. - Canonicalize: the URL is rewritten into a deterministic, ordered form so the same logical state always yields the same URL.
Error reporting
Each AST node carries the exact character span it came from, so when a link is malformed or a value is invalid the app can point at the precise spot. Problems are reported as diagnostics with a severity, a message, and a caret underline of the offending text, for example:
…energies=100~200:foo
^^^
Expected an energy unit such as keV, MeV, or MeV/u. Fatal errors (an unreadable link) block the calculation and offer "load defaults"; warnings (e.g. an out-of-range value) drop just the offending part and continue; unknown parameters are dropped silently.
Normative definition (full grammar, semantic rules, conformance vectors): shareable-urls-formal.md.