# Regex Tools

Regex Tools is a production-oriented, local-first browser workbench for
developing, understanding, testing and applying regular expressions. It is an
independent static React/Vite application and a module of add·ideas Toolbox.

Patterns, replacement templates, test text and project files stay in the
browser. There is no account, backend, upload, remote execution, telemetry,
runtime CDN, remote font, or cloud-save service.

## Current release — 0.4.0

The v0.4.0 release contains twelve selectable, executable profiles:

- **JavaScript (ECMAScript)** — the current browser's native `RegExp`, with
  ECMAScript 2025 syntax supplied by regexpp 4.12.2;
- **PCRE2** — official standalone PCRE2 10.47 as a self-hosted 8-bit
  WebAssembly pack;
- **PHP** — actual PHP 8.5.8 `preg_*` in `@php-wasm/web-8-5` 3.1.46; this
  runtime reports PCRE2 10.44 and is deliberately distinct from the standalone
  PCRE2 10.47 profile;
- **Perl** — actual Perl 5.28.1 in WebPerl 0.09-beta, prominently identified as
  a legacy beta compatibility target rather than current desktop Perl;
- **Python** — CPython 3.14.2 `re` in self-hosted Pyodide 314.0.3;
- **Ruby** — CRuby 4.0.0 `Regexp` in the minimal ruby.wasm
  2.9.3-2.9.4 runtime;
- **Java** — TeaVM 0.15.0's `java.util.regex` class-library implementation;
- **C++** — Emscripten 6.0.4 libc++ `std::wregex`, using the C++ modified
  ECMAScript grammar by default and exposing the standard alternate grammar
  choices;
- **Go** — Go 1.26.5 standard-library `regexp` with RE2 syntax;
- **Rust** — the Rust `regex` crate 1.13.1, built with rustc 1.97.1;
- **.NET** — .NET 10.0.10 `System.Text.RegularExpressions` in browser
  WebAssembly with invariant-culture execution; and
- **Scala compatibility** — Scala/JVM regex syntax and replacement semantics
  through the same TeaVM `java.util.regex` engine as the Java profile. No Scala
  runtime or Scala `Regex` wrapper API is shipped.

Every profile executes the named runtime. Java is TeaVM class-library code,
not OpenJDK. Scala is an explicitly labelled Java-compatibility profile, not a
relabeled Scala engine.

- Syntax acceptance: `@eslint-community/regexpp` 4.12.2, ECMAScript 2025
  grammar; application-owned structural explanations are partial and
  feature-matrix tested.
- ECMAScript native and editor offsets are UTF-16 code units.
- PCRE2 execution and substitution: pinned official PCRE2 10.47 compiled to a
  self-hosted 8-bit WebAssembly pack with UTF/UCP always enabled.
- PCRE2 native UTF-8 byte ranges are retained and normalized to exact editor
  UTF-16 ranges; inputs with lone surrogates are rejected instead of rewritten.
- PCRE2 match-step, depth and heap limits are configurable inside hard caps.
- PCRE2 automatic callouts are copied through a separate ABI operation and
  displayed from a dedicated killable trace worker under 50,000-event and
  10 MiB serialized-data hard caps.
- PHP compilation and match selection use actual `preg_match()` in PHP 8.5.8.
  A fixed byte-bounded expander reproduces PHP replacement tokens and is
  checked against native `preg_replace()` at load. PHP returns native UTF-8
  byte ranges and reports its own PCRE2 10.44 identity.
- The legacy Perl bridge passes user values as JSON data to fixed application
  code, disables runtime regex evaluation, rejects code assertions and exposes
  a pre-tokenized, deliberately bounded replacement grammar. Replacement bytes
  stream from native spans to a separate fixed MEMFS file instead of being
  materialized or JSON-escaped; the worker validates the exact bounded UTF-8
  file before returning it. Its prebuilt WebPerl loader has a dormant optional
  interoperability `eval` site; the fixed engine path is verified without broad
  CSP `'unsafe-eval'`.
- Python compilation, matching and replacement use the bundled CPython `re`
  module. Native code-point ranges are retained and normalized to exact editor
  UTF-16 ranges. Bounded replacement UTF-8 crosses the JSON bridge as canonical
  base64; the adapter validates the declared byte count, cap and UTF-8 before
  exposing output.
- Ruby compilation and matching use CRuby's `Regexp`; native `String#gsub`
  and `String#sub` block iteration drives replacement matching, while a fixed
  byte-bounded implementation of CRuby 4.0.0's `rb_reg_regsub` template
  grammar produces the output prefix. Native character ranges are retained as
  Unicode code-point offsets. Requests and replacement output use separate
  bounded files in the worker-local WASI memory filesystem; only bounded
  metadata returns as inert JSON, avoiding both output escaping amplification
  and ruby.wasm's JavaScript-eval conversion path.
- Java compilation, matching and replacement use TeaVM's
  `java.util.regex.Pattern` and `Matcher`. Native ranges are UTF-16 code units.
- TeaVM 0.15.0 replacement supports its actual single-digit `$n` subset.
  `$12` is therefore `$1` followed by literal `2`; Java SE `${name}`
  replacement is reported as unavailable rather than presented as OpenJDK
  behavior.
- C++ compilation and matching use libc++ `std::wregex`; a fixed byte-bounded
  formatter reproduces `match.format` grammar and checks parity with that
  native API at load. Its 32-bit `wchar_t` ranges are Unicode code points.
- Go and Rust return native UTF-8 byte offsets from their real runtimes. Their
  fixed bounded expanders are parity-checked against `Regexp.ExpandString` and
  `Captures::expand`, then return output through predictable base64 envelopes.
  Their adapters measure exact encoded request bytes, and their native JSON
  caps cover worst-case escaping for every accepted input field.
- .NET returns native UTF-16 offsets and executes with a bounded native regex
  timeout in addition to the killable worker boundary.
- Python, Java and all eight v0.4.0 community additions use deliberately
  partial application-owned lexical syntax providers; their actual runtime
  compiler remains authoritative.
- Parsing and execution run in separate workers.
- Actual execution can be terminated by killing its worker.
- The worker is recreated after timeout, crash or cancellation.
- ECMAScript capture ranges use the engine's `d` indices result; every other
  profile returns its native range records through a bounded fixed bridge.
- Named, numbered, optional, empty and repeated-final captures are represented.
- Match, replacement, typed list/extraction, corpus/apply and unit-test modes
  are available.
- ECMAScript-specific advisory static risk findings, bounded dynamic growth
  probes and cold/warm benchmarks run locally; timing and growth execution is
  isolated in a separately killable worker.
- ECMAScript and PCRE2 can be compared through two exact, independently timed
  requests without translating patterns, flags or replacements. Incomplete or
  rejected sides are explicitly not comparable.
- The reviewed PCRE2 C17 generator emits exact UTF-8 byte arrays, configured
  limits and error handling for the PCRE2 10.47 8-bit API.
- A cancellable, deterministic subject minimizer verifies saved-test failures,
  capture-range failures, exact timeouts and ECMAScript/PCRE2 mismatches against
  the selected real workers under explicit evaluation and wall budgets.
- Deterministic ECMAScript test-case generation samples the normalized AST from
  an explicit seed, reports structural coverage and unsupported constructs, and
  labels cases only after verification by the selected real engine worker.
- Project JSON import/export and optional IndexedDB save are available.
- Interactive test text is not persisted by default.

No licensed parser is used, referenced at runtime, or required to build the
project. This is the community build and will grow flavour by flavour using
reviewed open-source syntax providers and actual named engines.

## The two trees

The **explanation tree** is a deterministic structural explanation generated
from the application-owned normalized syntax tree. It is not an engine trace.

The **extraction tree** contains actual engine matches and captures. Its
hierarchy uses syntactic capture nesting while preserving actual engine ranges.
ECMAScript exposes only the final retained capture of a repeated group; Regex
Tools says so and never invents capture history.

Browser ECMAScript exposes no actual engine trace. PCRE2 has a separate bounded
automatic-callout viewer. Its event fields are reported by PCRE2, while
“forward”, “same position” and “apparent backtrack” are visibly derived only
from adjacent reported subject positions. The callout stream is not described
as a complete record of every internal engine action.

## Workbench modes

- **Match** — live highlighting, extraction tree and bounded capture table.
- **Replace** — bounded profile-authoritative substitution for all twelve
  executable profiles; the legacy Perl profile uses its documented fixed
  non-executable template grammar, and profiles that use fixed bounded
  expanders identify and parity-check them against their named runtime;
  output completeness is reported separately from bounded per-match
  original/result, changed-range and token-contribution previews that
  synchronize the replacement, pattern, subject and output editors.
- **List** — non-executable typed templates with text, CSV, JSON and NDJSON
  export; export is disabled when engine collection or bounded rendering is
  incomplete.
- **Corpus / Apply** — bounded multi-file UTF-8 and pasted-text input processed
  sequentially in killable workers. Whole-document and independent-line
  semantics are explicit; line apply preserves original separators. Progress,
  cancellation, bounded capture samples, replacement previews, per-document
  exact/partial summaries, content-free JSON/CSV/NDJSON reports and
  privacy-gated exact-output ZIP/downloads are available. Original files are
  never modified.
- **Unit tests** — editable and cloneable versioned presence, count, full-match,
  capture, replacement, timing and expected-timeout assertions. Capture a
  complete current result, select only cases to rerun, filter failures, and
  import/export a standalone validated JSON suite; execution stays in isolated
  workers.

The auxiliary **PCRE trace**, ECMAScript **Analysis**, **Compare & code**,
**Generate cases**, **Minimize** and **Format** workspaces are separate from
normal matching, replacement, tests and corpus execution. Trace collection is
never included in benchmark measurements.
Comparison retains native ranges while aligning actual results by normalized
editor UTF-16 ranges. Agreement is described only as evidence for the current
subject, never as proof of pattern equivalence.
Case generation is bounded sampling, not a proof of language coverage. Version
1 supports only a complete accepted ECMAScript normalized AST; the partial
PCRE2 provider is reported as unavailable rather than passed through or
relabeled. See
[`docs/GENERATED_CASES.md`](docs/GENERATED_CASES.md).
Minimization verifies every accepted candidate against the exact selected
worker request and claims only local minimality under its documented
Unicode-scalar transforms; see
[`docs/MINIMIZATION.md`](docs/MINIMIZATION.md).
Formatting is currently ECMAScript-only and changes only parser-reported
literal slash/control representations. Apply requires independent reparse,
actual-engine current-subject/replacement comparison, every applicable exact
unit test and explicit confirmation; see
[`docs/FORMATTING.md`](docs/FORMATTING.md).

## Flags and iteration

Pattern and flags are stored separately; delimiters are presentation only.
Each profile exposes only its reviewed allowlist, and application-level `g`
means bounded iteration where the underlying API has no compile-time global
flag. PHP delimiters/modifiers, Go and Rust inline options, C++ grammar
selection, .NET options and the legacy Perl character-set modes are mapped by
their own adapters rather than reused across profiles. Standalone PCRE2 always
uses UTF/UCP; PHP controls UTF through its exposed `u` modifier and reports its
own PCRE2 version. The optional **Scan all** action adds only an internal
iteration request and does not change saved flags. ECMAScript may add internal
`d` to obtain exact indices without changing match semantics. The exact flag
matrix is in [`docs/FLAVOUR_SUPPORT.md`](docs/FLAVOUR_SUPPORT.md).

Zero-length global iteration advances using the selected Unicode semantics and
cannot loop forever.

## Limits and timeout behavior

Important defaults:

| Limit                           |            Default |
| ------------------------------- | -----------------: |
| Live parse debounce             |             120 ms |
| Live execution debounce         |             220 ms |
| Live execution timeout          |             250 ms |
| Manual execution timeout        |                2 s |
| Advanced maximum timeout        |               10 s |
| Pattern hard limit              | 64 Ki UTF-16 units |
| Interactive subject hard limit  |             16 MiB |
| Corpus documents                |                256 |
| Per corpus document             |             16 MiB |
| Aggregate corpus input          |            256 MiB |
| Independent corpus lines        |            100,000 |
| Aggregate corpus matches        |            100,000 |
| Capture summaries per document  |                 32 |
| Samples per capture             |                  3 |
| Retained applied output         |             64 MiB |
| Applied ZIP                     |             68 MiB |
| Corpus batch wall time          |              5 min |
| Replacement template            | 64 Ki UTF-16 units |
| List template                   | 16 Ki UTF-16 units |
| Per-match replacement preview   |        200 matches |
| Replacement contributions       |              2,000 |
| Maximum matches                 |             10,000 |
| Maximum capture groups          |              1,000 |
| Maximum capture rows            |            100,000 |
| Replacement preview             |             64 MiB |
| Project JSON document           |       32 MiB UTF-8 |
| Standalone test-suite JSON      |       32 MiB UTF-8 |
| In-memory unit-test suite       |       32 MiB UTF-8 |
| Tests per suite                 |              1,000 |
| Retained message per test       |        2 KiB UTF-8 |
| Unit-test suite wall time       |               60 s |
| PCRE2 trace events              |             50,000 |
| PCRE2 serialized trace          |             10 MiB |
| Comparison retained differences |              5,000 |
| Comparison retained alignments  |              2,000 |
| Static analysis traversal       |       20,000 nodes |
| Static analysis findings        |                100 |
| Growth probe steps              |                 24 |
| Analysis aggregate wall time    |               30 s |
| Generated verified cases        |                 48 |
| Generated candidate attempts    |                192 |
| Generation aggregate wall time  |               15 s |
| Generation aggregate candidates |              1 MiB |
| Minimizer subject               |        1 MiB UTF-8 |
| Minimizer candidate evaluations |              2,000 |
| Minimizer aggregate wall time   |               60 s |
| Formatter validation tests      |              1,000 |
| Formatter test wall time        |               60 s |
| Retained minimizer history      |                200 |

Analysis defaults to three warm-up plus 15 measured samples (caps: 100 and
1,000), with a 2-second per-sample worker limit that can be raised to 10
seconds. Growth generation defaults to 1 MiB, preflights the central 16 MiB
subject cap and rejects more than 10,000,000 repetitions. Replacement timing
is skipped above an 8 Mi UTF-16-unit output estimate.

A timeout terminates actual execution. It is reported as **timed out**, never as
“no match”. The next request creates a fresh worker.

Replacement applies exactly the authoritative matches returned under the match
and capture-row limits. If either collection limit is reached, later subject
text remains unchanged and the output is explicitly reported as partial. The
separate output-byte limit reports a truncated output preview.

## Project persistence

Project schema version 1 stores flavour, pattern, flags, options, replacement,
list template, unit tests (including explicit scan-all behavior and supported
per-test timeouts) and selected UI state. Import rebuilds a validated plain
object; it never evaluates code and pauses live execution until review.
Workbench-wide manual timeout changes are not persisted, and unsupported
project resource overrides are rejected.

Interactive test text is excluded from local save and export unless explicitly
enabled. Unit-test definitions are deliberate project data, but their subjects
require separate explicit opt-ins for JSON export and IndexedDB save.
Standalone test-suite JSON likewise omits subjects by default, is schema- and
field-validated on import, and appends at most 1,000 total tests. Add, update,
clone and append-import also preflight the complete in-memory suite against a
32 MiB aggregate bound. Added generated tests retain their validated generator
version, seed, candidate ID and intended outcome; editing one removes that
provenance. Schema v1 can remember that the corpus workspace was selected, but
corpus documents, contents, outputs and results are always ephemeral and
excluded from JSON and IndexedDB. Imports that try to inject those fields are
rejected. Import, export and IndexedDB save enforce one 32 MiB aggregate UTF-8
JSON-document limit in addition to per-field limits. The latest local save is
read through an `updatedAt` index cursor; loading it does not materialize the
complete project store.

## Development

Requirements: Node.js 22 or later and npm 11.

```sh
npm install
npm run dev
```

Quality and release commands:

```sh
npm run typecheck
npm run lint
npm run format:check
npm test
npm run test:conformance
npm run test:browser
npm run engines:build
npm run engines:verify
npm run engines:pcre2:build -- --source-dir /path/to/pcre2 --emcc /path/to/emcc
npm run engines:pcre2:verify
npm run engines:pcre2:install
node scripts/python-engine-pack.mjs node_modules/pyodide .engine-build/python
node scripts/install-python-engine.mjs .engine-build/python
node scripts/java-engine-pack.mjs
node scripts/install-java-engine.mjs
node scripts/php-engine-pack.mjs
node scripts/install-php-engine.mjs
node scripts/perl-engine-pack.mjs --archive /path/to/webperl_prebuilt_v0.09-beta.zip
node scripts/install-perl-engine.mjs
node scripts/ruby-engine-pack.mjs
node scripts/install-ruby-engine.mjs
node scripts/cpp-engine-pack.mjs --empp /path/to/em++
node scripts/install-cpp-engine.mjs
node scripts/build-go-engine.mjs /path/to/go
node scripts/install-go-engine.mjs
node scripts/build-rust-engine.mjs /path/to/rust-toolchain
node scripts/install-rust-engine.mjs
node scripts/dotnet-engine-pack.mjs --dotnet /path/to/dotnet
node scripts/install-dotnet-engine.mjs
npm run build
npm run toolbox:check
npm run check
npm run release:artifact
```

The normal build performs no network download. `vite.config.ts` uses
`base: "./"`; all workers and assets are self-hosted and nested-path safe.

## Toolbox SDK and Portal

The application uses Toolbox SDK 0.2.2 `AppShell`. Its canonical typed/static
manifest source is `src/toolbox/manifest.source.json`; generation fails if its
version or source identity drifts.

The deterministic release ZIP can be consumed by Toolbox Portal using an exact
artifact URL, manifest ID, version and SHA-256. Portal builds consume the
artifact; they do not build Regex Tools source.

All runtime assets are self-hosted below the release path. Deployment must
serve `.wasm` as `application/wasm`, serve module/worker files with a
JavaScript MIME type and allow WebAssembly compilation in `script-src`; see
[`docs/PORTAL_REQUIREMENTS.md`](docs/PORTAL_REQUIREMENTS.md).

## Flavour support and boundaries

PCRE2's application-owned ABI provides bounded compilation, matching, native
substitution and separate automatic-callout tracing, copied caller-owned
records, deterministic allocation cleanup and explicit result completeness.
The exact source/toolchain build is reproducible offline and the verified pack
is bundled with its metadata, checksums and licence.

The Python slice executes actual CPython 3.14.2 `re` in Pyodide 314.0.3; it does
not translate a Python pattern into ECMAScript. Pyodide startup is a separate
worker-load operation and is excluded from the normal execution deadline.
Its checked-in pack contains a machine-verified `SOURCE-MANIFEST.json` with the
complete base-runtime inventory, primary source hashes or immutable Git trees,
the vendored Error Stack Parser and StackFrame source ports, all Pyodide
CPython/Emscripten patches and an independently anchored legal file for every
component. Optional wheels named by Pyodide's package catalogue are not
shipped.

PHP executes actual PHP 8.5.8 `preg_*`; the embedded PHP runtime reports PCRE2
10.44. Selecting PHP therefore does not execute the separately built PCRE2
10.47 pack. Perl executes the unmodified WebPerl 0.09-beta runtime containing
Perl 5.28.1 and is kept visibly legacy because that upstream beta was released
in 2019.

Ruby, C++, Go, Rust and .NET execute their named WebAssembly runtimes. Their
native range units are preserved and normalized at checked boundaries. The
C++ profile defaults to libc++'s modified ECMAScript grammar; its checked-in
source manifest binds the exact module to the traced Emscripten runtime, musl,
emmalloc, compiler-rt, libc++ and libc++abi inputs and records libunwind as an
audited non-selection. Go and Rust provide non-backtracking engines that
deliberately exclude look-around and backreferences; .NET uses
`System.Text.RegularExpressions` 10.0.10 with invariant culture.

The Java slice executes TeaVM 0.15.0's own `java.util.regex` class library. It
does not translate a Java pattern into ECMAScript, but it is also not OpenJDK:
in particular, TeaVM 0.15.0 does not implement
`Pattern.UNICODE_CHARACTER_CLASS`. The UI reports the `U` flag as a
compatibility request and preserves TeaVM's predefined-class and word-boundary
behaviour.

Scala reuses that exact TeaVM engine as a clearly named Scala/JVM compatibility
profile. It does not ship or claim Scala compiler/runtime, `scala.util.matching`
wrapper behavior or OpenJDK parity.

Generated-case version 1 is ECMAScript-only. It traverses the complete
application-owned ECMAScript AST under node, depth, attempt, byte, repetition
and wall-time caps, then verifies every retained label through the actual
browser `RegExp` adapter. PCRE2 generation remains unavailable until its syntax
provider can expose a complete normalized tree.

Pattern formatting is likewise ECMAScript-only. It consumes the complete
regexpp token model and does not reinterpret any partial lexical structure.
Other profiles remain unavailable until complete grammar-backed
implementations pass equivalent idempotence, reparse, engine and test gates.

The first advertised generated-code target is the PCRE2 10.47 8-bit C API.
Its deterministic match and replacement fixtures compile and execute against
the exact official release. See
[`docs/COMPARISON_AND_CODEGEN.md`](docs/COMPARISON_AND_CODEGEN.md).

No profile is emulated through JavaScript and relabelled. The sole shared
runtime is explicit: Scala compatibility intentionally delegates to the
verified TeaVM Java engine and labels that boundary in the selector and result
identity.

## Licensing

Regex Tools original source is GPL-3.0-or-later, copyright © 2026 Albrecht
Degering. Shipped dependencies retain their own compatible licences and
notices; see [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) and
[`LICENSES/`](LICENSES/).

The PHP 8.5.8 codebase is distributed under the elected PHP License version 4
(`BSD-3-Clause`) option, while WordPress Playground's `@php-wasm` packages
retain their separate GPL-2.0-or-later notice. Each runtime pack contains the
exact applicable licence and notice material recorded in its metadata.

No regex101 application source, branding, generated explanation prose,
reference text or visual design is copied. RegexLib and RGXP.RU fixtures are not
redistributed because a suitable fixture licence was not established. No
RegexHub fixture is shipped in v0.4.0.

## Known limitations

- ECMAScript syntax coverage follows regexpp's ECMAScript 2025 grammar; a later
  browser proposal may execute before the provider understands it.
- Exact consumed-length bounds for ECMAScript Unicode properties of strings
  remain unknown and are reported as such rather than guessed.
- regexpp does not provide tolerant AST recovery. Malformed input receives an
  exact reported error position as a zero-width range and an application-owned
  error node, not a fabricated multi-character span or partial provider AST.
- Native JavaScript compile errors do not consistently expose a machine-readable
  source offset; the provider diagnostic supplies the range.
- Capture history is unavailable. Browser ECMAScript exposes no engine trace;
  PCRE2 exposes bounded automatic callouts, not a complete internal trace.
  Movement classifications are derived and labelled as such.
- Static risk heuristics and generated-input observations apply only to
  ECMAScript 2025 and are advisory. They are never proof of safety or of a
  pattern’s general time complexity; analysis settings and results are
  ephemeral.
- PCRE2 structural explanations are deliberately partial. The application
  recognizes common capture forms and PCRE2-only constructs for reference, but
  the actual PCRE2 compiler is authoritative. Branch-reset capture numbering is
  omitted from the explanation rather than guessed.
- Python, Java, PHP, Perl, Ruby, C++, Go, Rust, .NET and Scala compatibility
  structural explanations are deliberately partial lexical models. The
  selected runtime remains authoritative for compilation, group count and
  matching.
- Python, Ruby, C++ and Perl report Unicode code-point offsets; Java, Scala,
  .NET and ECMAScript report UTF-16 offsets; standalone PCRE2, PHP, Go and Rust
  report UTF-8 bytes. Every range is checked before conversion and its native
  unit remains visible.
- TeaVM `java.util.regex` is not OpenJDK. Its `U` compatibility request does
  not add OpenJDK's `UNICODE_CHARACTER_CLASS` semantics; the same boundary
  applies to the Scala compatibility profile.
- WebPerl 0.09-beta and Perl 5.28.1 are intentionally legacy. The shipped
  upstream loader contains a dormant optional JavaScript-interoperability
  `eval` site, although the fixed bridge does not import or invoke it.
- Capture-table rendering is paged at 200 rows while aggregate collection is
  bounded separately.
- Explanation/extraction trees and editor decorations show bounded 2,000-node
  previews with explicit totals; normalized results and table/export data stay
  separate from those render-only caps.
- Extraction-tree values show at most 100 UTF-16 units and capture-table cells
  at most 512. UI-only shortening shows the exact range length; engine-clipped
  values are labelled as prefixes of that range. Copy and eligible JSON export
  continue to use the separately bounded engine value.
- Replacement parsing is complete only for the providers that advertise that
  coverage; the v0.4.0 community providers label their lexical replacement
  models as partial and defer semantics to the runtime. Editor marks, token
  rows and diagnostics have explicit presentation caps. List token chips are
  similarly bounded, and list rendering stops at 250,000 token evaluations.
- Per-match replacement presentation renders at most 200 matches and 2,000
  token contributions under a 50,000 token-evaluation budget. Bounded
  replacement output remains available separately and is labelled partial if
  match collection reached a limit.
- Result-value previews are capped per value and overall. List export is
  disabled whenever a bounded preview would otherwise be mistaken for complete
  data.
- Unit-test failure messages retain at most 2 KiB UTF-8 per case, so 1,000
  results stay below the 2 MiB log budget. Oversized exact replacement results
  offer a lightweight `should-match` draft when one is valid and fits the suite.
- Corpus input accepts recognized UTF-8 text only. It does not auto-detect
  legacy encodings or recursively traverse directories.
- Corpus capture extraction is a bounded per-document summary: full match plus
  at most 31 capture groups, three 160-unit samples each. Content-free reports
  include counts and clipping metadata, never the samples themselves.
- Generated cases deliberately sample only ECMAScript and report
  backreferences, lookaround, boundaries, complex Unicode sets and other
  unsupported constraints instead of claiming exhaustive coverage.
- Pattern formatting is intentionally narrow and ECMAScript-only. Its bounded
  source/candidate and unit-test validation is evidence for the checked
  snapshots, not a proof of equivalence over all subjects.
- Cross-flavour comparison, PCRE2 C generation and subject minimization remain
  scoped to their explicitly supported ECMAScript/PCRE2 paths; selecting any
  other profile does not silently substitute one of those engines.

Architecture, security, performance, provenance and flavour details live in
[`docs/`](docs/).
