diff --git a/README.md b/README.md index 89415e0..8fe64bd 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ The repo includes Playwright visual snapshots for the shipped viewer surfaces: ## Status - Markdown, code, diff, CSV, and JSON all render in the static shell -- Fragment transport supports `plain`, `lz`, `deflate`, `arx`, and `arx2`, with automatic shortest-fragment selection across available wire formats -- The `arx` substitution dictionary is served at `/arx-dictionary.json`; the `arx2` tuple-envelope overlay is served at `/arx2-dictionary.json` +- Fragment transport supports `plain`, `lz`, `deflate`, `arx`, `arx2`, and `arx3`, with automatic shortest-fragment selection across available wire formats +- The `arx` substitution dictionary is served at `/arx-dictionary.json` with a pre-compressed `/arx-dictionary.json.br` variant; the `arx2` tuple-envelope overlay is served at `/arx2-dictionary.json` with a pre-compressed `/arx2-dictionary.json.br` variant; `arx3` reuses those proven bytes and optimizes for compact visible Unicode fragments - The viewer toolbar copies artifact bodies to the clipboard, downloads them as files, and (for markdown) supports browser print-to-PDF - Deployment target: static hosting, including Cloudflare Pages @@ -60,7 +60,7 @@ The repo includes Playwright visual snapshots for the shipped viewer surfaces: - `code` - read-only CodeMirror view with line numbers, wrap toggle, syntax-tree-aware rainbow brackets, and maintained indentation markers - `diff` - review-style multi-file git patch viewer with unified and split modes - `csv` - parsed table view with sticky headers and horizontal overflow handling -- `json` - lightweight read-only tree view plus raw code view, with graceful malformed JSON fallback +- `json` - lightweight read-only tree view plus native raw source view, with graceful malformed JSON fallback ## Principles @@ -126,6 +126,7 @@ npm run preview ``` Set `NEXT_PUBLIC_BASE_PATH` before `npm run build` when you want to preview a subpath deployment locally. +The local preview server auto-detects the generated base path from the build manifest, so the same `npm run preview` command works for root and subpath exports. Set `NEXT_PUBLIC_SITE_URL` to your public origin before production builds so `sitemap.xml` and metadata use the correct canonical origin (see `docs/deployment.md`). diff --git a/docs/architecture.md b/docs/architecture.md index 736ac3e..043e079 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -25,7 +25,7 @@ The static export also emits `sitemap.xml` at the site root (and under `NEXT_PUB - `code` - read-only CodeMirror view with syntax-aware rendering and code affordances - `diff` - review-style diff view with unified and split modes - `csv` - table-focused data grid built from parsed rows and dynamic columns -- `json` - lightweight read-only tree view plus a raw CodeMirror view +- `json` - lightweight read-only tree view plus a native raw source view The viewer shell now routes all five artifact kinds through dynamically imported client-only renderers so the landing shell stays light and static-host friendly. @@ -60,12 +60,13 @@ That keeps the viewer static-hosting friendly while removing the brittle parts o ## Bundle tradeoffs -The largest remaining deferred cost is still the diff renderer stack, primarily `@git-diff-view/*` and its highlighting internals. It remains because it still provides the best review-style UX for multi-file git patches, split/unified modes, and syntax-aware rendering with less product code than a bespoke replacement. +The largest remaining deferred cost is still the diff renderer stack, primarily `@git-diff-view/*` and its highlighting internals. Its vendor stylesheet is served from `public/vendor/diff-view-pure.css` with a precompressed `.css.br` variant and injected only when a rich diff mounts, so the empty shell and non-diff artifacts do not pay that CSS cost. The stack remains because it still provides the best review-style UX for multi-file git patches, split/unified modes, and syntax-aware rendering with less product code than a bespoke replacement. The JSON and markdown paths are now substantially lighter because: - `vanilla-jsoneditor` was removed in favor of a lighter read-only tree view - `rehype-highlight` and its Highlight.js stack were removed +- raw markdown and CSV views use native source blocks instead of mounting CodeMirror - CodeMirror language support now loads on demand per active language ## Diff choice @@ -76,7 +77,7 @@ The JSON and markdown paths are now substantially lighter because: - split and unified views are built in - syntax highlighting and diff affordances are stronger out of the box for artifact viewing - individual file patches can be rendered as a sequence while preserving filenames and boundaries -- CodeMirror remains the better fit for raw source and raw JSON views +- CodeMirror remains the better fit for full source artifacts and markdown code fences `@codemirror/merge` stays a reasonable future option if the project ever needs a more editor-centric comparison workflow, but it is not the best default for shareable review artifacts. @@ -95,12 +96,13 @@ The fragment protocol keeps the JSON envelope stable and treats compression stri - `plain` stores base64url-encoded JSON for compatibility and debugging - `lz` stores compressed JSON via `lz-string` when it produces a smaller fragment - `deflate` stores deflate-compressed UTF-8 JSON bytes when it outperforms other codecs -- `arx` applies domain-dictionary substitution, brotli compression (quality 11), and binary-to-text encoding for best-in-class compression. Four wire shapes are candidates: base76 (ASCII, 77 fragment-safe chars), base64url (RFC 4648 `A-Za-z0-9-_` with a `B.` prefix for detection), base1k (Unicode, 1774 chars from U+00A1–U+07FF), and baseBMP (high-density Unicode, ~62k safe BMP code points from U+00A1–U+FFEF, ~15.92 bits/char). The async encoder tries all four and picks the shortest **transport** length (percent-encoded UTF-8 length for non-ASCII), so base64url can win over Unicode encodings on chat-style surfaces. baseBMP produces ~32% fewer characters than base1k and ~55% fewer than base76 for the same compressed bytes, achieving ~70% smaller fragments than deflate on typical payloads (~6.1x compression ratio for 8k markdown). Full pipeline timing is on the order of ~8–14ms for 8k payloads depending on the wire encoding. The substitution dictionary is served as a static file at `/arx-dictionary.json` so agents can fetch it for local compression; a pre-compressed `/arx-dictionary.json.br` variant is also available. The viewer loads the dictionary on startup and falls back to a built-in table if the fetch fails. -- `arx2` keeps the arx compression stack but replaces the JSON envelope with a compact tuple envelope and applies `/arx2-dictionary.json` as an overlay before the shared arx dictionary. It uses `v1.arx2..` and decodes back to the standard envelope before validation/rendering. +- `arx` applies domain-dictionary substitution, brotli compression (quality 11), and binary-to-text encoding for best-in-class compression. Four wire shapes are candidates: base76 (ASCII, 77 fragment-safe chars), base64url (RFC 4648 `A-Za-z0-9-_` with a `B.` prefix for detection), base1k (Unicode, 1774 chars from U+00A1–U+07FF), and baseBMP (high-density Unicode, ~62k safe BMP code points from U+00A1–U+FFEF, ~15.92 bits/char). The async encoder tries all four and picks the shortest **transport** length (percent-encoded UTF-8 length for non-ASCII), so base64url can win over Unicode encodings on chat-style surfaces. baseBMP produces ~32% fewer characters than base1k and ~55% fewer than base76 for the same compressed bytes, achieving ~70% smaller fragments than deflate on typical payloads (~6.1x compression ratio for 8k markdown). Full pipeline timing is on the order of ~8–14ms for 8k payloads depending on the wire encoding. The substitution dictionary is served as a static file at `/arx-dictionary.json` so agents can fetch it for local compression; a pre-compressed `/arx-dictionary.json.br` variant is also available. The viewer tries the pre-compressed dictionary first on default ARX-family loads, falls back to the JSON file, and only loads external dictionaries when an ARX/ARX2/ARX3 encode or decode path needs them. +- `arx2` keeps the arx compression stack but replaces the JSON envelope with a compact tuple envelope and applies `/arx2-dictionary.json` as an overlay before the shared arx dictionary. The viewer tries `/arx2-dictionary.json.br` first for default overlay loads and falls back to JSON. It uses `v1.arx2..` and decodes back to the standard envelope before validation/rendering. +- `arx3` uses the same tuple envelope, overlay dictionary, shared arx dictionary, and brotli bytes as arx2, then allows the dense baseBMP wire to win by decoded visible character length. This deliberately optimizes copyable visible URL length for trusted Unicode-preserving surfaces; it is not a stronger compressed-byte format than arx2. - packed wire mode (`p: 1`) shortens transport keys before compression, then unpacks back to the standard envelope during decode -- automatic async codec selection tries `arx2 -> arx -> deflate -> lz -> plain`; arx compares packed + non-packed candidates, while arx2 uses its tuple envelope +- automatic async codec selection tries `arx3 -> arx2 -> arx -> deflate -> lz -> plain`; arx compares packed + non-packed candidates, while arx2/arx3 use tuple envelopes - sync codec selection (used by examples and legacy paths) tries `deflate -> lz -> plain` -- decode enforces both fragment length and decoded payload size ceilings before UI rendering; arx/arx2 Brotli decompression uses a streaming output cap before final JSON or tuple parsing +- decode enforces both visible fragment length and decoded payload size ceilings before UI rendering; arx/arx2/arx3 Brotli decompression uses a streaming output cap before final JSON or tuple parsing - invalid bundle state is normalized or rejected before renderers mount ## Zero-retention boundaries diff --git a/docs/dependency-notes.md b/docs/dependency-notes.md index b2843c2..c17ef1e 100644 --- a/docs/dependency-notes.md +++ b/docs/dependency-notes.md @@ -12,7 +12,6 @@ - `@replit/codemirror-indentation-markers` - MIT - `@git-diff-view/*` - MIT - `papaparse` - MIT -- `@tanstack/react-table` - MIT - `lz-string` - MIT - `fflate` - MIT - `brotli-wasm` - Apache-2.0 @@ -28,12 +27,13 @@ ## Why these libraries - `react-markdown` plus `remark-gfm` plus `rehype-sanitize` covers the markdown path without introducing unsafe raw HTML by default. -- CodeMirror handles raw source and raw JSON well because it is excellent at read-only code presentation. +- `next` pins its nested `postcss` dependency to `8.5.14` via `package.json` overrides so Tailwind CSS v4's `postcss ^8.5.6` peer range is satisfied in the Next.js toolchain. +- CodeMirror handles source artifacts and markdown code fences because it is excellent at read-only code presentation; JSON, markdown raw, and CSV raw views use lighter native source blocks. - `@replit/codemirror-indentation-markers` replaces custom indent-guide logic with a maintained CM6 extension. -- `@git-diff-view/*` fits review-style diffs better than a generic merge editor for the current viewer. -- `papaparse` plus `@tanstack/react-table` keeps CSV parsing and rendering readable without coupling to a heavyweight data-grid framework. +- `@git-diff-view/*` fits review-style diffs better than a generic merge editor for the current viewer. Its pure CSS file is mirrored into `public/vendor/diff-view-pure.css` with a Brotli-compressed `public/vendor/diff-view-pure.css.br` copy by `npm run assets:compress`, and loaded only by the diff renderer; `tests/diff-style-asset.test.ts` keeps those assets in sync with the package copy. +- `papaparse` handles CSV parsing; CSV rendering uses a native read-only table to avoid a data-grid dependency for the shipped static viewer. - `fflate` provides portable deflate/inflate support across iOS Safari and Android Chromium without relying on browser-specific compression streams. -- `brotli-wasm` provides the arx/arx2 Brotli compression layer, including streaming decompression used to cap expanded output before allocating oversized decoded payloads. +- `brotli-wasm` provides the arx/arx2/arx3 Brotli compression layer, including streaming decompression used to cap expanded output before allocating oversized decoded payloads. - `mermaid` renders diagram definitions (flowcharts, sequence diagrams, etc.) to SVG client-side. Dynamically imported within the markdown renderer so it does not affect initial bundle size. - `better-sqlite3` provides synchronous SQLite access for the optional self-hosted server mode. Only used by `selfhosted/` code and not bundled into the static frontend export. @@ -41,3 +41,5 @@ - `rehype-highlight` was removed after review because markdown fences now reuse the CodeMirror viewer stack directly. - `vanilla-jsoneditor` was removed because its bundle cost was too high for the default JSON tree-view use case in a viewer-first product. +- `clsx` and `tailwind-merge` were removed because the app only needed simple conditional string joining, and the merge runtime was being pulled into shared client chunks. +- `next-themes` was removed because the static shell only needs to preserve the `theme` localStorage key and synchronize the `html.dark` class. diff --git a/docs/deployment.md b/docs/deployment.md index c1a80a5..21dfd14 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -33,7 +33,7 @@ NEXT_PUBLIC_BASE_PATH=/agent-render npm run build npm run preview ``` -Then serve `out/` under `/agent-render/` and open the sample fragment links from the landing page. +The preview server reads the generated build manifest, serves `out/` under `/agent-render/`, and keeps root requests working for convenience. Open the sample fragment links from the landing page. The preview server intentionally preserves the fragment payload and does not rely on hash-based in-page navigation for diff files. diff --git a/docs/payload-format.md b/docs/payload-format.md index e4ec47f..ac03d59 100644 --- a/docs/payload-format.md +++ b/docs/payload-format.md @@ -12,6 +12,7 @@ Payload contents are untrusted user content. Viewers, agents, and automations sh #agent-render=v1.. (plain | lz | deflate) #agent-render=v1.arx.. (arx) #agent-render=v1.arx2.. (arx2) +#agent-render=v1.arx3.. (arx3) ``` The fragment protocol includes version and codec in the outer format so unsupported formats fail cleanly. Fragment URLs can look long because they carry the artifact payload in the browser-only fragment instead of sending it to the host during the page request. @@ -21,8 +22,9 @@ Supported codecs: - `plain` - base64url-encoded JSON - `lz` - `lz-string` compressed JSON encoded for URL-safe transport - `deflate` - deflate-compressed UTF-8 JSON bytes encoded as base64url -- `arx` - domain-dictionary substitution + brotli (quality 11) + binary-to-text encoding. arx fragments include dictionary version metadata in the outer format (`v1.arx..`) so links stay portable across dictionary updates. Four wire shapes are tried and the shortest **transport** size wins (see `computeTransportLength` in `fragment.ts` — non-ASCII Unicode may count longer after percent-encoding): **base76** (ASCII-only, 77 fragment-safe chars), **base64url** (standard RFC 4648 alphabet `A-Za-z0-9-_`, no padding, prefixed with `B.` for detection), **base1k** (Unicode, 1774 chars from U+00A1–U+07FF), and **baseBMP** (high-density Unicode, ~62k safe BMP code points from U+00A1–U+FFEF, ~15.92 bits/char). BaseBMP produces ~32% fewer characters than base1k and ~55% fewer than base76 for the same compressed bytes. BaseBMP payloads are prefixed with a U+FFF0 marker for detection. The viewer’s `arxDecompress` auto-detects the wire shape (including the rare case where a base76 length prefix is also `B.` — it tries base64url first and falls back to base76 if Brotli fails). The substitution dictionary is served at `/arx-dictionary.json` (with a pre-compressed `/arx-dictionary.json.br` variant) so agents can fetch it for local compression. -- `arx2` - tuple-envelope transport + arx2 overlay substitution + the shared arx dictionary + brotli (quality 11) + the same four binary-to-text wire shapes. arx2 fragments use `v1.arx2..`, where `dictVersion` is the shared arx dictionary version. Existing `arx` links remain valid; async auto-selection tries arx2 and arx and keeps the shortest transport. +- `arx` - domain-dictionary substitution + brotli (quality 11) + binary-to-text encoding. arx fragments include dictionary version metadata in the outer format (`v1.arx..`) so links stay portable across dictionary updates. Four wire shapes are tried and the shortest **transport** size wins (see `computeTransportLength` in `fragment.ts` — non-ASCII Unicode may count longer after percent-encoding): **base76** (ASCII-only, 77 fragment-safe chars), **base64url** (standard RFC 4648 alphabet `A-Za-z0-9-_`, no padding, prefixed with `B.` for detection), **base1k** (Unicode, 1774 chars from U+00A1–U+07FF), and **baseBMP** (high-density Unicode, ~62k safe BMP code points from U+00A1–U+FFEF, ~15.92 bits/char). BaseBMP produces ~32% fewer characters than base1k and ~55% fewer than base76 for the same compressed bytes. BaseBMP payloads are prefixed with a U+FFF0 marker for detection. The viewer’s `arxDecompress` auto-detects the wire shape (including the rare case where a base76 length prefix is also `B.` — it tries base64url first and falls back to base76 if Brotli fails). The substitution dictionary is served at `/arx-dictionary.json` with a pre-compressed `/arx-dictionary.json.br` variant; the viewer tries the `.br` file first on default loads and falls back to JSON. The arx2 overlay dictionary follows the same `.br`-then-JSON default load pattern. +- `arx2` - tuple-envelope transport + arx2 overlay substitution + the shared arx dictionary + brotli (quality 11) + the same four binary-to-text wire shapes. arx2 fragments use `v1.arx2..`, where `dictVersion` is the shared arx dictionary version. Existing `arx` links remain valid; async auto-selection keeps arx2 as the conservative transport-measured tuple codec. +- `arx3` - the same tuple envelope, overlay substitution, shared arx dictionary, and brotli bytes as arx2, with a different selection rule: baseBMP may win by decoded visible character length instead of conservative percent-encoded transport length. This is the compact visible URL mode for trusted surfaces that preserve Unicode fragments. If a platform rewrites, truncates, or previews links aggressively, prefer arx2/base64url or UUID mode instead. The encoder now also supports a packed wire representation (`p: 1`) that shortens key names before compression. Packed mode is transport-only; decoded envelopes normalize back to the standard shape. @@ -96,25 +98,28 @@ Tuple fields: ## Limits -- Supported fragment budget: 8,192 characters +- Supported fragment budget: 8,192 decoded visible fragment characters - Supported decoded payload budget: 200,000 characters - Larger payloads should fail with a clear error before rendering -- Compression is selected automatically by shortest fragment across packed/non-packed candidates +- Compression is selected automatically across packed/non-packed candidates; arx and arx2 optimize conservative transport length, while arx3 optimizes compact visible length for its dense Unicode wire - Default sync codec priority is `deflate -> lz -> plain` -- Default async codec priority is `arx2 -> arx -> deflate -> lz -> plain` +- Default async codec priority is `arx3 -> arx2 -> arx -> deflate -> lz -> plain` - Optional budget-aware encoding can target strict limits like 1,500 chars and returns the shortest fragment when none fit When a payload does not fit the fragment budget or the target surface is hostile to long URLs, use UUID mode instead of weakening the fragment protocol. Current UUID mode stores the encoded payload server-side and is not zero-retention. ### Codec benchmark -Running `npm run bench:codecs` checks a fixed corpus across markdown, code, diff, CSV, JSON, and multi-artifact bundles. The current committed baseline shows: +Running `npm run bench:codecs` checks a fixed corpus across markdown, a real code-bench report, code, diff, CSV, JSON, and multi-artifact bundles. The current committed baseline shows: -- total `arx`: 11,889 brotli bytes -- total `arx2`: 11,767 brotli bytes -- `arx2` delta: 1.03% smaller overall +- total `arx`: 5,544 brotli bytes +- total `arx2`: 5,410 brotli bytes +- total `arx3`: 5,410 brotli bytes +- `arx2` delta: 2.42% smaller overall +- `arx3` visible delta vs arx2: 60.48% fewer visible fragment characters +- real code-bench report row: arx2 is 2,984 visible fragment characters; arx3 is 1,142 -The gate fails if arx2 is less than 0.5% smaller overall or if any individual corpus row regresses by more than 0.5%. Use `npm run bench:codecs:update` only when intentionally refreshing the committed baseline. +The gate fails if arx2 is less than 0.5% smaller overall, if arx3 is less than 35% smaller by visible characters overall, or if any individual corpus row regresses by more than 0.5%. Use `npm run bench:codecs:update` only when intentionally refreshing the committed baseline. ## Active artifact behavior @@ -124,7 +129,7 @@ Internal viewer navigation, such as moving between files inside a multi-file dif ## Examples -Two sample envelopes live in `src/lib/payload/examples.ts` for local development and documentation. +Sample envelopes live in `src/lib/payload/examples.ts` for local development and documentation. The homepage uses precomputed sample link data that is checked against those generated examples in tests, keeping the large sample strings out of the initial shell chunk. ### Markdown artifact example diff --git a/docs/testing.md b/docs/testing.md index a9155dc..bc5c6f5 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -11,8 +11,10 @@ ```bash npm run test npm run test:watch +npm run assets:compress npm run bench:codecs npm run bench:codecs:update +npm run check:build-budgets npm run test:e2e npm run test:e2e:update npm run test:browsers @@ -45,7 +47,9 @@ The suite is intentionally split by responsibility: - visual tests protect empty state, artifact views, theme presentation, and compact-content spacing - component tests protect selector/disclosure UI contracts - unit tests protect transport codecs, envelope validation, diff parsing, and language inference -- `npm run bench:codecs` protects arx/arx2 compression ratios against the committed `scripts/bench-baseline.json` +- `npm run assets:compress` regenerates minified/precompressed public assets, including the ARX dictionaries and mirrored diff-view stylesheet +- `npm run bench:codecs` protects arx/arx2 compressed-byte ratios and arx3 visible-character wins against the committed `scripts/bench-baseline.json`; its corpus is fixed in `scripts/bench-codecs.mjs` so unrelated source, docs, or package metadata edits do not create false codec regressions +- `npm run check:build-budgets` reads the generated `.next` manifests after `npm run build` and fails if the homepage shell or key deferred renderer chunks exceed their gzip budgets ## Self-hosted mode tests @@ -61,4 +65,4 @@ They run as part of the standard `npm run test` command. ## CI -The repository includes `.github/workflows/test.yml`, which installs Playwright browsers and runs `npm run test:ci` on pushes, pull requests, and manual dispatch. +The repository includes `.github/workflows/test.yml`, which installs Playwright browsers and runs `npm run test:ci` on pushes, pull requests, and manual dispatch. That CI command includes the exported-app browser suite and the generated build-budget check. diff --git a/docs/url-fragments.md b/docs/url-fragments.md index b703ff0..fb5a38d 100644 --- a/docs/url-fragments.md +++ b/docs/url-fragments.md @@ -3,7 +3,7 @@ agent-render links carry the artifact in the URL fragment: ```text -https://agent-render.com/#agent-render=v1.arx.1. +https://agent-render.com/#agent-render=v1.arx3.1. ``` Everything before `#` loads the static app. Everything after `#` is the artifact payload the browser decodes locally. @@ -12,21 +12,27 @@ Everything before `#` loads the static app. Everything after `#` is the artifact - `agent-render` tells the app this hash belongs to agent-render. - `v1` is the payload format version. -- `arx` is the compression codec. +- `arx3` is the compression codec. - `1` is the arx dictionary version. - `` is the encoded artifact bundle. -For non-arx links the shape is shorter: +Other links may use a shorter shape: ```text #agent-render=v1.. ``` -where `` is `plain`, `lz`, or `deflate`. +where `` is `plain`, `lz`, or `deflate`. ARX-family links include the dictionary version: + +```text +#agent-render=v1.arx.. +#agent-render=v1.arx2.. +#agent-render=v1.arx3.. +``` ## Why arx exists -Artifacts can be bigger than a comfortable URL. `arx` keeps links shorter by applying an agent-render substitution dictionary, Brotli compression, and URL-safe binary-to-text encoding. The result can look strange because it is optimized for transport, not human reading. +Artifacts can be bigger than a comfortable URL. The ARX family keeps links shorter by applying agent-render substitution dictionaries, Brotli compression, tuple envelopes for arx2/arx3, and binary-to-text encoding. `arx3` favors compact visible Unicode fragments, so it can look especially strange even though the browser decodes it locally. ## Privacy tradeoff diff --git a/eslint.config.mjs b/eslint.config.mjs index 72f8a34..8752a43 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -12,7 +12,7 @@ const compat = new FlatCompat({ const config = [...compat.extends("next/core-web-vitals")]; config.push({ - ignores: ["out/**", ".next/**"], + ignores: [".next/**", "out/**", "playwright-report/**", "test-results/**"], }); export default config; diff --git a/next-env.d.ts b/next-env.d.ts index 1b3be08..830fb59 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts index 07e4702..3b060e7 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,10 @@ import type { NextConfig } from "next"; const configuredBasePath = process.env.NEXT_PUBLIC_BASE_PATH?.trim() ?? ""; -const basePath = configuredBasePath === "/" ? "" : configuredBasePath.replace(/\/$/, ""); +const basePath = + configuredBasePath === "" || configuredBasePath === "/" + ? "" + : `${configuredBasePath.startsWith("/") ? "" : "/"}${configuredBasePath}`.replace(/\/$/, ""); const nextConfig: NextConfig = { output: "export", diff --git a/package-lock.json b/package-lock.json index 71f223f..72a4969 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@codemirror/commands": "^6.10.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-javascript": "^6.2.4", @@ -18,28 +17,22 @@ "@codemirror/lang-python": "^6.2.1", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.12.3", - "@codemirror/search": "^6.5.10", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.38.2", - "@git-diff-view/file": "^0.1.1", "@git-diff-view/react": "^0.1.1", "@replit/codemirror-indentation-markers": "^6.5.3", - "@tanstack/react-table": "^8.21.3", "brotli-wasm": "^3.0.1", - "clsx": "^2.1.1", "fflate": "^0.8.2", "lucide-react": "^0.577.0", "lz-string": "^1.5.0", - "mermaid": "^11.14.0", - "next": "15.1.11", - "next-themes": "^0.4.6", + "mermaid": "^11.15.0", + "next": "15.5.18", "papaparse": "^5.5.3", "react": "19.1.0", "react-dom": "19.1.0", "react-markdown": "^10.1.0", "rehype-sanitize": "^6.0.0", - "remark-gfm": "^4.0.1", - "tailwind-merge": "^3.4.0" + "remark-gfm": "^4.0.1" }, "devDependencies": { "@playwright/test": "^1.58.2", @@ -53,7 +46,7 @@ "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "eslint": "^9.22.0", - "eslint-config-next": "15.1.11", + "eslint-config-next": "15.5.18", "jsdom": "^28.1.0", "tailwindcss": "^4.0.6", "tsx": "^4.21.0", @@ -151,7 +144,6 @@ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", @@ -167,7 +159,6 @@ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -201,41 +192,10 @@ "specificity": "bin/cli.js" } }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz", - "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "12.0.0", - "@chevrotain/types": "12.0.0" - } - }, - "node_modules/@chevrotain/gast": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-12.0.0.tgz", - "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "12.0.0" - } - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz", - "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==", - "license": "Apache-2.0" - }, "node_modules/@chevrotain/types": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-12.0.0.tgz", - "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-12.0.0.tgz", - "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", "license": "Apache-2.0" }, "node_modules/@codemirror/autocomplete": { @@ -250,18 +210,6 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@codemirror/commands": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz", - "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.4.0", - "@codemirror/view": "^6.27.0", - "@lezer/common": "^1.1.0" - } - }, "node_modules/@codemirror/lang-css": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", @@ -365,6 +313,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -385,22 +334,12 @@ "crelt": "^1.0.5" } }, - "node_modules/@codemirror/search": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", - "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.37.0", - "crelt": "^1.0.5" - } - }, "node_modules/@codemirror/state": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", "license": "MIT", + "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } @@ -410,6 +349,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.16.tgz", "integrity": "sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -505,6 +445,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -545,6 +486,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -1198,19 +1140,6 @@ "lowlight": "^3.3.0" } }, - "node_modules/@git-diff-view/file": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@git-diff-view/file/-/file-0.1.1.tgz", - "integrity": "sha512-AaqRq53ks/gfFl47jVtgN3BFuOzqnLN/NAKGLcgRniytlijy5km6zJBMUHfYHBGKxhRdiRBmkd2IupOoXsfzhg==", - "license": "MIT", - "dependencies": { - "@git-diff-view/core": "^0.1.1", - "diff": "^8.0.3", - "fast-diff": "^1.3.0", - "highlight.js": "^11.11.0", - "lowlight": "^3.3.0" - } - }, "node_modules/@git-diff-view/lowlight": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@git-diff-view/lowlight/-/lowlight-0.1.1.tgz", @@ -1310,10 +1239,20 @@ "mlly": "^1.8.0" } }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", - "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], @@ -1329,13 +1268,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.4" + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", - "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], @@ -1351,13 +1290,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.4" + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", - "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], @@ -1371,9 +1310,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", - "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], @@ -1387,9 +1326,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", - "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" ], @@ -1403,9 +1342,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", - "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" ], @@ -1418,10 +1357,42 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", - "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" ], @@ -1435,9 +1406,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", - "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], @@ -1451,9 +1422,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", - "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], @@ -1467,9 +1438,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", - "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], @@ -1483,9 +1454,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", - "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], @@ -1501,13 +1472,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.5" + "@img/sharp-libvips-linux-arm": "1.2.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", - "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], @@ -1523,13 +1494,57 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.4" + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", - "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], @@ -1545,13 +1560,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.4" + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", - "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], @@ -1567,13 +1582,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.4" + "@img/sharp-libvips-linux-x64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", - "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], @@ -1589,13 +1604,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", - "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], @@ -1611,21 +1626,40 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", - "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.2.0" + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -1634,9 +1668,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", - "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], @@ -1653,9 +1687,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", - "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], @@ -1828,12 +1862,12 @@ "license": "MIT" }, "node_modules/@mermaid-js/parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz", - "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz", + "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==", "license": "MIT", "dependencies": { - "langium": "^4.0.0" + "@chevrotain/types": "~11.1.1" } }, "node_modules/@napi-rs/wasm-runtime": { @@ -1850,15 +1884,15 @@ } }, "node_modules/@next/env": { - "version": "15.1.11", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.11.tgz", - "integrity": "sha512-yp++FVldfLglEG5LoS2rXhGypPyoSOyY0kxZQJ2vnlYJeP8o318t5DrDu5Tqzr03qAhDWllAID/kOCsXNLcwKw==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.18.tgz", + "integrity": "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.1.11", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.1.11.tgz", - "integrity": "sha512-jpAu+46v5FF/TO8YUdOBHn/Wr4SCiU4IgjQ45S9Nn3vR4nZVS2SR+m9lpxcCv/xqMUoYuYFQZUP0H/ptw0W6+w==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.18.tgz", + "integrity": "sha512-w4MYq8M26a8PNrfto0JosLf5/3ssln1rsyP96g2DkC8uFVymStM5DLSz5ElxxrPRg2XnTMnFo3kREFlhYvxhWw==", "dev": true, "license": "MIT", "dependencies": { @@ -1866,9 +1900,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.9.tgz", - "integrity": "sha512-sQF6MfW4nk0PwMYYq8xNgqyxZJGIJV16QqNDgaZ5ze9YoVzm4/YNx17X0exZudayjL9PF0/5RGffDtzXapch0Q==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.18.tgz", + "integrity": "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ==", "cpu": [ "arm64" ], @@ -1882,9 +1916,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.9.tgz", - "integrity": "sha512-fp0c1rB6jZvdSDhprOur36xzQvqelAkNRXM/An92sKjjtaJxjlqJR8jiQLQImPsClIu8amQn+ZzFwl1lsEf62w==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.18.tgz", + "integrity": "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og==", "cpu": [ "x64" ], @@ -1898,9 +1932,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.9.tgz", - "integrity": "sha512-77rYykF6UtaXvxh9YyRIKoaYPI6/YX6cy8j1DL5/1XkjbfOwFDfTEhH7YGPqG/ePl+emBcbDYC2elgEqY2e+ag==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.18.tgz", + "integrity": "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw==", "cpu": [ "arm64" ], @@ -1914,9 +1948,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.9.tgz", - "integrity": "sha512-uZ1HazKcyWC7RA6j+S/8aYgvxmDqwnG+gE5S9MhY7BTMj7ahXKunpKuX8/BA2M7OvINLv7LTzoobQbw928p3WA==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.18.tgz", + "integrity": "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw==", "cpu": [ "arm64" ], @@ -1930,9 +1964,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.9.tgz", - "integrity": "sha512-gQIX1d3ct2RBlgbbWOrp+SHExmtmFm/HSW1Do5sSGMDyzbkYhS2sdq5LRDJWWsQu+/MqpgJHqJT6ORolKp/U1g==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.18.tgz", + "integrity": "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A==", "cpu": [ "x64" ], @@ -1946,9 +1980,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.9.tgz", - "integrity": "sha512-fJOwxAbCeq6Vo7pXZGDP6iA4+yIBGshp7ie2Evvge7S7lywyg7b/SGqcvWq/jYcmd0EbXdb7hBfdqSQwTtGTPg==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.18.tgz", + "integrity": "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA==", "cpu": [ "x64" ], @@ -1962,9 +1996,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.9.tgz", - "integrity": "sha512-crfbUkAd9PVg9nGfyjSzQbz82dPvc4pb1TeP0ZaAdGzTH6OfTU9kxidpFIogw0DYIEadI7hRSvuihy2NezkaNQ==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.18.tgz", + "integrity": "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA==", "cpu": [ "arm64" ], @@ -1978,9 +2012,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.1.9", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.9.tgz", - "integrity": "sha512-SBB0oA4E2a0axUrUwLqXlLkSn+bRx9OWU6LheqmRrO53QEAJP7JquKh3kF0jRzmlYOWFZtQwyIWJMEJMtvvDcQ==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.18.tgz", + "integrity": "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg==", "cpu": [ "x64" ], @@ -2047,6 +2081,7 @@ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.58.2" }, @@ -2439,12 +2474,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2789,39 +2818,6 @@ "tailwindcss": "4.2.1" } }, - "node_modules/@tanstack/react-table": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", - "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", - "license": "MIT", - "dependencies": { - "@tanstack/table-core": "8.21.3" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/@tanstack/table-core": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", - "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -2849,7 +2845,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -2939,8 +2934,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/better-sqlite3": { "version": "7.6.13", @@ -3316,6 +3310,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3326,6 +3321,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3388,6 +3384,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -3542,9 +3539,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -4041,6 +4038,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4091,7 +4089,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4438,9 +4435,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -4495,17 +4492,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -4663,34 +4649,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chevrotain": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz", - "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "12.0.0", - "@chevrotain/gast": "12.0.0", - "@chevrotain/regexp-to-ast": "12.0.0", - "@chevrotain/types": "12.0.0", - "@chevrotain/utils": "12.0.0" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.4.3.tgz", - "integrity": "sha512-2X4mkroolSMKqW+H22pyPMUVDqYZzPhephTmg/NODKb1IGYPHfxfhcW0EjS7wcPJNbze2i4vBWT7zT5FKF2lrQ==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.18.1" - }, - "peerDependencies": { - "chevrotain": "^12.0.0" - } - }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -4704,34 +4662,11 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4744,20 +4679,9 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, + "dev": true, "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -4868,6 +4792,7 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -5268,6 +5193,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -5590,15 +5516,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -5617,13 +5534,12 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dompurify": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", - "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz", + "integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -5872,6 +5788,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", @@ -5933,6 +5859,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5988,13 +5915,13 @@ } }, "node_modules/eslint-config-next": { - "version": "15.1.11", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.1.11.tgz", - "integrity": "sha512-RK5q3f8CKMTwNXULOqd2TAsz+7kA5+5fy5YK7T6SeczLFOuOUcuJOGlYUbyoeU6+UKQrpFsYgCz71hI1F9q5Cg==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.18.tgz", + "integrity": "sha512-HuoJU6uUPD00eyiud78IBnT4HLhztFj2V+ild2Uon5ZUrYZKe0Olu2QRD99e9IgL4/H1eg5Onka3BsfRW2U0Xw==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.1.11", + "@next/eslint-plugin-next": "15.5.18", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -6106,6 +6033,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6542,9 +6470,9 @@ } }, "node_modules/flatted": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.0.tgz", - "integrity": "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -7158,13 +7086,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", - "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", - "license": "MIT", - "optional": true - }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -7656,6 +7577,7 @@ "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.8.1", @@ -7781,24 +7703,6 @@ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, - "node_modules/langium": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.3.tgz", - "integrity": "sha512-sOPIi4hISFnY7twwV97ca1TsxpBtXq0URu/LL1AvxwccPG/RIBBlKS7a/f/EL6w8lTNaS0EFs/F+IdSOaqYpng==", - "license": "MIT", - "dependencies": { - "@chevrotain/regexp-to-ast": "~12.0.0", - "chevrotain": "~12.0.0", - "chevrotain-allstar": "~0.4.3", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.1.0" - }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" - } - }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -8537,14 +8441,14 @@ } }, "node_modules/mermaid": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz", - "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==", + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz", + "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", - "@mermaid-js/parser": "^1.1.0", + "@mermaid-js/parser": "^1.1.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", @@ -8555,14 +8459,14 @@ "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", + "es-toolkit": "^1.45.1", "katex": "^0.16.25", "khroma": "^2.1.0", - "lodash-es": "^4.17.23", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "node_modules/micromark": { @@ -9262,15 +9166,13 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.1.11", - "resolved": "https://registry.npmjs.org/next/-/next-15.1.11.tgz", - "integrity": "sha512-UiVJaOGhKST58AadwbFUZThlNBmYhKqaCs8bVtm4plTxsgKq0mJ0zTsp7t7j/rzsbAEj9WcAMdZCztjByi4EoQ==", + "version": "15.5.18", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.18.tgz", + "integrity": "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ==", "license": "MIT", "dependencies": { - "@next/env": "15.1.11", - "@swc/counter": "0.1.3", + "@next/env": "15.5.18", "@swc/helpers": "0.5.15", - "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -9282,19 +9184,19 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.1.9", - "@next/swc-darwin-x64": "15.1.9", - "@next/swc-linux-arm64-gnu": "15.1.9", - "@next/swc-linux-arm64-musl": "15.1.9", - "@next/swc-linux-x64-gnu": "15.1.9", - "@next/swc-linux-x64-musl": "15.1.9", - "@next/swc-win32-arm64-msvc": "15.1.9", - "@next/swc-win32-x64-msvc": "15.1.9", - "sharp": "^0.33.5" + "@next/swc-darwin-arm64": "15.5.18", + "@next/swc-darwin-x64": "15.5.18", + "@next/swc-linux-arm64-gnu": "15.5.18", + "@next/swc-linux-arm64-musl": "15.5.18", + "@next/swc-linux-x64-gnu": "15.5.18", + "@next/swc-linux-x64-musl": "15.5.18", + "@next/swc-win32-arm64-msvc": "15.5.18", + "@next/swc-win32-x64-msvc": "15.5.18", + "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", + "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", @@ -9315,44 +9217,6 @@ } } }, - "node_modules/next-themes": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", - "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" - } - }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/node-abi": { "version": "3.89.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", @@ -9716,9 +9580,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -9775,6 +9639,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -9812,10 +9677,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "funding": [ { "type": "opencollective", @@ -9884,7 +9748,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -9900,7 +9763,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -9913,8 +9775,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prop-types": { "version": "15.8.1", @@ -10011,6 +9872,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10020,6 +9882,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -10547,16 +10410,16 @@ } }, "node_modules/sharp": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", - "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, "license": "Apache-2.0", "optional": true, "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.3" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -10565,25 +10428,30 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.5", - "@img/sharp-darwin-x64": "0.33.5", - "@img/sharp-libvips-darwin-arm64": "1.0.4", - "@img/sharp-libvips-darwin-x64": "1.0.4", - "@img/sharp-libvips-linux-arm": "1.0.5", - "@img/sharp-libvips-linux-arm64": "1.0.4", - "@img/sharp-libvips-linux-s390x": "1.0.4", - "@img/sharp-libvips-linux-x64": "1.0.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", - "@img/sharp-libvips-linuxmusl-x64": "1.0.4", - "@img/sharp-linux-arm": "0.33.5", - "@img/sharp-linux-arm64": "0.33.5", - "@img/sharp-linux-s390x": "0.33.5", - "@img/sharp-linux-x64": "0.33.5", - "@img/sharp-linuxmusl-arm64": "0.33.5", - "@img/sharp-linuxmusl-x64": "0.33.5", - "@img/sharp-wasm32": "0.33.5", - "@img/sharp-win32-ia32": "0.33.5", - "@img/sharp-win32-x64": "0.33.5" + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/shebang-command": { @@ -10739,16 +10607,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", - "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -10803,14 +10661,6 @@ "node": ">= 0.4" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -11070,16 +10920,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tailwind-merge": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", - "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, "node_modules/tailwindcss": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", @@ -11183,11 +11023,12 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11331,6 +11172,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -11455,6 +11297,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11489,9 +11332,9 @@ } }, "node_modules/undici": { - "version": "7.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", - "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", "engines": { @@ -11654,16 +11497,16 @@ "optional": true }, "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/esm/bin/uuid" + "uuid": "dist-node/bin/uuid" } }, "node_modules/vfile": { @@ -11695,11 +11538,12 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -11788,11 +11632,12 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11879,9 +11724,9 @@ } }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -11891,55 +11736,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "license": "MIT" - }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/package.json b/package.json index abc741a..12f7b54 100644 --- a/package.json +++ b/package.json @@ -23,27 +23,28 @@ ], "scripts": { "dev": "next dev", - "build": "next build", + "build": "node scripts/clean-build-output.mjs && next build", "preview": "node scripts/serve-export.mjs", "start": "npm run preview", "lint": "eslint . && npm run check:public-export-docs", "test": "vitest run", "test:watch": "vitest", - "test:e2e": "playwright test", - "test:e2e:update": "playwright test --update-snapshots", - "test:ci": "npm run lint && npm run test && npm run typecheck && npm run test:e2e", + "test:e2e": "env -u NO_COLOR playwright test", + "test:e2e:update": "env -u NO_COLOR playwright test --update-snapshots", + "test:ci": "npm run lint && npm run test && npm run typecheck && npm run test:e2e && npm run check:build-budgets", "test:browsers": "playwright install chromium", "bench:codecs": "node scripts/bench-codecs.mjs", "bench:codecs:update": "node scripts/bench-codecs.mjs --write-baseline", + "assets:compress": "node scripts/compress-dictionary.mjs", "typecheck": "node scripts/ensure-next-types.mjs && tsc --noEmit", - "check": "npm run lint && npm run test && npm run bench:codecs && npm run typecheck && npm run build", + "check": "npm run lint && npm run test && npm run bench:codecs && npm run typecheck && npm run build && npm run check:build-budgets", + "check:build-budgets": "node scripts/check-build-budgets.mjs", "check:public-export-docs": "node scripts/check-public-export-docs.mjs", "selfhosted:dev": "node --import tsx selfhosted/server.ts", "selfhosted:build": "tsc -p selfhosted/tsconfig.json", "selfhosted:start": "node selfhosted/dist/server.js" }, "dependencies": { - "@codemirror/commands": "^6.10.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-javascript": "^6.2.4", @@ -52,28 +53,22 @@ "@codemirror/lang-python": "^6.2.1", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.12.3", - "@codemirror/search": "^6.5.10", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.38.2", - "@git-diff-view/file": "^0.1.1", "@git-diff-view/react": "^0.1.1", "@replit/codemirror-indentation-markers": "^6.5.3", - "@tanstack/react-table": "^8.21.3", "brotli-wasm": "^3.0.1", - "clsx": "^2.1.1", "fflate": "^0.8.2", "lucide-react": "^0.577.0", "lz-string": "^1.5.0", - "mermaid": "^11.14.0", - "next": "15.1.11", - "next-themes": "^0.4.6", + "mermaid": "^11.15.0", + "next": "15.5.18", "papaparse": "^5.5.3", "react": "19.1.0", "react-dom": "19.1.0", "react-markdown": "^10.1.0", "rehype-sanitize": "^6.0.0", - "remark-gfm": "^4.0.1", - "tailwind-merge": "^3.4.0" + "remark-gfm": "^4.0.1" }, "devDependencies": { "@playwright/test": "^1.58.2", @@ -87,7 +82,7 @@ "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "eslint": "^9.22.0", - "eslint-config-next": "15.1.11", + "eslint-config-next": "15.5.18", "jsdom": "^28.1.0", "tailwindcss": "^4.0.6", "tsx": "^4.21.0", @@ -97,6 +92,11 @@ "optionalDependencies": { "better-sqlite3": "^12.8.0" }, + "overrides": { + "next": { + "postcss": "8.5.14" + } + }, "engines": { "node": ">=20.10.0" } diff --git a/playwright.config.ts b/playwright.config.ts index a6b61e6..3ea64fa 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,7 @@ import { defineConfig, devices } from "@playwright/test"; const port = Number(process.env.PLAYWRIGHT_PORT || 4401); +const cleanColorEnv = "env -u NO_COLOR"; export default defineConfig({ testDir: "tests/e2e", @@ -24,7 +25,7 @@ export default defineConfig({ video: "retain-on-failure", }, webServer: { - command: `NEXT_PUBLIC_BASE_PATH=/agent-render npm run build && PORT=${port} NEXT_PUBLIC_BASE_PATH=/agent-render npm run preview`, + command: `${cleanColorEnv} NEXT_PUBLIC_BASE_PATH=/agent-render npm run build && ${cleanColorEnv} PORT=${port} NEXT_PUBLIC_BASE_PATH=/agent-render npm run preview`, port, reuseExistingServer: !process.env.CI, timeout: 120000, diff --git a/public/_headers b/public/_headers index 668acb5..05f6d97 100644 --- a/public/_headers +++ b/public/_headers @@ -4,3 +4,18 @@ X-Content-Type-Options: nosniff X-Frame-Options: DENY Permissions-Policy: accelerometer=(), ambient-light-sensor=(), autoplay=(), bluetooth=(), camera=(), clipboard-read=(), clipboard-write=(self), display-capture=(), encrypted-media=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), xr-spatial-tracking=() + +/arx-dictionary.json.br + Content-Type: application/json; charset=utf-8 + Content-Encoding: br + Vary: Accept-Encoding + +/arx2-dictionary.json.br + Content-Type: application/json; charset=utf-8 + Content-Encoding: br + Vary: Accept-Encoding + +/vendor/diff-view-pure.css.br + Content-Type: text/css; charset=utf-8 + Content-Encoding: br + Vary: Accept-Encoding diff --git a/public/arx2-dictionary.json b/public/arx2-dictionary.json index f015f0f..fe769e1 100644 --- a/public/arx2-dictionary.json +++ b/public/arx2-dictionary.json @@ -1 +1 @@ -{"version":1,"description":"Overlay substitution dictionary for the arx2 tuple-envelope codec. These substitutions are applied before the shared arx v1 dictionary, then reversed after the v1 dictionary during decode.","singleByteSlots":["[\"m\",\"","[\"c\",\""],"extendedSlots":["[\"d\",\"","[\"s\",\"","[\"j\",\"","[2,","[3,","],[","\",\"","\",null",",null,\"",",null,null,\"",",\"split\"",",\"unified\"","export const ","export function ","export const value = ","export type ","export interface ","import { ","} from \""," as const","async function ","return <","className=\"","\\n## ","\\n### ","\\n- [x] ","\\n- [ ] ","](#","diff --git ","\\n+++","\\n---","\\n@@ -"]} +{"version":1,"description":"Overlay substitution dictionary for the arx2 tuple-envelope codec. These substitutions are applied before the shared arx v1 dictionary, then reversed after the v1 dictionary during decode.","singleByteSlots":["[\"m\",\"","[\"c\",\""],"extendedSlots":["[\"d\",\"","[\"s\",\"","[\"j\",\"","[2,","[3,","],[","\",\"","\",null",",null,\"",",null,null,\"",",\"split\"",",\"unified\"","export const ","export function ","export const value = ","export type ","export interface ","import { ","} from \""," as const","async function ","return <","className=\"","\\n## ","\\n### ","\\n- [x] ","\\n- [ ] ","](#","diff --git ","\\n+++","\\n---","\\n@@ -"]} \ No newline at end of file diff --git a/public/arx2-dictionary.json.br b/public/arx2-dictionary.json.br new file mode 100644 index 0000000..f5a686b Binary files /dev/null and b/public/arx2-dictionary.json.br differ diff --git a/public/vendor/diff-view-pure.css b/public/vendor/diff-view-pure.css new file mode 100644 index 0000000..81032e5 --- /dev/null +++ b/public/vendor/diff-view-pure.css @@ -0,0 +1,660 @@ +.diff-tailwindcss-wrapper .container { + width: 100%; +} +@media (min-width: 640px) { + .diff-tailwindcss-wrapper .container { + max-width: 640px; + } +} +@media (min-width: 768px) { + .diff-tailwindcss-wrapper .container { + max-width: 768px; + } +} +@media (min-width: 1024px) { + .diff-tailwindcss-wrapper .container { + max-width: 1024px; + } +} +@media (min-width: 1280px) { + .diff-tailwindcss-wrapper .container { + max-width: 1280px; + } +} +@media (min-width: 1536px) { + .diff-tailwindcss-wrapper .container { + max-width: 1536px; + } +} +.diff-tailwindcss-wrapper .invisible { + visibility: hidden; +} +.diff-tailwindcss-wrapper .absolute { + position: absolute; +} +.diff-tailwindcss-wrapper .relative { + position: relative; +} +.diff-tailwindcss-wrapper .sticky { + position: sticky; +} +.diff-tailwindcss-wrapper .left-0 { + left: 0px; +} +.diff-tailwindcss-wrapper .left-\[100\%\] { + left: 100%; +} +.diff-tailwindcss-wrapper .right-\[100\%\] { + right: 100%; +} +.diff-tailwindcss-wrapper .top-0 { + top: 0px; +} +.diff-tailwindcss-wrapper .top-\[50\%\] { + top: 50%; +} +.diff-tailwindcss-wrapper .z-\[1\] { + z-index: 1; +} +.diff-tailwindcss-wrapper .ml-\[-1\.5em\] { + margin-left: -1.5em; +} +.diff-tailwindcss-wrapper .block { + display: block; +} +.diff-tailwindcss-wrapper .inline-block { + display: inline-block; +} +.diff-tailwindcss-wrapper .flex { + display: flex; +} +.diff-tailwindcss-wrapper .table { + display: table; +} +.diff-tailwindcss-wrapper .hidden { + display: none; +} +.diff-tailwindcss-wrapper .h-\[50\%\] { + height: 50%; +} +.diff-tailwindcss-wrapper .h-full { + height: 100%; +} +.diff-tailwindcss-wrapper .min-h-\[28px\] { + min-height: 28px; +} +.diff-tailwindcss-wrapper .w-\[1\%\] { + width: 1%; +} +.diff-tailwindcss-wrapper .w-\[1\.5em\] { + width: 1.5em; +} +.diff-tailwindcss-wrapper .w-\[1\.5px\] { + width: 1.5px; +} +.diff-tailwindcss-wrapper .w-\[10px\] { + width: 10px; +} +.diff-tailwindcss-wrapper .w-\[1px\] { + width: 1px; +} +.diff-tailwindcss-wrapper .w-\[50\%\] { + width: 50%; +} +.diff-tailwindcss-wrapper .w-full { + width: 100%; +} +.diff-tailwindcss-wrapper .w-max { + width: -moz-max-content; + width: max-content; +} +.diff-tailwindcss-wrapper .min-w-\[100px\] { + min-width: 100px; +} +.diff-tailwindcss-wrapper .min-w-\[40px\] { + min-width: 40px; +} +.diff-tailwindcss-wrapper .min-w-full { + min-width: 100%; +} +.diff-tailwindcss-wrapper .flex-shrink-0 { + flex-shrink: 0; +} +.diff-tailwindcss-wrapper .shrink-0 { + flex-shrink: 0; +} +.diff-tailwindcss-wrapper .basis-\[50\%\] { + flex-basis: 50%; +} +.diff-tailwindcss-wrapper .table-fixed { + table-layout: fixed; +} +.diff-tailwindcss-wrapper .border-collapse { + border-collapse: collapse; +} +.diff-tailwindcss-wrapper .border-spacing-0 { + --tw-border-spacing-x: 0px; + --tw-border-spacing-y: 0px; + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); +} +.diff-tailwindcss-wrapper .origin-center { + transform-origin: center; +} +.diff-tailwindcss-wrapper .translate-x-\[-50\%\] { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.diff-tailwindcss-wrapper .translate-x-\[50\%\] { + --tw-translate-x: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.diff-tailwindcss-wrapper .translate-y-\[-50\%\] { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.diff-tailwindcss-wrapper .transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.diff-tailwindcss-wrapper .cursor-pointer { + cursor: pointer; +} +.diff-tailwindcss-wrapper .select-none { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} +.diff-tailwindcss-wrapper .flex-col { + flex-direction: column; +} +.diff-tailwindcss-wrapper .items-start { + align-items: flex-start; +} +.diff-tailwindcss-wrapper .items-center { + align-items: center; +} +.diff-tailwindcss-wrapper .justify-center { + justify-content: center; +} +.diff-tailwindcss-wrapper .overflow-x-auto { + overflow-x: auto; +} +.diff-tailwindcss-wrapper .overflow-y-hidden { + overflow-y: hidden; +} +.diff-tailwindcss-wrapper .whitespace-nowrap { + white-space: nowrap; +} +.diff-tailwindcss-wrapper .break-all { + word-break: break-all; +} +.diff-tailwindcss-wrapper .rounded-\[2px\] { + border-radius: 2px; +} +.diff-tailwindcss-wrapper .rounded-md { + border-radius: 0.375rem; +} +.diff-tailwindcss-wrapper .border-l-\[1px\] { + border-left-width: 1px; +} +.diff-tailwindcss-wrapper .fill-current { + fill: currentColor; +} +.diff-tailwindcss-wrapper .p-0 { + padding: 0px; +} +.diff-tailwindcss-wrapper .p-\[1px\] { + padding: 1px; +} +.diff-tailwindcss-wrapper .px-\[10px\] { + padding-left: 10px; + padding-right: 10px; +} +.diff-tailwindcss-wrapper .py-\[2px\] { + padding-top: 2px; + padding-bottom: 2px; +} +.diff-tailwindcss-wrapper .py-\[6px\] { + padding-top: 6px; + padding-bottom: 6px; +} +.diff-tailwindcss-wrapper .pl-\[1\.5em\] { + padding-left: 1.5em; +} +.diff-tailwindcss-wrapper .pl-\[10px\] { + padding-left: 10px; +} +.diff-tailwindcss-wrapper .pl-\[2\.0em\] { + padding-left: 2.0em; +} +.diff-tailwindcss-wrapper .pr-\[10px\] { + padding-right: 10px; +} +.diff-tailwindcss-wrapper .text-right { + text-align: right; +} +.diff-tailwindcss-wrapper .indent-\[0\.2em\] { + text-indent: 0.2em; +} +.diff-tailwindcss-wrapper .align-top { + vertical-align: top; +} +.diff-tailwindcss-wrapper .align-middle { + vertical-align: middle; +} +.diff-tailwindcss-wrapper .text-\[1\.2em\] { + font-size: 1.2em; +} +.diff-tailwindcss-wrapper .leading-\[1\.6\] { + line-height: 1.6; +} +.diff-tailwindcss-wrapper .\!text-red-500 { + --tw-text-opacity: 1 !important; + color: rgb(239 68 68 / var(--tw-text-opacity, 1)) !important; +} +.diff-tailwindcss-wrapper .opacity-\[0\.5\] { + opacity: 0.5; +} +.diff-tailwindcss-wrapper .transition-transform { + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.diff-tailwindcss-wrapper * { + box-sizing: border-box; +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-style-root { + --diff-border--: #dedede; + --diff-add-content--: #dafbe1; + --diff-del-content--: #ffebe9; + --diff-add-lineNumber--: #aceebb; + --diff-del-lineNumber--: #ffcecb; + --diff-plain-content--: #ffffff; + --diff-expand-content--: #fafafa; + --diff-plain-lineNumber--: #fafafa; + --diff-expand-lineNumber--: #fafafa; + --diff-plain-lineNumber-color--: #555555; + --diff-expand-lineNumber-color--: #555555; + --diff-hunk-content--: #ddf4ff; + --diff-hunk-lineNumber--: #b6e3ff; + --diff-hunk-lineNumber-hover--: #0969da; + --diff-add-content-highlight--: #aceebb; + --diff-del-content-highlight--: #ffcecb; + --diff-add-widget--: #0969d2; + --diff-add-widget-color--: #ffffff; + --diff-empty-content--: #fafafa; + --diff-hunk-content-color--: #777777; + + color: black; +} +.diff-tailwindcss-wrapper .diff-style-root .diff-line-syntax-raw *, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw * { + color: var(--diff-view-light, inherit); + font-weight: var(--diff-view-light-font-weight, inherit); +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-style-root { + --diff-border--: #3d444d; + --diff-add-content--: #18271f; + --diff-del-content--: #23191c; + --diff-add-lineNumber--: #284228; + --diff-del-lineNumber--: #4f2828; + --diff-plain-content--: #0d1117; + --diff-expand-content--: #161b22; + --diff-plain-lineNumber--: #161b22; + --diff-expand-lineNumber--: #161b22; + --diff-plain-lineNumber-color--: #a0aaab; + --diff-expand-lineNumber-color--: #a0aaab; + --diff-hunk-content--: #131d2e; + --diff-hunk-lineNumber--: #0c2d6b; + --diff-hunk-lineNumber-hover--: #1f6feb; + --diff-add-content-highlight--: #2f5732; + --diff-del-content-highlight--: #713431; + --diff-add-widget--: #0969d2; + --diff-add-widget-color--: #ffffff; + --diff-empty-content--: #161b22; + --diff-hunk-content-color--: #9298a0; + + color: white; +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw * { + color: var(--diff-view-dark, inherit); + font-weight: var(--diff-view-dark-font-weight, inherit); +} +.diff-tailwindcss-wrapper table, +.diff-tailwindcss-wrapper tr, +.diff-tailwindcss-wrapper td { + border-color: transparent; + border-width: 0px; + text-align: left; +} +.diff-tailwindcss-wrapper td { + padding: 0; +} +.diff-tailwindcss-wrapper .diff-line-old-num, +.diff-tailwindcss-wrapper .diff-line-new-num, +.diff-tailwindcss-wrapper .diff-line-num { + text-align: right; +} +.diff-tailwindcss-wrapper .diff-style-root tr { + content-visibility: auto; +} +.diff-tailwindcss-wrapper .diff-add-widget-wrapper { + transform-origin: center; + transform: translateX(-50%) !important; +} +.diff-tailwindcss-wrapper .diff-line-old-content .diff-add-widget-wrapper, +.diff-tailwindcss-wrapper .diff-line-new-content .diff-add-widget-wrapper { + transform: translateX(50%) !important; +} +.diff-tailwindcss-wrapper .diff-add-widget-wrapper:hover { + transform: translateX(-50%) scale(1.1) !important; +} +.diff-tailwindcss-wrapper .diff-line-old-content .diff-add-widget-wrapper:hover, +.diff-tailwindcss-wrapper .diff-line-new-content .diff-add-widget-wrapper:hover { + transform: translateX(50%) scale(1.1) !important; +} +.diff-tailwindcss-wrapper .diff-widget-tooltip { + position: relative; +} +.diff-tailwindcss-wrapper .diff-add-widget, +.diff-tailwindcss-wrapper .diff-widget-tooltip { + font-family: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + font-size: 100%; + font-weight: inherit; + line-height: inherit; + letter-spacing: inherit; + color: inherit; + margin: 0; + text-transform: none; + border-width: 0px; + background-color: transparent; + background-image: none; +} +.diff-tailwindcss-wrapper .diff-widget-tooltip::after { + display: none; + box-sizing: border-box; + background-color: #555555; + position: absolute; + content: attr(data-title); + font-size: 11px; + padding: 1px 2px; + border-radius: 4px; + overflow: hidden; + top: 50%; + white-space: nowrap; + transform: translateY(-50%); + left: calc(100% + 8px); + color: #ffffff; +} +.diff-tailwindcss-wrapper .diff-widget-tooltip::before { + display: none; + box-sizing: border-box; + content: ""; + position: absolute; + top: 50%; + left: calc(100% - 2px); + transform: translateY(-50%); + border: 6px solid transparent; + border-right-color: #555555; +} +.diff-tailwindcss-wrapper .diff-widget-tooltip:hover { + background-color: var(--diff-hunk-lineNumber-hover--); + color: white; +} +.diff-tailwindcss-wrapper .diff-widget-tooltip:hover::before { + display: block; +} +.diff-tailwindcss-wrapper .diff-widget-tooltip:hover::after { + display: block; +} +.diff-line-extend-wrapper * { + color: initial; +} +.diff-line-widget-wrapper * { + color: initial; +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw code.hljs { + padding: 3px 5px +} +/*! + Theme: GitHub + Description: Light theme as seen on github.com + Author: github.com + Maintainer: @Hirse + Updated: 2021-05-15 + + Outdated base version: https://github.com/primer/github-syntax-light + Current colors taken from GitHub's CSS +*/ +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs { + color: #24292e; + background: #ffffff +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-doctag, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-keyword, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-meta .hljs-keyword, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-template-tag, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-template-variable, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-type, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-variable.language_ { + /* prettylights-syntax-keyword */ + color: #d73a49 +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-title, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-title.class_, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-title.class_.inherited__, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-title.function_ { + /* prettylights-syntax-entity */ + color: #6f42c1 +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-attr, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-attribute, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-literal, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-meta, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-number, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-operator, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-variable, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-attr, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-class, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-id { + /* prettylights-syntax-constant */ + color: #005cc5 +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-regexp, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-string, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-meta .hljs-string { + /* prettylights-syntax-string */ + color: #032f62 +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-built_in, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-symbol { + /* prettylights-syntax-variable */ + color: #e36209 +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-comment, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-code, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-formula { + /* prettylights-syntax-comment */ + color: #6a737d +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-name, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-quote, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-tag, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-selector-pseudo { + /* prettylights-syntax-entity-tag */ + color: #22863a +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-subst { + /* prettylights-syntax-storage-modifier-import */ + color: #24292e +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-section { + /* prettylights-syntax-markup-heading */ + color: #005cc5; + font-weight: bold +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-bullet { + /* prettylights-syntax-markup-list */ + color: #735c0f +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-emphasis { + /* prettylights-syntax-markup-italic */ + color: #24292e; + font-style: italic +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-strong { + /* prettylights-syntax-markup-bold */ + color: #24292e; + font-weight: bold +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-addition { + /* prettylights-syntax-markup-inserted */ + color: #22863a; + background-color: #f0fff4 +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-deletion { + /* prettylights-syntax-markup-deleted */ + color: #b31d28; + background-color: #ffeef0 +} +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-char.escape_, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-link, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-params, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-property, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-punctuation, +.diff-tailwindcss-wrapper[data-theme="light"] .diff-line-syntax-raw .hljs-tag { + /* purposely ignored */ + +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw code.hljs { + padding: 3px 5px +} +/*! + Theme: GitHub Dark + Description: Dark theme as seen on github.com + Author: github.com + Maintainer: @Hirse + Updated: 2021-05-15 + + Outdated base version: https://github.com/primer/github-syntax-dark + Current colors taken from GitHub's CSS +*/ +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs { + color: #c9d1d9; + background: #0d1117 +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-doctag, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-keyword, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-meta .hljs-keyword, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-template-tag, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-template-variable, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-type, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-variable.language_ { + /* prettylights-syntax-keyword */ + color: #ff7b72 +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-title, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-title.class_, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-title.class_.inherited__, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-title.function_ { + /* prettylights-syntax-entity */ + color: #d2a8ff +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-attr, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-attribute, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-literal, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-meta, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-number, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-operator, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-variable, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-attr, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-class, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-id { + /* prettylights-syntax-constant */ + color: #79c0ff +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-regexp, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-string, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-meta .hljs-string { + /* prettylights-syntax-string */ + color: #a5d6ff +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-built_in, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-symbol { + /* prettylights-syntax-variable */ + color: #ffa657 +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-comment, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-code, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-formula { + /* prettylights-syntax-comment */ + color: #8b949e +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-name, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-quote, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-tag, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-selector-pseudo { + /* prettylights-syntax-entity-tag */ + color: #7ee787 +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-subst { + /* prettylights-syntax-storage-modifier-import */ + color: #c9d1d9 +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-section { + /* prettylights-syntax-markup-heading */ + color: #1f6feb; + font-weight: bold +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-bullet { + /* prettylights-syntax-markup-list */ + color: #f2cc60 +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-emphasis { + /* prettylights-syntax-markup-italic */ + color: #c9d1d9; + font-style: italic +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-strong { + /* prettylights-syntax-markup-bold */ + color: #c9d1d9; + font-weight: bold +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-addition { + /* prettylights-syntax-markup-inserted */ + color: #aff5b4; + background-color: #033a16 +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-deletion { + /* prettylights-syntax-markup-deleted */ + color: #ffdcd7; + background-color: #67060c +} +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-char.escape_, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-link, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-params, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-property, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-punctuation, +.diff-tailwindcss-wrapper[data-theme="dark"] .diff-line-syntax-raw .hljs-tag { + /* purposely ignored */ + +} +.diff-tailwindcss-wrapper .hover\:scale-110:hover { + --tw-scale-x: 1.1; + --tw-scale-y: 1.1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.diff-tailwindcss-wrapper .group:hover .group-hover\:visible { + visibility: visible; +} diff --git a/public/vendor/diff-view-pure.css.br b/public/vendor/diff-view-pure.css.br new file mode 100644 index 0000000..4e6954e Binary files /dev/null and b/public/vendor/diff-view-pure.css.br differ diff --git a/scripts/bench-baseline.json b/scripts/bench-baseline.json index 1d807f8..b73e91f 100644 --- a/scripts/bench-baseline.json +++ b/scripts/bench-baseline.json @@ -1,48 +1,102 @@ { - "version": 1, - "generatedAt": "2026-04-24T01:27:55.503Z", + "version": 2, + "generatedAt": "2026-05-16T07:56:40.375Z", "rows": { "markdown-agents:arx": { - "encodedBytes": 2664 + "encodedBytes": 408, + "visibleChars": 568 }, "markdown-agents:arx2": { - "encodedBytes": 2644 + "encodedBytes": 401, + "visibleChars": 560 + }, + "markdown-agents:arx3": { + "encodedBytes": 401, + "visibleChars": 228 + }, + "code-bench-report:arx": { + "encodedBytes": 2238, + "visibleChars": 3008 + }, + "code-bench-report:arx2": { + "encodedBytes": 2219, + "visibleChars": 2984 + }, + "code-bench-report:arx3": { + "encodedBytes": 2219, + "visibleChars": 1142 }, "code-fragment:arx": { - "encodedBytes": 2101 + "encodedBytes": 395, + "visibleChars": 551 }, "code-fragment:arx2": { - "encodedBytes": 2084 + "encodedBytes": 388, + "visibleChars": 543 + }, + "code-fragment:arx3": { + "encodedBytes": 388, + "visibleChars": 221 }, "diff-patch:arx": { - "encodedBytes": 129 + "encodedBytes": 129, + "visibleChars": 196 }, "diff-patch:arx2": { - "encodedBytes": 113 + "encodedBytes": 113, + "visibleChars": 176 + }, + "diff-patch:arx3": { + "encodedBytes": 113, + "visibleChars": 83 }, "diff-pair:arx": { - "encodedBytes": 101 + "encodedBytes": 101, + "visibleChars": 159 }, "diff-pair:arx2": { - "encodedBytes": 92 + "encodedBytes": 92, + "visibleChars": 148 + }, + "diff-pair:arx3": { + "encodedBytes": 92, + "visibleChars": 73 }, "csv-grid:arx": { - "encodedBytes": 629 + "encodedBytes": 629, + "visibleChars": 863 }, "csv-grid:arx2": { - "encodedBytes": 623 + "encodedBytes": 623, + "visibleChars": 856 + }, + "csv-grid:arx3": { + "encodedBytes": 623, + "visibleChars": 340 }, "json-package:arx": { - "encodedBytes": 991 + "encodedBytes": 345, + "visibleChars": 484 }, "json-package:arx2": { - "encodedBytes": 972 + "encodedBytes": 321, + "visibleChars": 453 + }, + "json-package:arx3": { + "encodedBytes": 321, + "visibleChars": 188 }, "multi-bundle:arx": { - "encodedBytes": 5274 + "encodedBytes": 1299, + "visibleChars": 1756 }, "multi-bundle:arx2": { - "encodedBytes": 5239 + "encodedBytes": 1253, + "visibleChars": 1696 + }, + "multi-bundle:arx3": { + "encodedBytes": 1253, + "visibleChars": 656 } } } diff --git a/scripts/bench-codecs.mjs b/scripts/bench-codecs.mjs index 8db4498..bfdb783 100644 --- a/scripts/bench-codecs.mjs +++ b/scripts/bench-codecs.mjs @@ -8,9 +8,12 @@ const BASELINE_PATH = "scripts/bench-baseline.json"; const WRITE_BASELINE = process.argv.includes("--write-baseline"); const MAX_BASELINE_REGRESSION = 0.005; const MIN_ARX2_TOTAL_WIN = 0.005; +const MIN_ARX3_VISIBLE_TOTAL_WIN = 0.35; +const BMP_BASE_SIZE = 62_000; const v1Dictionary = JSON.parse(readFileSync("public/arx-dictionary.json", "utf8")); const overlayDictionary = JSON.parse(readFileSync("public/arx2-dictionary.json", "utf8")); +const codeBenchReportFixture = readFileSync("tests/fixtures/baanish-code-bench-report.md", "utf8"); const singleByteCodes = [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0b, 0x0e, 0x0f, 0x10, @@ -30,16 +33,21 @@ function buildPairs(dict, singleCodes = singleByteCodes, extendedPrefix = "\x00" } const v1Pairs = buildPairs(v1Dictionary); -const overlayPairs = [ - ...buildPairs({ singleByteSlots: overlayDictionary.singleByteSlots, extendedSlots: [] }, [0x1e, 0x7f]), - ...overlayDictionary.extendedSlots.map((slot, index) => [slot, "\x1f" + String.fromCharCode(0x20 + index)]), -]; - -function buildTrie(pairs) { +const overlayPairs = buildPairs( + overlayDictionary, + [0x1e, 0x7f], + "\x1f", + 0x20, +); + +function buildTrie(pairs, reversed = false) { const root = { children: new Map() }; for (const [from, to] of pairs) { + const match = reversed ? to : from; + const replacement = reversed ? from : to; + let node = root; - for (const char of from) { + for (const char of match) { let child = node.children.get(char); if (!child) { child = { children: new Map() }; @@ -47,7 +55,7 @@ function buildTrie(pairs) { } node = child; } - node.replacement ??= to; + node.replacement ??= replacement; } return root; } @@ -81,9 +89,9 @@ function applyTrie(text, trie) { } const v1EncodeTrie = buildTrie(v1Pairs); -const v1DecodeTrie = buildTrie(v1Pairs.map(([from, to]) => [to, from])); +const v1DecodeTrie = buildTrie(v1Pairs, true); const overlayEncodeTrie = buildTrie(overlayPairs); -const overlayDecodeTrie = buildTrie(overlayPairs.map(([from, to]) => [to, from])); +const overlayDecodeTrie = buildTrie(overlayPairs, true); function brotli(input) { return brotliCompressSync(Buffer.from(input, "utf8"), { @@ -94,7 +102,11 @@ function brotli(input) { function trimOptional(fields) { let end = fields.length; while (end > 0 && fields[end - 1] === undefined) end--; - return fields.slice(0, end).map((field) => field === undefined ? null : field); + const trimmed = new Array(end); + for (let index = 0; index < end; index++) { + trimmed[index] = fields[index] === undefined ? null : fields[index]; + } + return trimmed; } function artifactTuple(artifact) { @@ -125,13 +137,22 @@ function artifactTuple(artifact) { } function tupleEnvelope(envelope) { - const artifacts = envelope.artifacts.map(artifactTuple); + const artifacts = new Array(envelope.artifacts.length); + const activeArtifactId = envelope.activeArtifactId; + let activeIndex = -1; + + for (let index = 0; index < envelope.artifacts.length; index++) { + const artifact = envelope.artifacts[index]; + artifacts[index] = artifactTuple(artifact); + + if (artifact.id === activeArtifactId) { + activeIndex = index; + } + } + if (artifacts.length === 1) { return trimOptional([3, artifacts[0], envelope.title]); } - const activeIndex = envelope.activeArtifactId - ? envelope.artifacts.findIndex((artifact) => artifact.id === envelope.activeArtifactId) - : -1; return trimOptional([2, artifacts, envelope.title, activeIndex > 0 ? activeIndex : undefined]); } @@ -145,6 +166,11 @@ function encodeArx2(envelope) { return applyTrie(applyTrie(tupleJson, overlayEncodeTrie), v1EncodeTrie); } +function encodeArx3(envelope) { + const tupleJson = JSON.stringify(tupleEnvelope({ ...envelope, codec: "arx3" })); + return applyTrie(applyTrie(tupleJson, overlayEncodeTrie), v1EncodeTrie); +} + function decodeArx(buf) { const substituted = brotliDecompressSync(buf).toString("utf8"); return JSON.parse(applyTrie(substituted, v1DecodeTrie)); @@ -155,6 +181,25 @@ function decodeArx2(buf) { return JSON.parse(applyTrie(applyTrie(substituted, v1DecodeTrie), overlayDecodeTrie)); } +function decodeArx3(buf) { + const substituted = brotliDecompressSync(buf).toString("utf8"); + return JSON.parse(applyTrie(applyTrie(substituted, v1DecodeTrie), overlayDecodeTrie)); +} + +function base64urlPayloadChars(byteLength) { + return 2 + Math.ceil(byteLength * 4 / 3); +} + +function baseBmpPayloadChars(byteLength) { + return 3 + Math.ceil((byteLength * 8) / Math.log2(BMP_BASE_SIZE)); +} + +function visibleFragmentChars(codec, byteLength) { + const headerLength = `agent-render=v1.${codec}.1.`.length; + const payloadLength = codec === "arx3" ? baseBmpPayloadChars(byteLength) : base64urlPayloadChars(byteLength); + return headerLength + payloadLength; +} + function median(values) { const sorted = [...values].sort((a, b) => a - b); return sorted[Math.floor(sorted.length / 2)] ?? 0; @@ -190,6 +235,170 @@ function textEnvelope(kind, title, content, extra = {}) { }; } +function repeatedFixture(block, targetLength, segmentSuffix = (index) => `\nfixture segment ${index}\n`) { + let fixture = ""; + let index = 0; + while (fixture.length < targetLength) { + fixture += `${block}${segmentSuffix(index)}`; + index++; + } + return Array.from(fixture).slice(0, targetLength).join(""); +} + +const markdownAgentsFixture = repeatedFixture( + [ + "# AGENTS.md excerpt", + "", + "`agent-render` is a static artifact viewer for AI-generated outputs.", + "Keep markdown, code, diffs, CSV, and JSON readable across chat surfaces.", + "", + "## Product contract", + "", + "- Fragment payloads use `#agent-render=v1..`.", + "- Artifact contents stay out of the host request path.", + "- Supported codecs are `plain`, `lz`, `deflate`, `arx`, `arx2`, and `arx3`.", + "- Supported artifact kinds are `markdown`, `code`, `diff`, `csv`, and `json`.", + "", + "Preserve the static shell, the zero-retention wording, and the renderer-first layout.", + "", + ].join("\n"), + 8000, + (index) => `\nFixture note ${index}: fragment transport, renderer readiness, and artifact metadata stay aligned.\n\n`, +); + +const codeFragmentFixture = repeatedFixture( + [ + "export async function decodeFragmentAsync(hash: string, options?: DecodeOptions) {", + " const parsed = parseFragmentPrefix(hash);", + " if (!parsed.ok) return parsed;", + " if (parsed.codec === \"arx\" || parsed.codec === \"arx2\") {", + " const { decodeArxFragmentAsync } = await import(\"./fragment-arx\");", + " return decodeArxFragmentAsync(parsed, options);", + " }", + " return decodePlainFragment(parsed.payload, options);", + "}", + "", + "export async function encodeEnvelopeAsync(envelope: PayloadEnvelope, options: EncodeOptions = {}) {", + " const codec = options.codec ?? envelope.codec ?? \"deflate\";", + " if (codec === \"arx\" || codec === \"arx2\") {", + " const { encodeArxEnvelopeAsync } = await import(\"./fragment-arx\");", + " return encodeArxEnvelopeAsync(envelope, codec);", + " }", + " return encodeEnvelope(envelope, { codec });", + "}", + "", + ].join("\n"), + 8000, + (index) => `\n// fixture segment ${index}: codec branch coverage and bundle shape stay stable.\n`, +); + +const packageManifestFixture = JSON.stringify( + { + name: "agent-render", + version: "0.1.0", + private: true, + scripts: { + build: "next build", + preview: "node scripts/serve-export.mjs", + check: "npm run lint && npm run test && npm run bench:codecs && npm run typecheck && npm run build", + }, + dependencies: { + "@codemirror/view": "^6.38.2", + "@git-diff-view/react": "^0.1.1", + "brotli-wasm": "^3.0.1", + "fflate": "^0.8.2", + "lucide-react": "^0.577.0", + "next": "15.1.11", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-markdown": "^10.1.0", + }, + devDependencies: { + "@playwright/test": "^1.58.2", + "typescript": "^5.8.2", + "vitest": "^4.0.18", + }, + }, + null, + 2, +); + +const readmeFixture = repeatedFixture( + [ + "# agent-render", + "", + "A static, open artifact viewer for AI outputs.", + "", + "Paste content into the browser-side link creator, choose a renderer, and share the resulting fragment URL.", + "The static host serves the shell; the browser decodes the artifact from the fragment.", + "", + "## Supported artifacts", + "", + "- Markdown with sanitized GFM and Mermaid fences.", + "- Code with a read-only CodeMirror surface.", + "- Review-style git patches with unified and split modes.", + "- CSV tables and JSON trees.", + "", + ].join("\n"), + 9000, + (index) => `\nFixture section ${index}: static export links should remain inspectable across chat clients.\n\n`, +); + +const arxCodecFixture = repeatedFixture( + [ + "const singleByteCodes = [0x01, 0x02, 0x03, 0x04, 0x05];", + "function buildPairs(dictionary, prefix = \"\\\\x00\") {", + " return dictionary.extendedSlots.map((slot, index) => [slot, prefix + String.fromCharCode(index + 1)]);", + "}", + "function applyTrie(text, trie) {", + " const out = [];", + " let index = 0;", + " while (index < text.length) {", + " let node = trie;", + " let cursor = index;", + " let replacement;", + " while (cursor < text.length) {", + " node = node.children.get(text[cursor]);", + " if (!node) break;", + " cursor++;", + " if (node.replacement !== undefined) replacement = node.replacement;", + " }", + " out.push(replacement ?? text[index]);", + " index++;", + " }", + " return out.join(\"\");", + "}", + "", + ].join("\n"), + 12000, + (index) => `\n// fixture segment ${index}: trie substitutions and tuple overlays remain comparable.\n`, +); + +const tsconfigFixture = JSON.stringify( + { + compilerOptions: { + target: "ES2022", + lib: ["dom", "dom.iterable", "esnext"], + allowJs: false, + skipLibCheck: true, + strict: true, + noEmit: true, + module: "esnext", + moduleResolution: "bundler", + resolveJsonModule: true, + isolatedModules: true, + jsx: "preserve", + paths: { + "@/*": ["./src/*"], + }, + }, + include: ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + exclude: ["node_modules"], + }, + null, + 2, +); + const patch = [ "diff --git a/src/a.ts b/src/a.ts", "--- a/src/a.ts", @@ -200,23 +409,31 @@ const patch = [ "", ].join("\n").repeat(12); -const csv = [ - "name,value,notes", - ...Array.from({ length: 180 }, (_, index) => `row-${index},${index},"export const value ${index}"`), -].join("\n"); +const csvRows = ["name,value,notes"]; +for (let index = 0; index < 180; index++) { + csvRows.push(`row-${index},${index},"export const value ${index}"`); +} +const csv = csvRows.join("\n"); const corpus = [ { name: "markdown-agents", kind: "markdown", - envelope: textEnvelope("markdown", "AGENTS.md excerpt", readFileSync("AGENTS.md", "utf8").slice(0, 8000), { + envelope: textEnvelope("markdown", "AGENTS.md excerpt", markdownAgentsFixture, { filename: "AGENTS.md", }), }, + { + name: "code-bench-report", + kind: "markdown", + envelope: textEnvelope("markdown", "Baanish Code Bench", codeBenchReportFixture, { + filename: "results.md", + }), + }, { name: "code-fragment", kind: "code", - envelope: textEnvelope("code", "fragment.ts excerpt", readFileSync("src/lib/payload/fragment.ts", "utf8").slice(0, 8000), { + envelope: textEnvelope("code", "fragment.ts excerpt", codeFragmentFixture, { filename: "fragment.ts", language: "ts", }), @@ -259,7 +476,7 @@ const corpus = [ { name: "json-package", kind: "json", - envelope: textEnvelope("json", "package.json", readFileSync("package.json", "utf8"), { filename: "package.json" }), + envelope: textEnvelope("json", "package.json", packageManifestFixture, { filename: "package.json" }), }, { name: "multi-bundle", @@ -270,17 +487,17 @@ const corpus = [ title: "Mixed bundle", activeArtifactId: "source", artifacts: [ - { id: "readme", kind: "markdown", filename: "README.md", content: readFileSync("README.md", "utf8") }, + { id: "readme", kind: "markdown", filename: "README.md", content: readmeFixture }, { id: "source", kind: "code", filename: "arx-codec.ts", language: "ts", - content: readFileSync("src/lib/payload/arx-codec.ts", "utf8").slice(0, 12000), + content: arxCodecFixture, }, { id: "patch", kind: "diff", filename: "bundle.patch", patch, view: "split" }, { id: "table", kind: "csv", filename: "table.csv", content: csv.slice(0, 1200) }, - { id: "manifest", kind: "json", filename: "tsconfig.json", content: readFileSync("tsconfig.json", "utf8") }, + { id: "manifest", kind: "json", filename: "tsconfig.json", content: tsconfigFixture }, ], }, }, @@ -288,10 +505,15 @@ const corpus = [ const rows = []; +const codecImplementations = { + arx: [encodeArx, decodeArx], + arx2: [encodeArx2, decodeArx2], + arx3: [encodeArx3, decodeArx3], +}; + for (const entry of corpus) { - for (const codec of ["arx", "arx2"]) { - const encoder = codec === "arx" ? encodeArx : encodeArx2; - const decoder = codec === "arx" ? decodeArx : decodeArx2; + for (const codec of ["arx", "arx2", "arx3"]) { + const [encoder, decoder] = codecImplementations[codec]; const rawJson = JSON.stringify({ ...entry.envelope, codec }); const encodedInput = encoder(entry.envelope); const encode = measure(() => brotli(encodedInput)); @@ -304,6 +526,7 @@ for (const entry of corpus) { name: entry.name, rawBytes: Buffer.byteLength(rawJson, "utf8"), encodedBytes: encode.result.length, + visibleChars: visibleFragmentChars(codec, encode.result.length), ratio: Buffer.byteLength(rawJson, "utf8") / encode.result.length, encodeMs: encode.ms, decodeMs: decode.ms, @@ -312,44 +535,67 @@ for (const entry of corpus) { } const baseline = { - version: 1, + version: 2, generatedAt: new Date().toISOString(), - rows: Object.fromEntries(rows.map((row) => [row.id, { encodedBytes: row.encodedBytes }])), + rows: {}, }; +for (const row of rows) { + baseline.rows[row.id] = { encodedBytes: row.encodedBytes, visibleChars: row.visibleChars }; +} + if (WRITE_BASELINE) { writeFileSync(BASELINE_PATH, `${JSON.stringify(baseline, null, 2)}\n`); } const table = [ - "| codec | kind | name | raw B | encoded B | ratio | encode ms | decode ms |", - "|---|---:|---|---:|---:|---:|---:|---:|", - ...rows.map((row) => `| ${row.codec} | ${row.kind} | ${row.name} | ${row.rawBytes} | ${row.encodedBytes} | ${row.ratio.toFixed(2)}x | ${row.encodeMs.toFixed(2)} | ${row.decodeMs.toFixed(2)} |`), + "| codec | kind | name | raw B | encoded B | visible chars | ratio | encode ms | decode ms |", + "|---|---:|---|---:|---:|---:|---:|---:|---:|", ]; +for (const row of rows) { + table.push(`| ${row.codec} | ${row.kind} | ${row.name} | ${row.rawBytes} | ${row.encodedBytes} | ${row.visibleChars} | ${row.ratio.toFixed(2)}x | ${row.encodeMs.toFixed(2)} | ${row.decodeMs.toFixed(2)} |`); +} + console.log(table.join("\n")); -const totals = rows.reduce((acc, row) => { - acc[row.codec] = (acc[row.codec] ?? 0) + row.encodedBytes; - return acc; -}, {}); +const totals = {}; +const visibleTotals = {}; +for (const row of rows) { + totals[row.codec] = (totals[row.codec] ?? 0) + row.encodedBytes; + visibleTotals[row.codec] = (visibleTotals[row.codec] ?? 0) + row.visibleChars; +} const arx2Win = (totals.arx - totals.arx2) / totals.arx; +const arx3VisibleWin = (visibleTotals.arx2 - visibleTotals.arx3) / visibleTotals.arx2; console.log(`\nTotal arx: ${totals.arx} B`); console.log(`Total arx2: ${totals.arx2} B`); +console.log(`Total arx3: ${totals.arx3} B`); +console.log(`Total arx2 visible: ${visibleTotals.arx2} chars`); +console.log(`Total arx3 visible: ${visibleTotals.arx3} chars`); console.log(`arx2 delta: ${(arx2Win * 100).toFixed(2)}%`); +console.log(`arx3 visible delta vs arx2: ${(arx3VisibleWin * 100).toFixed(2)}%`); const failures = []; if (arx2Win < MIN_ARX2_TOTAL_WIN) { failures.push(`arx2 total win ${(arx2Win * 100).toFixed(2)}% is below ${(MIN_ARX2_TOTAL_WIN * 100).toFixed(2)}%.`); } +if (arx3VisibleWin < MIN_ARX3_VISIBLE_TOTAL_WIN) { + failures.push(`arx3 visible-character win ${(arx3VisibleWin * 100).toFixed(2)}% is below ${(MIN_ARX3_VISIBLE_TOTAL_WIN * 100).toFixed(2)}%.`); +} + for (const entry of corpus) { const arx = rows.find((row) => row.id === `${entry.name}:arx`); const arx2 = rows.find((row) => row.id === `${entry.name}:arx2`); + const arx3 = rows.find((row) => row.id === `${entry.name}:arx3`); const delta = (arx.encodedBytes - arx2.encodedBytes) / arx.encodedBytes; if (delta < -MAX_BASELINE_REGRESSION) { failures.push(`${entry.name} arx2 regressed vs arx by ${(-delta * 100).toFixed(2)}%.`); } + const arx3VisibleDelta = (arx2.visibleChars - arx3.visibleChars) / arx2.visibleChars; + if (arx3VisibleDelta < -MAX_BASELINE_REGRESSION) { + failures.push(`${entry.name} arx3 visible chars regressed vs arx2 by ${(-arx3VisibleDelta * 100).toFixed(2)}%.`); + } } if (!WRITE_BASELINE && existsSync(BASELINE_PATH)) { @@ -364,6 +610,13 @@ if (!WRITE_BASELINE && existsSync(BASELINE_PATH)) { if (regression > MAX_BASELINE_REGRESSION) { failures.push(`${row.id} regressed ${(regression * 100).toFixed(2)}% vs baseline.`); } + + if (typeof baselineRow.visibleChars === "number") { + const visibleRegression = (row.visibleChars - baselineRow.visibleChars) / baselineRow.visibleChars; + if (visibleRegression > MAX_BASELINE_REGRESSION) { + failures.push(`${row.id} visible chars regressed ${(visibleRegression * 100).toFixed(2)}% vs baseline.`); + } + } } } diff --git a/scripts/check-build-budgets.mjs b/scripts/check-build-budgets.mjs new file mode 100644 index 0000000..38ef070 --- /dev/null +++ b/scripts/check-build-budgets.mjs @@ -0,0 +1,175 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { gzipSync } from "node:zlib"; + +const nextDir = join(process.cwd(), ".next"); +const appBuildManifestPath = join(nextDir, "app-build-manifest.json"); +const reactLoadableManifestPath = join(nextDir, "react-loadable-manifest.json"); +const gzipSizeCache = new Map(); + +const budgets = [ + { + maxBytes: 115 * 1024, + name: "homepage route JS", + route: "/page", + type: "route", + }, + { + importKeyParts: ["components/viewer/artifact-stage", "code-renderer"], + maxBytes: 100 * 1024, + name: "code renderer deferred JS", + type: "loadable", + }, + { + importKeyParts: ["components/viewer/artifact-stage", "markdown-renderer"], + maxBytes: 52 * 1024, + name: "markdown renderer deferred JS", + type: "loadable", + }, + { + importKeyParts: ["components/renderers/diff-renderer", "@git-diff-view/react"], + maxBytes: 340 * 1024, + name: "rich diff library deferred JS", + type: "loadable", + }, +]; + +function formatBytes(bytes) { + return `${(bytes / 1024).toFixed(1)} KiB`; +} + +function readJson(path) { + if (!existsSync(path)) { + throw new Error(`${path} is missing. Run npm run build before checking build budgets.`); + } + + return JSON.parse(readFileSync(path, "utf8")); +} + +function gzipFileSize(path) { + const cached = gzipSizeCache.get(path); + if (cached !== undefined) { + return cached; + } + + const size = gzipSync(readFileSync(path)).length; + gzipSizeCache.set(path, size); + return size; +} + +function getUniqueJsFiles(files) { + const seen = new Set(); + const jsFiles = []; + + for (const file of files) { + if (!file.endsWith(".js") || seen.has(file)) { + continue; + } + + seen.add(file); + jsFiles.push(file); + } + + return jsFiles; +} + +function getRouteFiles(appBuildManifest, route) { + const files = appBuildManifest.pages?.[route]; + + if (!files) { + throw new Error(`Route "${route}" was not found in app-build-manifest.json.`); + } + + return getUniqueJsFiles(files); +} + +function getLoadableFiles(reactLoadableManifest, keyParts) { + const files = []; + let hasMatch = false; + + for (const key in reactLoadableManifest) { + let isMatch = true; + for (const part of keyParts) { + if (!key.includes(part)) { + isMatch = false; + break; + } + } + + if (!isMatch) { + continue; + } + + hasMatch = true; + const value = reactLoadableManifest[key]; + for (const file of value.files ?? []) { + files.push(file); + } + } + + if (!hasMatch) { + throw new Error(`No react-loadable entry matched: ${keyParts.join(" + ")}`); + } + + return getUniqueJsFiles(files); +} + +function getBudgetFiles(budget, manifests) { + if (budget.type === "route") { + return getRouteFiles(manifests.appBuildManifest, budget.route); + } + + return getLoadableFiles(manifests.reactLoadableManifest, budget.importKeyParts); +} + +function measureBudget(budget, manifests) { + const files = getBudgetFiles(budget, manifests); + let gzipBytes = 0; + + for (const file of files) { + gzipBytes += gzipFileSize(join(nextDir, file)); + } + + return { + ...budget, + files, + gzipBytes, + }; +} + +const manifests = { + appBuildManifest: readJson(appBuildManifestPath), + reactLoadableManifest: readJson(reactLoadableManifestPath), +}; + +const results = []; +const failures = []; + +for (const budget of budgets) { + const result = measureBudget(budget, manifests); + results.push(result); + + if (result.gzipBytes > result.maxBytes) { + failures.push(result); + } +} + +for (const result of results) { + const status = result.gzipBytes > result.maxBytes ? "FAIL" : "ok"; + console.log( + `${status} ${result.name}: ${formatBytes(result.gzipBytes)} / ${formatBytes(result.maxBytes)}`, + ); +} + +if (failures.length > 0) { + console.error("\nBuild budget exceeded:"); + for (const failure of failures) { + console.error(`- ${failure.name}: ${formatBytes(failure.gzipBytes)} > ${formatBytes(failure.maxBytes)}`); + for (const file of failure.files) { + console.error(` ${file}`); + } + } + process.exit(1); +} + +console.log("\nBuild budget check passed."); diff --git a/scripts/clean-build-output.mjs b/scripts/clean-build-output.mjs new file mode 100644 index 0000000..429fe51 --- /dev/null +++ b/scripts/clean-build-output.mjs @@ -0,0 +1,5 @@ +import { rm } from "node:fs/promises"; + +for (const directory of [".next", "out"]) { + await rm(directory, { recursive: true, force: true }); +} diff --git a/scripts/compress-dictionary.mjs b/scripts/compress-dictionary.mjs index f7cf1f4..011aa04 100644 --- a/scripts/compress-dictionary.mjs +++ b/scripts/compress-dictionary.mjs @@ -1,31 +1,55 @@ #!/usr/bin/env node /** - * Pre-compresses public/arx-dictionary.json into minified and brotli-compressed variants. + * Prepares pre-compressed static assets that are served directly from `public/`. * * Outputs: * public/arx-dictionary.json — minified JSON (agents can fetch this directly) * public/arx-dictionary.json.br — brotli-compressed (CDN or agent can use this) + * public/arx2-dictionary.json — minified overlay JSON + * public/arx2-dictionary.json.br — brotli-compressed overlay JSON + * public/vendor/diff-view-pure.css — mirrored @git-diff-view stylesheet + * public/vendor/diff-view-pure.css.br — brotli-compressed stylesheet loaded by diffs * * Run: node scripts/compress-dictionary.mjs */ -import { readFileSync, writeFileSync } from "fs"; +import { mkdirSync, readFileSync, writeFileSync } from "fs"; import { createRequire } from "module"; +import { dirname } from "path"; const require = createRequire(import.meta.url); const brotli = require("brotli-wasm"); -const src = JSON.parse(readFileSync("public/arx-dictionary.json", "utf8")); +function writeCompressedTextAsset(path, contents) { + const compressed = brotli.compress(Buffer.from(contents, "utf8"), { quality: 11 }); -// Re-serialize minified (no whitespace) -const minified = JSON.stringify(src); -writeFileSync("public/arx-dictionary.json", minified, "utf8"); + writeFileSync(`${path}.br`, compressed); -// Brotli-compress the minified JSON -const compressed = brotli.compress(Buffer.from(minified, "utf8"), { quality: 11 }); -writeFileSync("public/arx-dictionary.json.br", compressed); + console.log(`${path}: ${contents.length} bytes`); + console.log(`${path}.br: ${compressed.length} bytes (brotli q11)`); + console.log(`Compression ratio: ${((1 - compressed.length / contents.length) * 100).toFixed(1)}%`); +} -console.log(`arx-dictionary.json: ${minified.length} bytes (minified)`); -console.log(`arx-dictionary.json.br: ${compressed.length} bytes (brotli q11)`); -console.log(`Compression ratio: ${((1 - compressed.length / minified.length) * 100).toFixed(1)}%`); +function compressDictionary(path) { + const source = JSON.parse(readFileSync(path, "utf8")); + const minified = JSON.stringify(source); + + writeFileSync(path, minified, "utf8"); + writeCompressedTextAsset(path, minified); +} + +function mirrorAndCompressTextAsset(sourcePath, targetPath) { + const source = readFileSync(sourcePath, "utf8"); + + mkdirSync(dirname(targetPath), { recursive: true }); + writeFileSync(targetPath, source, "utf8"); + writeCompressedTextAsset(targetPath, source); +} + +compressDictionary("public/arx-dictionary.json"); +compressDictionary("public/arx2-dictionary.json"); +mirrorAndCompressTextAsset( + "node_modules/@git-diff-view/react/styles/diff-view-pure.css", + "public/vendor/diff-view-pure.css", +); diff --git a/scripts/ensure-next-types.mjs b/scripts/ensure-next-types.mjs index 0de9264..eecc994 100644 --- a/scripts/ensure-next-types.mjs +++ b/scripts/ensure-next-types.mjs @@ -1,18 +1,67 @@ -import { mkdir, access, writeFile } from "node:fs/promises"; +import { access, mkdir, readdir, rm, writeFile } from "node:fs/promises"; import { constants } from "node:fs"; +import path from "node:path"; -const files = [ - [".next/types/app/layout.ts", "export {};\n"], - [".next/types/app/page.ts", "export {};\n"], - [".next/types/cache-life.d.ts", "export {};\n"], +const appDirectory = "src/app"; +const generatedTypesDirectory = ".next/types"; +const staticFiles = [ + [path.join(generatedTypesDirectory, "cache-life.d.ts"), "export {};\n"], + [path.join(generatedTypesDirectory, "routes.d.ts"), "export {};\n"], + [path.join(generatedTypesDirectory, "validator.ts"), "export {};\n"], + [path.join(generatedTypesDirectory, "package.json"), '{"type":"module"}\n'], ]; -for (const [filePath, contents] of files) { +async function fileExists(filePath) { try { await access(filePath, constants.F_OK); + return true; } catch { - const directory = filePath.slice(0, filePath.lastIndexOf("/")); - await mkdir(directory, { recursive: true }); - await writeFile(filePath, contents, "utf8"); + return false; } } + +async function* getAppEntries(directory = appDirectory) { + const children = await readdir(directory, { withFileTypes: true }); + + for (const child of children) { + const childPath = path.join(directory, child.name); + + if (child.isDirectory()) { + yield* getAppEntries(childPath); + continue; + } + + if (child.name === "layout.tsx" || child.name === "page.tsx") { + yield childPath; + } + } +} + +function getGeneratedAppTypePath(sourcePath) { + const relativePath = path.relative(appDirectory, sourcePath).replace(/\.tsx$/, ".ts"); + return path.join(generatedTypesDirectory, "app", relativePath); +} + +async function ensureFile(filePath, contents) { + if (await fileExists(filePath)) { + return false; + } + + await mkdir(path.dirname(filePath), { recursive: true }); + await writeFile(filePath, contents, "utf8"); + return true; +} + +let wroteStub = false; + +for (const [filePath, contents] of staticFiles) { + wroteStub = (await ensureFile(filePath, contents)) || wroteStub; +} + +for await (const entry of getAppEntries()) { + wroteStub = (await ensureFile(getGeneratedAppTypePath(entry), "export {};\n")) || wroteStub; +} + +if (wroteStub) { + await rm("tsconfig.tsbuildinfo", { force: true }); +} diff --git a/scripts/serve-export.mjs b/scripts/serve-export.mjs index 47d56ea..469243d 100644 --- a/scripts/serve-export.mjs +++ b/scripts/serve-export.mjs @@ -1,13 +1,10 @@ import { createServer } from "node:http"; -import { existsSync, createReadStream } from "node:fs"; -import { stat } from "node:fs/promises"; +import { createReadStream, existsSync, readFileSync, readdirSync, statSync } from "node:fs"; import path from "node:path"; import process from "node:process"; const outputDirectory = path.resolve("out"); const port = Number(process.env.PORT || 3000); -const configuredBasePath = (process.env.NEXT_PUBLIC_BASE_PATH || "").trim(); -const basePath = configuredBasePath === "/" ? "" : configuredBasePath.replace(/\/$/, ""); const apiCatalogContentType = 'application/linkset+json; profile="https://www.rfc-editor.org/info/rfc9727"'; const apiCatalogLinkHeader = '; rel="api-catalog"; type="application/linkset+json"'; @@ -28,97 +25,178 @@ const contentTypes = new Map([ [".jpeg", "image/jpeg"], [".woff", "font/woff"], [".woff2", "font/woff2"], + [".wasm", "application/wasm"], [".yaml", "application/yaml; charset=utf-8"], [".yml", "application/yaml; charset=utf-8"], ]); +function normalizeBasePath(value) { + const configuredBasePath = (value || "").trim(); + if (configuredBasePath === "" || configuredBasePath === "/") { + return ""; + } + + const withLeadingSlash = configuredBasePath.startsWith("/") ? configuredBasePath : `/${configuredBasePath}`; + return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash; +} + +function readManifestBasePath() { + try { + const manifest = JSON.parse(readFileSync(path.resolve(".next", "routes-manifest.json"), "utf8")); + return normalizeBasePath(typeof manifest.basePath === "string" ? manifest.basePath : ""); + } catch { + return ""; + } +} + +const basePath = normalizeBasePath(process.env.NEXT_PUBLIC_BASE_PATH) || readManifestBasePath(); + function contentTypeFor(filePath) { if (filePath.endsWith(`${path.sep}.well-known${path.sep}api-catalog`)) { return apiCatalogContentType; } - return contentTypes.get(path.extname(filePath)) || "application/octet-stream"; + const typePath = filePath.endsWith(".br") ? filePath.slice(0, -3) : filePath; + + return contentTypes.get(path.extname(typePath)) || "application/octet-stream"; } -function headersFor(filePath) { - const headers = { "Content-Type": contentTypeFor(filePath) }; +function isNextStaticAsset(filePath) { + return filePath.includes(`${path.sep}_next${path.sep}static${path.sep}`); +} + +function headersFor(filePath, contentLength) { + const headers = { + "Content-Length": String(contentLength), + "Content-Type": contentTypeFor(filePath), + }; if (filePath.endsWith(`${path.sep}.well-known${path.sep}api-catalog`)) { headers.Link = apiCatalogLinkHeader; } + if (filePath.endsWith(".br")) { + headers["Content-Encoding"] = "br"; + headers.Vary = "Accept-Encoding"; + } + + if (isNextStaticAsset(filePath)) { + headers["Cache-Control"] = "public, max-age=31536000, immutable"; + } + return headers; } -function toFilePath(urlPath) { - const cleanPath = urlPath.split("?", 1)[0].split("#", 1)[0]; - let relativePath = cleanPath; +function isInsideOutputDirectory(filePath) { + const relativePath = path.relative(outputDirectory, filePath); + return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath)); +} + +function addStaticRoute(files, routePath, file) { + if (!files.has(routePath)) { + files.set(routePath, file); + } +} + +function addStaticRouteAliases(files, routePath, file) { + addStaticRoute(files, routePath, file); + + if (routePath === "/index.html") { + addStaticRoute(files, "/", file); + return; + } + + if (routePath.endsWith("/index.html")) { + const directoryRoute = routePath.slice(0, -"index.html".length); + addStaticRoute(files, directoryRoute, file); + addStaticRoute(files, directoryRoute.slice(0, -1), file); + } +} + +function collectStaticFiles(directory, files = new Map()) { + const entries = readdirSync(directory, { withFileTypes: true }); + + for (const entry of entries) { + const entryPath = path.join(directory, entry.name); + if (!isInsideOutputDirectory(entryPath)) { + continue; + } + + if (entry.isDirectory()) { + collectStaticFiles(entryPath, files); + continue; + } + + if (!entry.isFile()) { + continue; + } + + const routePath = `/${path.relative(outputDirectory, entryPath).split(path.sep).join("/")}`; + addStaticRouteAliases(files, routePath, { + filePath: entryPath, + size: statSync(entryPath).size, + }); + } + + return files; +} + +const staticFiles = collectStaticFiles(outputDirectory); + +function toRoutePath(urlPath) { + const queryIndex = urlPath.indexOf("?"); + const hashIndex = urlPath.indexOf("#"); + let pathEnd = urlPath.length; + if (queryIndex !== -1) pathEnd = Math.min(pathEnd, queryIndex); + if (hashIndex !== -1) pathEnd = Math.min(pathEnd, hashIndex); + const cleanPath = urlPath.slice(0, pathEnd); + let routePath = cleanPath.startsWith("/") ? cleanPath : `/${cleanPath}`; if (basePath) { - if (relativePath === "/") { - relativePath = `${basePath}/`; + if (routePath === "/") { + routePath = `${basePath}/`; } - if (relativePath === basePath) { - relativePath = `${basePath}/`; + if (routePath === basePath) { + routePath = `${basePath}/`; } - if (!relativePath.startsWith(basePath)) { + if (!routePath.startsWith(`${basePath}/`)) { return null; } - relativePath = relativePath.slice(basePath.length) || "/"; + routePath = routePath.slice(basePath.length) || "/"; } - const normalizedPath = relativePath === "/" ? "/index.html" : relativePath; - const tentativePath = path.join(outputDirectory, normalizedPath); - return normalizedPath.endsWith("/") ? path.join(tentativePath, "index.html") : tentativePath; + return routePath || "/"; } const server = createServer(async (request, response) => { const requestPath = request.url || "/"; const method = (request.method || "GET").toUpperCase(); - const filePath = toFilePath(requestPath); - - if (!filePath) { - response.writeHead(404); - response.end("Not found"); + if (method !== "GET" && method !== "HEAD") { + response.writeHead(405, { Allow: "GET, HEAD" }); + response.end("Method not allowed"); return; } - let finalPath = filePath; - - try { - const details = await stat(finalPath); - if (details.isDirectory()) { - finalPath = path.join(finalPath, "index.html"); - } - } catch { - if (!path.extname(finalPath)) { - finalPath = path.join(finalPath, "index.html"); - } - } + const routePath = toRoutePath(requestPath); + const staticFile = routePath ? staticFiles.get(routePath) : null; - if (!existsSync(finalPath)) { + if (!staticFile) { response.writeHead(404); response.end("Not found"); return; } - if (method !== "GET" && method !== "HEAD") { - response.writeHead(405, { Allow: "GET, HEAD" }); - response.end("Method not allowed"); - return; - } - - response.writeHead(200, headersFor(finalPath)); + response.writeHead(200, headersFor(staticFile.filePath, staticFile.size)); if (method === "HEAD") { response.end(); return; } - createReadStream(finalPath).pipe(response); + createReadStream(staticFile.filePath).pipe(response); }); server.listen(port, () => { diff --git a/selfhosted/server.ts b/selfhosted/server.ts index 3f7e17e..9f7228a 100644 --- a/selfhosted/server.ts +++ b/selfhosted/server.ts @@ -15,6 +15,7 @@ import { validatePayload } from "./validate.js"; const port = Number(process.env.PORT || 3000); const host = process.env.HOST || "0.0.0.0"; const outputDirectory = path.resolve(process.env.OUT_DIR || "out"); +const outputDirectoryWithSeparator = `${outputDirectory}${path.sep}`; const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const API_CATALOG_CONTENT_TYPE = 'application/linkset+json; profile="https://www.rfc-editor.org/info/rfc9727"'; @@ -32,6 +33,7 @@ const contentTypes = new Map([ [".jpeg", "image/jpeg"], [".woff", "font/woff"], [".woff2", "font/woff2"], + [".wasm", "application/wasm"], [".br", "application/octet-stream"], [".yaml", "application/yaml; charset=utf-8"], [".yml", "application/yaml; charset=utf-8"], @@ -44,9 +46,21 @@ function contentTypeFor(filePath: string): string { return API_CATALOG_CONTENT_TYPE; } + if (filePath.endsWith(".json.br")) { + return "application/json; charset=utf-8"; + } + + if (filePath.endsWith(".css.br")) { + return "text/css; charset=utf-8"; + } + return contentTypes.get(path.extname(filePath)) || "application/octet-stream"; } +function isNextStaticAsset(filePath: string): boolean { + return filePath.includes(`${path.sep}_next${path.sep}static${path.sep}`); +} + function headersFor(filePath: string): Record { const headers: Record = { "Content-Type": contentTypeFor(filePath) }; @@ -54,6 +68,15 @@ function headersFor(filePath: string): Record { headers.Link = API_CATALOG_LINK_HEADER; } + if (filePath.endsWith(".json.br") || filePath.endsWith(".css.br")) { + headers["Content-Encoding"] = "br"; + headers.Vary = "Accept-Encoding"; + } + + if (isNextStaticAsset(filePath)) { + headers["Cache-Control"] = "public, max-age=31536000, immutable"; + } + return headers; } @@ -125,14 +148,12 @@ function htmlResponse(res: ServerResponse, status: number, body: string): void { /** Serve a static file from the output directory. */ async function serveStatic(res: ServerResponse, urlPath: string, method: string): Promise { - const cleanPath = urlPath.split("?", 1)[0].split("#", 1)[0]; - const normalizedPath = cleanPath === "/" ? "/index.html" : cleanPath; + const normalizedPath = urlPath === "/" ? "/index.html" : urlPath; let filePath = path.resolve(path.join(outputDirectory, normalizedPath)); - const resolvedOutputDir = path.resolve(outputDirectory); // Prevent path traversal outside the output directory - if (!filePath.startsWith(resolvedOutputDir + path.sep) && filePath !== resolvedOutputDir) { + if (!filePath.startsWith(outputDirectoryWithSeparator) && filePath !== outputDirectory) { res.writeHead(403); res.end("Forbidden"); return; diff --git a/skills/agent-render-linking/SKILL.md b/skills/agent-render-linking/SKILL.md index 98f7264..874858d 100644 --- a/skills/agent-render-linking/SKILL.md +++ b/skills/agent-render-linking/SKILL.md @@ -28,6 +28,7 @@ Use this fragment shape: #agent-render=v1.. (plain | lz | deflate) #agent-render=v1.arx.. (arx) #agent-render=v1.arx2.. (arx2) +#agent-render=v1.arx3.. (arx3) ``` Supported codecs: @@ -35,12 +36,13 @@ Supported codecs: - `lz`: `lz-string` compressed JSON encoded for URL-safe transport - `deflate`: deflate-compressed UTF-8 JSON bytes encoded as base64url - `arx`: domain-dictionary substitution + brotli (quality 11) + binary-to-text encoding (~70% smaller than deflate with baseBMP). Fetch the shared dictionary from `https://agent-render.com/arx-dictionary.json` to apply substitutions locally before brotli compression. Four wire shapes: baseBMP (~62k safe BMP code points, ~15.92 bits/char, best raw density), base1k (1774 Unicode code points U+00A1–U+07FF), base64url (ASCII `A-Za-z0-9-_`, `B.` prefix — good when Unicode would be percent-encoded), and base76 (77-char ASCII). The product encoder tries all four and picks the shortest **transport** length. -- `arx2`: tuple-envelope transport + `https://agent-render.com/arx2-dictionary.json` overlay + the shared arx dictionary + brotli (quality 11) + the same four wire shapes. Existing arx links remain valid; prefer arx2 when it is the shortest transport. +- `arx2`: tuple-envelope transport + `https://agent-render.com/arx2-dictionary.json` overlay (or pre-compressed `https://agent-render.com/arx2-dictionary.json.br`) + the shared arx dictionary + brotli (quality 11) + the same four wire shapes. Existing arx links remain valid; prefer arx2 when it is the shortest transport. +- `arx3`: same tuple envelope, arx2 overlay, shared arx dictionary, and brotli bytes as arx2, but the dense baseBMP wire may win by decoded visible character length. Use it for trusted surfaces that preserve Unicode fragments and strict visible URL budgets. Prefer arx2/base64url or UUID mode when the target platform rewrites, truncates, or previews long links aggressively. - packed wire mode (`p: 1`) may be used automatically to shorten transport keys Prefer: 1. shortest valid fragment for the target surface -2. codec priority `arx2 -> arx -> deflate -> lz -> plain` unless explicitly overridden +2. codec priority `arx3 -> arx2 -> arx -> deflate -> lz -> plain` unless explicitly overridden 3. packed wire mode when available ## Envelope shape @@ -170,6 +172,7 @@ Construct the final URL as: https://agent-render.com/#agent-render=v1.. (plain | lz | deflate) https://agent-render.com/#agent-render=v1.arx.. (arx) https://agent-render.com/#agent-render=v1.arx2.. (arx2) +https://agent-render.com/#agent-render=v1.arx3.. (arx3) ``` For `plain`: @@ -213,7 +216,7 @@ To use the dictionary for local `arx` encoding: - Base76 uses 77 ASCII fragment-safe characters. ~6.27 bits/char 5. Prepend `v1.arx..` to form the fragment payload (use the same dictionary version used for substitution) -The dictionary includes JSON envelope boilerplate patterns (like `","kind":"Markdown","content":"`), JSON-escaped Markdown syntax, programming keywords, and common English words. The viewer loads the same dictionary on startup to reverse substitutions during decode. +The dictionary includes JSON envelope boilerplate patterns, JSON-escaped Markdown syntax, and programming-language patterns that are already present in the shipped corpus. The viewer tries the pre-compressed dictionary first on default ARX/ARX2/ARX3 encode or decode paths, falls back to the JSON file, and falls back again to its built-in table if external fetches fail. If the dictionary fetch fails, fall back to `deflate` codec. @@ -234,15 +237,17 @@ Then apply substitutions in this order: 4. Try baseBMP, base1k, base64url, and base76; choose the shortest transport representation 5. Prepend `v1.arx2..`, using the shared arx dictionary version +For `arx3`, use the same tuple, substitution, and brotli bytes as arx2, then choose the baseBMP wire when the visible character count is the optimization target. Prepend `v1.arx3..`, using the shared arx dictionary version. Do not invent a new dictionary entry unless it is backed by corpus evidence and improves the benchmark gate. + ## Practical limits Respect these limits: -- target fragment budget: about 8,192 characters +- target fragment budget: about 8,192 decoded visible characters - target decoded payload budget: about 200,000 characters - strict Discord practical budget for linked text workflows: about 1,500 characters If a link is getting too large: -1. try `arx2` first, then `arx`, then `deflate`, then `lz`, then `plain` +1. try `arx3` first for trusted Unicode-preserving surfaces; otherwise try `arx2`, then `arx`, then `deflate`, then `lz`, then `plain` 2. allow packed wire mode 3. trim unnecessary prose or metadata 4. prefer a focused artifact over a bloated one @@ -252,7 +257,7 @@ If a link is getting too large: When the caller provides a strict budget (for example 1,500 chars): -1. encode using all available candidates (`arx2/arx/deflate/lz/plain`, packed and non-packed where applicable) +1. encode using all available candidates (`arx3/arx2/arx/deflate/lz/plain`, packed and non-packed where applicable) 2. choose the shortest fragment that is within budget 3. if no candidate fits, return the shortest fragment plus a clear budget failure explanation diff --git a/skills/selfhosted-agent-render/SKILL.md b/skills/selfhosted-agent-render/SKILL.md index 4a5372e..2e65ffb 100644 --- a/skills/selfhosted-agent-render/SKILL.md +++ b/skills/selfhosted-agent-render/SKILL.md @@ -49,7 +49,7 @@ Response (`201`): } ``` -The `payload` field is the same payload string used in fragment links — the fragment body after `#`. Use the same envelope format and codecs (`plain`, `lz`, `deflate`, `arx`, `arx2`) described in the `agent-render-linking` skill. +The `payload` field is the same payload string used in fragment links — the fragment body after `#`. Use the same envelope format and codecs (`plain`, `lz`, `deflate`, `arx`, `arx2`, `arx3`) described in the `agent-render-linking` skill. ### Read an artifact @@ -241,7 +241,7 @@ A single `patch` string may contain multiple `diff --git` sections. Encode the envelope using the same codec pipeline as fragment links: 1. Serialize envelope as compact JSON -2. Encode with a codec (`plain` = base64url, `lz` = lz-string, `deflate` = deflate + base64url, or the async arx/arx2 pipelines) +2. Encode with a codec (`plain` = base64url, `lz` = lz-string, `deflate` = deflate + base64url, or the async arx/arx2/arx3 pipelines) 3. Prepend `agent-render=v1..` 4. POST the resulting string as the `payload` field diff --git a/src/app/globals.css b/src/app/globals.css index 4b6e41e..b7e6335 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,5 +1,4 @@ @import "tailwindcss"; -@import "@git-diff-view/react/styles/diff-view-pure.css"; :root { /* ── Surface hierarchy — warm ivory with sunset undertone ── */ @@ -750,6 +749,7 @@ select { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; + scroll-margin-top: 4.5rem; } /* ── Compact toolbar ribbon ── */ @@ -1402,6 +1402,22 @@ select { padding: 0.75rem 0; } +.artifact-raw-source, +.json-raw-source { + max-height: min(68vh, 42rem); + overflow: auto; + margin: 0; + border: 1px solid var(--surface-code-chrome-border); + border-radius: var(--radius-lg); + background: var(--surface-code); + color: var(--surface-code-text); + padding: 1rem 1.05rem 1.15rem; + font-family: var(--font-mono), monospace; + font-size: 0.82rem; + line-height: 1.65; + white-space: pre; +} + .json-node { border-left: 1px solid color-mix(in srgb, var(--border) 82%, transparent); margin-left: 0.8rem; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index dceb4a6..03556de 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,6 @@ import type { Metadata } from "next"; import { Fraunces, IBM_Plex_Mono, IBM_Plex_Sans } from "next/font/google"; import type { ReactNode } from "react"; -import { ThemeProvider } from "@/components/theme-provider"; import { getCanonicalSiteUrl, getMetadataBase } from "@/lib/site/canonical-base"; import "./globals.css"; @@ -32,9 +31,21 @@ export const metadata: Metadata = { description: "A static, zero-retention artifact viewer shell for fragment-based markdown, code, diff, CSV, and JSON payloads.", }; +const themeInitScript = ` +(() => { + try { + const stored = window.localStorage.getItem("theme"); + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + const resolved = stored === "dark" || ((stored === null || stored === "system") && prefersDark) ? "dark" : "light"; + document.documentElement.classList.toggle("dark", resolved === "dark"); + document.documentElement.style.colorScheme = resolved; + } catch {} +})(); +`; + /** - * Root layout for the static shell that installs fonts and global theme context for all viewer states. - * Accepts `children` from Next.js app routing and wraps them with the shared `ThemeProvider`. + * Root layout for the static shell that installs fonts and the pre-hydration theme class. + * Accepts `children` from Next.js app routing and keeps the exported shell provider-free. * Sets hydration-safe HTML/body structure used by lazy renderer mounts and fallback screens. */ export default function RootLayout({ @@ -44,10 +55,11 @@ export default function RootLayout({ }>) { return ( + +