diff --git a/.gitignore b/.gitignore index 2f74949f..56fca8cb 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ htmlcov/ # CodeGraph local index .codegraph/ + +# Storybook local build output +storybook-static/ diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 00000000..34c6c5fa --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,9 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + framework: '@storybook/react-vite', + stories: ['../stories/**/*.stories.@(ts|tsx)'], + addons: ['@storybook/addon-docs'], +}; + +export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 00000000..3470a462 --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,11 @@ +import type { Preview } from '@storybook/react'; + +import '../src/styles.css'; + +const preview: Preview = { + parameters: { + controls: { disable: true }, + }, +}; + +export default preview; diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a42dd5a5..5c9e6900 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -25,6 +25,8 @@ The protected standalone product provides: - dependency-locked Chromium/Firefox/WebKit rich-clipboard release assurance; and - a network-free Office renderer for deterministic DOCX, XLSX, and PPTX output. +A named editor-chrome theme-token catalog and Storybook inventory for repeating toolbar/editor objects are Active PR / Proposed. Hosts override `--cwl-*` on `.cwl-editor`; Inkspan does not own Figma Variables, brand certification, or design-tool sync. + Hosts own transport, authorization, tenant isolation, persistence, credentials, migration, retention, and model-use policy. They also own authentication, deployment, durable audit, print destination policy, and any durable PDF/print-service authority; persistence includes durable storage and commit authority. Inkspan therefore never opens a production collaboration connection, chooses a tenant, stores a provider secret, creates a durable database transaction, decides a retention schedule, authorizes an AI operation, or claims that a browser print destination constitutes a durable authorized export. A standalone adopter can provide those capabilities directly; a CWL host can provide them through shared platform services. diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d1d3dc..0008f483 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ Historical release entries from **0.1.0 through 0.5.27** are preserved verbatim ## [Unreleased] +### Added +- Named the repeating editor chrome as a host-facing theme-token catalog and Storybook inventory so hosts can override `--cwl-*` custom properties on `.cwl-editor` after checking WCAG 2.2 contrast, without editing Inkspan internals. Color catalog values now distinguish light, dark, and `@media print` remaps; forced-colors mode is not treated as a token assignment. Hosts can call `getEditorThemeTokenContrast()` to compare inventoried pairs, including `--cwl-accent` on `--cwl-accent-soft`, against the 4.5:1 text threshold via `meetsTextContrast`. + +### Accessibility +- Prepared the active-PR dark active-toolbar accent change from protected-main `#4493f8` to `#58a6ff`, increasing `--cwl-accent` text on `--cwl-accent-soft: #163356` from about 4.13:1 to about 5.06:1 so the candidate default 13px active-button text meets the WCAG 2.2 4.5:1 normal-text threshold; this remains active-PR evidence until protected integration. `getEditorThemeTokenContrast()` checks catalog values, and host overrides must be re-checked with `contrastRatioFromHex(actualForegroundHex, actualBackgroundHex)` using the actual resolved colors. +- Restored a visible `:focus-visible` indicator on the editable textbox, mapped it to `CanvasText` in forced-colors mode, and suppresses that interactive focus chrome under `@media print`; dependency-locked Chromium, Firefox, and WebKit acceptance exercises the packed stylesheet on the real `role="textbox"` surface. + ## [0.6.0] — 2026-08-10 ### Release @@ -15,7 +22,7 @@ Historical release entries from **0.1.0 through 0.5.27** are preserved verbatim - Added the selected standalone Markdown or HTML value to an explicitly configured SSR native form field, preserving controlled-value precedence, external form association, React attribute escaping, and the synchronous post-hydration TipTap transaction mirror ### Security -- Added a fail-closed draft release asset inventory gate that requires exactly one npm tarball, one Office wheel, and `SHA256SUMS`, rejects stale or unexpected draft assets before immutable publication, and verifies every GitHub-reported `sha256:` asset digest against the transferred local file +- Added a fail-closed draft release asset inventory gate that requires exactly one npm tarball, one Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`, rejects stale or unexpected draft assets before immutable publication, and verifies every GitHub-reported `sha256:` asset digest against the transferred local file - Kept SSR document disclosure opt-in through `formFieldName`; hidden-field values remain client-controlled submission data and do not replace host authentication, authorization, tenant isolation, CSRF defenses, server validation, durable concurrency, or persistence controls - Kept collaborative Yjs document content out of server markup until the host-owned client collaboration lifecycle is bound - Added packed headless Markdown authority verification that rejects external runtime imports, dynamic module loaders, ambient network/environment credential access, React/TipTap/Yjs runtime coupling, CWL host coupling, and model credential references from the dedicated conversion artifact @@ -106,7 +113,7 @@ Historical release entries from **0.1.0 through 0.5.27** are preserved verbatim - Corrected the autosave onboarding so initial and replacement validators are checked before use and come from the durable host's server-issued strong `ETag` rather than local revision evidence; missing, weak, or malformed validators fail closed in the example - Documented that host-owned save callbacks must apply their own timeout or abort signal because an unresolved callback intentionally retains the active single-flight operation; retry policy remains host-owned - Added a deterministic repository contract test and APA 7th-style doctoring for README, npm-search, Node.js package-export discoverability, RFC 9110 validator ownership, and quoted opaque-tag syntax -- Added operator, doctoring, and release evidence for the durable autosave session, coherent recovery-time flush snapshots, host ownership boundaries, exact-head verification, and acquisition-review scope +- Added operator, doctoring, and release evidence for the durable autosave session, coherent recovery-time flush snapshots, exact-head verification, and acquisition-review scope ## [0.5.28] — 2026-08-05 diff --git a/README.md b/README.md index f2b02332..dfc5713c 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,25 @@ export function Example() { Set `mode="html"` when `value` and `onChange` should exchange HTML. Both modes embed accepted images as inline data URIs. +### Theme the repeating chrome + +Override the named `--cwl-*` custom properties on `.cwl-editor` after checking +WCAG 2.2 contrast for body text and active toolbar text. Use +`getEditorThemeTokenContrast('cwl-accent', 'cwl-accent-soft', 'dark')` to inspect +Inkspan's catalog baseline. After overriding CSS, pass the actual resolved +foreground/background hex values to `contrastRatioFromHex()` before shipping the +host theme. Do not edit Inkspan internals. See +[`docs/design-tokens.md`](docs/design-tokens.md) for the token catalog and +[`docs/storybook-inventory.md`](docs/storybook-inventory.md) for the Storybook +preview of toolbar and editor chrome. + +```css +.cwl-editor { + --cwl-accent: #0b6e4f; + --cwl-accent-soft: #d8f3e8; +} +``` + ### Server rendering `CwlEditor` and `CollaborativeCwlEditor` are safe to include in server-rendered diff --git a/docs/CONTRACTS.md b/docs/CONTRACTS.md index 52ab1bdf..5a62716e 100644 --- a/docs/CONTRACTS.md +++ b/docs/CONTRACTS.md @@ -138,7 +138,7 @@ Expected degraded states are explicit rather than mapped to false success: A public release binds one exact integrated protected source head to package/artifact identity, applicable CI/security/accessibility/document-fidelity evidence, owned production coverage, public-docstring evidence, SBOM/provenance/reproducibility where configured, formal review requirements, rollback guidance, and post-publication smoke verification. -Before immutable publication, the canonical draft inventory is **exactly three regular top-level files**: exactly one npm tarball, exactly one Inkspan Office wheel, and `SHA256SUMS`. Missing, stale, unexpected, duplicate, non-regular, incompletely uploaded, or digest-mismatched assets fail closed. After upload and before publication, the authenticated paginated GitHub Releases API inventory must equal the local release directory by exact asset name, every remote asset must report an uploaded state, and every GitHub-reported `sha256:` digest must equal the digest of the exact transferred local file. The workflow does not silently delete an unexpected remote asset to make an ambiguous draft look clean. +Before immutable publication, the canonical draft inventory is **exactly four regular top-level files**: exactly one npm tarball, exactly one Inkspan Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`. Missing, stale, unexpected, duplicate, non-regular, incompletely uploaded, or digest-mismatched assets fail closed. After upload and before publication, the authenticated paginated GitHub Releases API inventory must equal the local release directory by exact asset name, every remote asset must report an uploaded state, and every GitHub-reported `sha256:` digest must equal the digest of the exact transferred local file. The workflow does not silently delete an unexpected remote asset to make an ambiguous draft look clean. Rollback must preserve readable canonical documents and must not require silently reinterpreting persisted schema or selector-projection semantics. Host-owned migrations, persistence rollback, annotation re-anchoring, tenant recovery, and deployment rollback remain host responsibilities unless a future versioned contract explicitly assigns them to Inkspan. @@ -152,9 +152,10 @@ Rollback must preserve readable canonical documents and must not require silentl | autosave | local ordering/state, callback contract, validator validation | transport, durable CAS, retry/offline policy, persistence | | collaboration | provider-neutral editor/Yjs binding | provider lifecycle, rooms, identity, authorization, persistence, awareness privacy | | Office rendering | deterministic bounded JSON→artifact conversion | file destination policy, downstream distribution, tenant authorization | +| editor chrome theming (Active PR / Proposed) | named `--cwl-*` tokens, DTCG interchange snapshot, Storybook inventory, inventoried pair contrast including `--cwl-accent` on `--cwl-accent-soft` | host brand CSS, contrast certification, Figma Variables, design-tool sync | | naruon composition | stable local package/module boundary | authenticated compose transport, tenancy, provider/model policy | | model assistance | deterministic proposal acceptance boundary | provider, prompt/data policy, credentials, human approval | -| release evidence | exact three-file draft inventory, package/artifact/digest verification and repository evidence | downstream deployment and operational rollout | +| release evidence | exact four-file draft inventory, package/artifact/digest verification and repository evidence | downstream deployment and operational rollout | ## Related canonical documents diff --git a/docs/DOCUMENTATION_FITNESS.md b/docs/DOCUMENTATION_FITNESS.md index cdb52e18..46d9737f 100644 --- a/docs/DOCUMENTATION_FITNESS.md +++ b/docs/DOCUMENTATION_FITNESS.md @@ -56,6 +56,7 @@ Document fitness and implementation maturity are independent. A `present_current | Unified stable registry release train | ADR 0019, protected release workflow and release doctoring | `present_current` | `implemented_on_protected_main` | Stable npm/Office version equality, OIDC Trusted Publishing, exact-artifact publication and post-publication digest verification are source-integrated; live registry publication remains separate operational evidence. | | Framework-neutral Markdown package boundary | ADR 0020, protected `@contextualwisdomlab/cwl-editor/markdown` package subpath and shared policy modules from #114 | `present_current` | `implemented_on_protected_main` | Server/worker consumers can reuse deterministic Markdown/HTML/email/plain-text conversion without evaluating the React/TipTap editor graph. | | CSS paged-media print boundary | ADR 0021, protected `src/styles.css`, packaged stylesheet evidence and real-browser print tests from #116/#127 | `present_current` | `implemented_on_protected_main` | Declarative print output removes screen-only clipping/chrome while preserving authored content without creating a durable PDF service. | +| Editor chrome design tokens / Storybook inventory | ADR 0031, `docs/design-tokens.md`, doctoring, token catalog, and Storybook stories | `present_current` | `implemented_on_active_pr` | Hosts can name, override, and preview repeating toolbar/editor tokens without treating the interchange snapshot as shipped protected-main authority. | | Informative DOCX PNG figures | ADR 0022, Office schema/renderer/tests and guidance | `present_current` | `implemented_on_protected_main` | Strict bounded inline PNG figures preserve informative alternative text without remote-resource or arbitrary OOXML authority. | | DOCX bounded rich-text runs | ADR 0023, Office schema/renderer/tests and doctoring | `present_current` | `implemented_on_protected_main` | Ordered bold/italic/underline runs preserve common inline fidelity under one bounded deterministic contract. | | DOCX bounded paragraph alignment | ADR 0024, Office schema/renderer/tests, Office guidance and doctoring | `present_current` | `implemented_on_protected_main` | `paragraph` and `rich_paragraph` preserve explicit left/center/right/justify alignment while omission retains inherited/default behavior. | @@ -82,6 +83,7 @@ The documentation pack is substantially complete for acquisition review, but rep 1. The protected manifests now agree at `0.6.0`, while registry operational acceptance remains open under issue #118 because the exact protected release still needs its tag/GitHub Release, live npm/PyPI Trusted Publisher execution, and public artifact digest verification. 2. Future protected-source changes must continue to reconcile PRD/TRD/Architecture/ADR/UML/DATA_MODEL/security/test/operability/traceability semantics rather than treating this baseline as permanently complete. 3. Documentation becoming mergeable, green, or protected-merged is never a reason for the commercial loop to stop; the next safe product, release, security, accessibility, package, Office-fidelity, or interoperability lane continues. +4. Hosts still need a named, Storybook-previewable chrome-token catalog so brand theming does not require editing Inkspan internals; that lane is Active PR / Proposed and must not be described as shipped until protected integration. ## Sufficiency decision diff --git a/docs/OPERABILITY.md b/docs/OPERABILITY.md index e96d053c..23a22c6e 100644 --- a/docs/OPERABILITY.md +++ b/docs/OPERABILITY.md @@ -60,7 +60,7 @@ Release publication occurs only from an exact integrated protected head. The rel Before publication: 1. fetch the current protected `main` ref and require the release tag event commit SHA to equal that exact integration tip, not merely be an ancestor of it; -2. build exactly three regular top-level release files: exactly one npm tarball, exactly one Inkspan Office wheel, and `SHA256SUMS`; +2. build exactly four regular top-level release files: exactly one npm tarball, exactly one Inkspan Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`; 3. reject missing, duplicate, non-regular, stale, or unexpected local entries and verify the local digests; 4. after upload, query the authenticated paginated GitHub Releases API and require the resumed remote draft asset-name set to equal the local release directory exactly; 5. require every remote asset state to be uploaded and every GitHub-reported `sha256:` digest to equal the exact transferred local file digest; diff --git a/docs/PRD.md b/docs/PRD.md index 899086f3..3ccec07c 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -146,3 +146,5 @@ Shareable acquisition evidence excludes production tenant content and credential Protected `main` is the sole implemented baseline. Open PRs may describe Proposed or Active work but are not shipped contracts until protected integration. Canonical documentation must state when a requirement is target architecture rather than current implementation. SafeClipboard, real Chromium/Firefox/WebKit release assurance, lifecycle observation, the root security disclosure lifecycle, toolbar shortcut accessibility metadata, SSR/native-form serialization, revision-scoped selection evidence, W3C text-position selector evidence, document-transition evidence, and envelope identity migration routing are implemented on protected `main`. + +A named editor-chrome theme-token catalog, DTCG 2025.10 interchange snapshot, and Storybook inventory for repeating toolbar/editor objects are Active PR / Proposed and are not shipped claims until protected integration. Hosts must check inventoried active-chrome contrast (`--cwl-accent` on `--cwl-accent-soft`) in addition to body text. diff --git a/docs/README.md b/docs/README.md index 2b617f7c..3f9c3270 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,7 @@ This directory is the discoverable index for Inkspan's product, technical, secur | [`package-distribution.md`](package-distribution.md) | Buyer-facing public npm package entrypoints, packaged contents, runtime dependency boundaries, and consumer verification | | [`email-output.md`](email-output.md) | Deterministic email fragment/full-document authority, language/direction metadata, accessibility and host-owned transport boundary | | [`print-output.md`](print-output.md) | Browser print/paged-media presentation, accessibility/fidelity limits, host-owned governed-export boundary, and rollback | +| [`design-tokens.md`](design-tokens.md) | Host-facing editor chrome tokens, DTCG 2025.10 interchange snapshot, and Storybook inventory (Active PR / Proposed) | | [`UML.md`](UML.md) | Component, sequence, state and authority-flow diagrams | | [`DATA_MODEL.md`](DATA_MODEL.md) | Conceptual evidence/domain model and persistence ownership | | [`THREAT_MODEL.md`](THREAT_MODEL.md) | Trust boundaries, abuse cases, security/privacy controls and residual risks | diff --git a/docs/TEST_STRATEGY.md b/docs/TEST_STRATEGY.md index 34e903fc..d9cde200 100644 --- a/docs/TEST_STRATEGY.md +++ b/docs/TEST_STRATEGY.md @@ -56,7 +56,7 @@ At minimum, maintain regressions for: - autosave stale validators, conflict/failure recovery, ambiguous transport outcomes, duplicate/no-op lifecycle transitions, callback exceptions, queue bounds, flush/close behavior, and durable-validator coherence; - selection/revision races and document movement during asynchronous hashing; - Office formula prefixes, invalid XML characters, malicious strings, path/publication races, invalid worksheet names, invalid freeze panes, cyclic input, pathological nesting, excessive container size, and partial write failure; -- package/release stale draft assets, unexpected or non-regular local entries, exact three-file inventory violations, incomplete remote uploads, GitHub-vs-local digest mismatch, stale exact-head evidence, mutable provenance inputs, and isolated packed-consumer behavior. +- package/release stale draft assets, unexpected or non-regular local entries, exact four-file inventory violations, incomplete remote uploads, GitHub-vs-local digest mismatch, stale exact-head evidence, mutable provenance inputs, and isolated packed-consumer behavior. ## Concurrency and failure testing @@ -68,7 +68,7 @@ Host persistence transactions, tenant isolation, distributed collaboration autho A release candidate requires the exact integrated protected head to satisfy applicable CI, security, JavaScript/TypeScript 100% statement/branch/function/line coverage, Office coverage.py 100% report plus public-docstring completeness, package-consumer, accessibility, browser differential, Office artifact, SBOM/provenance, reproducibility, unresolved-thread, actually required independent-review, and release-workflow gates. Queued, skipped-required, cancelled, absent, stale-head, predecessor-head, status-only, or synthetic-merge evidence is not accepted as success. -The release workflow must also satisfy the normative `docs/CONTRACTS.md` draft inventory contract: exactly one npm tarball, exactly one Office wheel, and `SHA256SUMS`; no other top-level entry; remote uploaded asset names exactly equal local names; and every GitHub-reported `sha256:` digest equals the exact transferred local file digest. Missing, stale, unexpected, non-regular, incomplete, or digest-mismatched assets are failures, not cleanup opportunities. +The release workflow must also satisfy the normative `docs/CONTRACTS.md` draft inventory contract: exactly one npm tarball, exactly one Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`; no other top-level entry; remote uploaded asset names exactly equal local names; and every GitHub-reported `sha256:` digest equals the exact transferred local file digest. Missing, stale, unexpected, non-regular, incomplete, or digest-mismatched assets are failures, not cleanup opportunities. The 0.6.0 rich-clipboard release line specifically requires the protected dependency-locked **Playwright 1.62.0** Chromium, Firefox, and WebKit differential gate on the exact integrated protected release candidate before publication. Deterministic jsdom coverage remains useful but is not a substitute for browser-engine acceptance. Tagged release evidence must be generated anew from the release candidate and must verify the exact packed npm artifact, not merely reuse a previously green feature-branch run. diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index d120cec7..ffe5d86d 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -21,6 +21,7 @@ This record maps durable Inkspan product decisions to authoritative standards, p | Browser clipboard behavior | Security-relevant rich HTML handling requires actual paste-pipeline integration and bounded semantic reconstruction before editor state | WHATWG HTML parsing; W3C Clipboard API | protected-main rich-clipboard unit/integration corpus and SafeClipboard ADR | Protected jsdom/TipTap integration success is not universal browser-engine conformance | | Cross-engine release assurance | The same committed synthetic adversarial corpus runs under required Chromium, Firefox, and WebKit projects; exact package-lock and packed npm artifact SHA-256 digests are required, and only focused standards-grounded safe differences may be admitted | WHATWG HTML Living Standard; W3C Clipboard API and events; Playwright 1.62 release notes and browser/project documentation | ADR 0016, protected-main browser evidence source/workflows, TEST_STRATEGY, OPERABILITY and UML | Protected-main implementation is the release-policy authority; every release candidate must regenerate fresh exact-source/lock/run/browser evidence bound to the exact packed npm artifact SHA-256 and does not claim byte-identical browser serialization or branded enterprise-policy coverage | | CSS paged-media output | Shipped editor CSS has a declarative print boundary that removes interactive chrome and screen clipping while preserving authored document flow and bounded fragmentation behavior | W3C Media Queries Level 3; CSS Fragmentation Level 3; CSS Paged Media Level 3 as tracked draft input | protected-main #116 packaged stylesheet, real-browser print-media evidence, ADR 0021, print doctoring and tests | `implemented_on_protected_main`; browser print styling does not create a durable PDF service, page-number/header authority, persistence, signing, or PDF-conformance claim | +| Editor chrome design tokens | Repeating toolbar/editor surfaces share named `--cwl-*` custom properties; hosts override those properties on `.cwl-editor` and preview them in Storybook | Design Tokens Format Module 2025.10; WCAG 2.2 contrast including inventoried `--cwl-accent` on `--cwl-accent-soft`; Storybook React/Vite | Active-PR doctoring `docs/doctoring/editor-design-tokens.md`, operator guide, ADR 0031, token catalog tests, and Storybook inventory | Proposed until protected `main`; the interchange snapshot is not complete DTCG conformance, Figma Variables sync, or a host WCAG certification | | Editor integration | Public behavior must exercise the actual TipTap/ProseMirror integration path, not an inert extension field or test-only hook | official TipTap and ProseMirror documentation for the locked dependency line | integration tests and package consumers | Inkspan does not claim compatibility with untested major-version integration semantics | | Collaboration | Inkspan provides provider-neutral editor/Yjs bindings; host owns provider lifecycle, room authorization, awareness privacy, persistence and audit | official Yjs/provider documentation plus Inkspan public contract | collaboration tests and architecture ownership matrix | No network-provider or tenant-authorization authority is implied | | Secure development | Security controls are developed test-first, with exact-head scanning/review/package evidence and root-cause regression | NIST SP 800-218 SSDF 1.1 | CI/security/SAST/package/provenance gates, doctoring and regression history | Repository evidence is not a claim of complete SSDF organizational conformance | @@ -38,6 +39,8 @@ Bray, T. (Ed.). (2015). *The I-JSON Message Format* (RFC 7493). RFC Editor. http Bray, T. (Ed.). (2017). *The JavaScript Object Notation (JSON) Data Interchange Format* (RFC 8259; STD 90). RFC Editor. https://doi.org/10.17487/RFC8259 +Design Tokens Community Group. (2025, October 28). *Design Tokens Format Module 2025.10* (Final Community Group Report). World Wide Web Consortium. https://www.w3.org/community/reports/design-tokens/CG-FINAL-format-20251028/ + Ecma International. (2021). *ECMA-376: Office Open XML file formats* (5th ed.). https://ecma-international.org/publications-and-standards/standards/ecma-376/ Ecma International. (2026). *ECMA-402: ECMAScript 2026 internationalization API specification* (13th ed.). https://402.ecma-international.org/ @@ -70,6 +73,8 @@ Rundgren, A., Jordan, B., & Erdtman, S. (2020). *JSON Canonicalization Scheme (J Souppaya, M., Scarfone, K., & Dodson, D. (2022). *Secure Software Development Framework (SSDF) Version 1.1: Recommendations for Mitigating the Risk of Software Vulnerabilities* (NIST SP 800-218). National Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-218 +Storybook. (n.d.). *Storybook for React with Vite*. Retrieved August 16, 2026, from https://storybook.js.org/docs/get-started/frameworks/react-vite + Web Hypertext Application Technology Working Group. (2026). *HTML Standard: Parsing HTML documents* (Living Standard). Retrieved August 10, 2026, from https://html.spec.whatwg.org/multipage/parsing.html World Wide Web Consortium. (2013). *PROV-DM: The PROV Data Model*. https://www.w3.org/TR/prov-dm/ diff --git a/docs/TRD.md b/docs/TRD.md index 80cec07a..22ecdaa7 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -85,9 +85,9 @@ The Office JSON→DOCX/XLSX/PPTX renderer is **network-free**, **macro-free**, m Protected DOCX fidelity includes informative bounded inline PNG figures under ADR 0022, bounded `rich_paragraph` bold/italic/underline runs under ADR 0023, exact optional `left`/`center`/`right`/`justify` alignment for `paragraph` and `rich_paragraph` under ADR 0024, and the same bounded alignment for `heading` under ADR 0025. Omitted alignment preserves inherited/default Word semantics. -Active PR #137 implements the Proposed ADR 0026 extension for one optional relationship-backed hyperlink on a rich-text run. That active contract deliberately permits only printable-ASCII absolute HTTP(S) targets up to 4,096 characters, rejects local/executable/data/mail/telephone/relative/protocol-relative/credential-bearing/backslash/whitespace-control/non-ASCII targets, preserves the exact accepted target and existing run emphasis, and remains network-free. Until protected integration, this is `implemented_on_active_pr`, not shipped authority. +Protected `main` implements Accepted ADR 0026 through integrated PR #137: one optional relationship-backed hyperlink on a rich-text run. The protected contract deliberately permits only printable-ASCII absolute HTTP(S) targets up to 4,096 characters, rejects local/executable/data/mail/telephone/relative/protocol-relative/credential-bearing/backslash/whitespace-control/non-ASCII targets, preserves the exact accepted target and existing run emphasis, and remains network-free. This is `implemented_on_protected_main`. -The protected additive fidelity contracts and active hyperlink proposal do not grant arbitrary style, font, color, spacing, indentation, arbitrary relationship IDs/types, internal/bookmark hyperlinks, field codes, raw OOXML, remote-resource fetching, decorative-image semantics, destination trust validation, or source-format parsing authority. +The protected additive fidelity and hyperlink contracts do not grant arbitrary style, font, color, spacing, indentation, arbitrary relationship IDs/types, internal/bookmark hyperlinks, field codes, raw OOXML, remote-resource fetching, decorative-image semantics, destination trust validation, or source-format parsing authority. Format-fidelity claims are limited to tested supported constructs. The renderer does not execute macros, scripts, formula calculation, embedded external resources, or Desktop Office automation. A stored external hyperlink relationship is document metadata, not permission to dereference or trust its destination. @@ -97,6 +97,8 @@ CSS paged-media print boundary is implemented on protected `main` under Accepted The CSS contract does not introduce JavaScript print orchestration, a PDF service, durable export authority, credentials, network access, identity, tenancy, persistence, page-number/header/footer generation, signature authority, or PDF-conformance claims. Browser/OS print destination and durable artifact policy remain host/user responsibilities. +A named editor-chrome theme-token catalog, DTCG 2025.10 interchange snapshot, and Storybook inventory are Active PR / Proposed. CSS remains runtime presentation authority. Hosts override `--cwl-*` on `.cwl-editor` after checking WCAG 2.2 contrast for body text and the inventoried active toolbar pair (`--cwl-accent` on `--cwl-accent-soft`). The catalog's optional print values are the `@media print` remaps; forced-colors mode does not assign those custom properties. This slice adds no Figma, network, persistence, credential, or model authority. + ## Model-assisted authoring Inkspan does not own model credentials, provider routing, prompt retention, external-data-use approval, tenant disclosure policy, or model audit. A host may route assistance through contextual-orchestrator or another approved provider. Model output is untrusted proposed content and cannot bypass deterministic clipboard/document/conversion validation, user/host approval, or durable save authorization. @@ -139,6 +141,6 @@ Queued, cancelled, skipped-required, absent, stale-head, predecessor-head, statu ## Implemented versus proposed -Protected `main` is the sole shipped implementation baseline. SafeClipboard, cross-engine browser assurance, the security disclosure lifecycle, autosave lifecycle observation, toolbar shortcut accessibility metadata, accessible editor placeholder semantics, SSR/native-form serialization, revision-scoped selection evidence, W3C text-position selector evidence, the React-free text-position-selector subpath, document-transition evidence, envelope identity routing, framework-neutral deterministic Markdown conversion, CSS paged-media print output, DOCX informative PNG figures, bounded rich-text runs, bounded paragraph alignment, bounded heading alignment, and the OIDC-backed unified stable registry release train are `implemented_on_protected_main`. +Protected `main` is the sole shipped implementation baseline. SafeClipboard, cross-engine browser assurance, the security disclosure lifecycle, autosave lifecycle observation, toolbar shortcut accessibility metadata, accessible editor placeholder semantics, SSR/native-form serialization, revision-scoped selection evidence, W3C text-position selector evidence, the React-free text-position-selector subpath, document-transition evidence, envelope identity routing, framework-neutral deterministic Markdown conversion, CSS paged-media print output, DOCX informative PNG figures, bounded rich-text runs, bounded paragraph alignment, bounded heading alignment, bounded DOCX rich-run external hyperlinks, and the OIDC-backed unified stable registry release train are `implemented_on_protected_main`. -The bounded DOCX rich-run external hyperlink contract in #137 is `implemented_on_active_pr` under Proposed ADR 0026. Open branches may extend the protected boundary, but no active-PR capability becomes shipped merely because its design, tests, or documentation are complete. +Open branches may extend the protected boundary, but no active-PR capability becomes shipped merely because its design, tests, or documentation are complete. diff --git a/docs/UML.md b/docs/UML.md index 5b67b163..de4e20d3 100644 --- a/docs/UML.md +++ b/docs/UML.md @@ -38,6 +38,8 @@ flowchart LR The host owns transport, authentication, authorization, tenant isolation, persistence, credentials, provider lifecycle, retention, deployment, durable audit, and model-use policy. Inkspan owns deterministic local editor/conversion/evidence behavior only. +A named editor-chrome theme-token catalog and Storybook inventory are Active PR / Proposed. Hosts override `--cwl-*` on `.cwl-editor` after checking WCAG 2.2 contrast for body text and active toolbar text (`--cwl-accent` on `--cwl-accent-soft`). CSS remains runtime presentation authority. Storybook previews a class-level chrome sample (including `:focus-visible`) and a live shipped Toolbar. It does not mount `CwlEditor`. Print-media remaps live in `@media print`; forced-colors mode does not assign those custom properties. Figma Variables, brand certification, and design-tool sync remain host-owned. + ## Rich paste sequence ```mermaid diff --git a/docs/adr/0031-editor-design-tokens-storybook.md b/docs/adr/0031-editor-design-tokens-storybook.md new file mode 100644 index 00000000..2e3cfdb0 --- /dev/null +++ b/docs/adr/0031-editor-design-tokens-storybook.md @@ -0,0 +1,45 @@ +# ADR 0031: Editor chrome design tokens and Storybook inventory + +Status: Proposed + +## Context + +Inkspan ships repeating toolbar buttons, groups, and editor chrome styled through `--cwl-*` custom properties. Hosts already re-theme by overriding those properties, but the names, light/dark/print values, and buyer next action live only inside protected `src/styles.css`. There is no protected-main typed catalog, Design Tokens Format Module interchange snapshot, or Storybook inventory of the repeating objects; those capabilities are proposed on this active PR. + +Inventorying the actual active-toolbar foreground/background pair also exposed a product-owned accessibility defect: protected main's dark `--cwl-accent: #4493f8` on `--cwl-accent-soft: #163356` produces about 4.13:1 for 13px active-button text, below the WCAG 2.2 4.5:1 normal-text threshold. Treating that shipped-default failure as a host override responsibility would contradict Inkspan's ownership of its default presentation. + +## Alternatives considered + +1. **Leave theming as undocumented CSS.** Rejected because hosts would keep reading internals and drift from the protected token set. +2. **Generate CSS from a new token source of truth.** Rejected for this slice because it would rewrite the protected stylesheet and collide with print, forced-colors, and Office-unrelated presentation contracts. +3. **Add Figma Variables sync or a design-tool connector.** Rejected because Figma/design-tool credentials and sync authority are host-owned; Inkspan remains independently usable. +4. **Disclose the failing shipped dark pair and require every host to override it.** Rejected because the protected default palette is Inkspan-owned; documenting a product defect does not repair it. +5. **Publish a named catalog plus Storybook inventory while CSS remains runtime authority, and repair the failing protected-main inventoried pair at the stylesheet boundary.** Selected because hosts can override and preview repeating chrome without moving presentation authority, while Inkspan remains accountable for its own defaults. + +## Decision + +If integrated, Inkspan will publish a host-facing theme-token catalog for nine inventoried chrome tokens, a Design Tokens Format Module 2025.10 interchange snapshot, and a Storybook inventory of repeating toolbar/editor objects. Inkspan's inventoried normal-text pairs will be required to meet the WCAG 2.2 4.5:1 threshold in the resulting protected light/dark/print defaults; the active-PR candidate therefore uses `#58a6ff` on `#163356` for dark active-toolbar text, about 5.06:1. `getEditorThemeTokenContrast()` evaluates only the catalog values for a named scheme. Hosts overriding `--cwl-*` on `.cwl-editor` must pass their actual resolved foreground/background hex values to `contrastRatioFromHex()` and re-check their resulting body and active-toolbar pairs. Unknown token names fail closed. No Figma, network, persistence, credential, or model authority is added. + +## Consequences and ownership trade-offs + +After integration, hosts gain a copyable token list and a Storybook preview. Inkspan keeps CSS as the runtime source and owns accessibility defects in its default token combinations. naruon and other CWL hosts can apply their own overrides through host-owned brand CSS, use `getEditorThemeTokenContrast()` to inspect the Inkspan catalog baseline, and use `contrastRatioFromHex()` to validate their actual resolved custom values. Complete DTCG conformance, Figma Variables, host-theme WCAG certification, and automated remediation of arbitrary host palettes remain out of scope. + +## Failure and recovery + +An unknown token name throws `EditorThemeTokenError` without reflecting caller input. A protected default pair that fails an applicable repository contrast contract must be repaired in Inkspan's token catalog and stylesheet together before integration or release. A host override that fails contrast is recovered by changing only the named host tokens, measuring the actual resolved custom pair with `contrastRatioFromHex()`, and rechecking the applicable WCAG threshold; do not edit Inkspan internals or disable forced-colors. + +## Security and privacy impact + +The proposed catalog contains only public presentation values. It does not carry document bodies, tenant identifiers, credentials, or diagnostics. Storybook is a local development preview and is not a production transport. + +## Compatibility and migration + +The catalog is additive if integrated. Existing CSS overrides on `.cwl-editor` continue to work. The active-PR candidate changes the dark default accent from `#4493f8` to `#58a6ff`; hosts that already override `--cwl-accent` are unaffected by that candidate default-value change but remain responsible for validating their actual custom pair. A later CSS token addition or default-value change must update the catalog, directly affected documentation/tests, and this ADR together. + +## Verification and acceptance evidence + +Required evidence includes token-catalog tests against `src/styles.css`, deterministic contrast assertions for inventoried normal-text pairs, resolved-hex override guidance tests, documentation-contract tests, Storybook inventory stories for toolbar button states and token swatches, and exact-head CI/coverage/package/security gates on the unchanged head. The accessibility regression must fail against protected main's `#4493f8`/`#163356` dark active pair and pass against the active-PR `#58a6ff`/`#163356` pair. This ADR stays Proposed until protected integration. + +## Rollback or supersession + +Before integration, rollback removes the catalog export, Storybook inventory/config, operator/doctoring records, and this ADR together. After integration, reverting only the compliant dark accent while retaining the active-text contrast requirement is not a valid partial rollback. Supersession requires a new ADR if CSS ceases to be runtime presentation authority or if a design-tool sync contract is accepted. diff --git a/docs/adr/README.md b/docs/adr/README.md index df8b7b80..b797812e 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -32,6 +32,7 @@ This index records durable architectural decisions. Protected-main implementatio | [0024](0024-bounded-docx-paragraph-alignment.md) | Accepted | Bounded paragraph alignment in deterministic DOCX output | | [0025](0025-bounded-docx-heading-alignment.md) | Accepted | Bounded heading alignment in deterministic DOCX output | | [0026](0026-bounded-docx-external-hyperlinks.md) | Accepted | Bounded external hyperlinks in deterministic DOCX rich text | +| [0031](0031-editor-design-tokens-storybook.md) | Proposed | Editor chrome design tokens and Storybook inventory | ## Decision discipline diff --git a/docs/design-tokens.md b/docs/design-tokens.md new file mode 100644 index 00000000..b1056994 --- /dev/null +++ b/docs/design-tokens.md @@ -0,0 +1,50 @@ +# Editor chrome design tokens + +Status: Active PR / Proposed + +Use this catalog when you need to re-theme Inkspan's repeating toolbar and editor chrome. Protected-main CSS defaults remain the shipped presentation baseline; the Active-PR repaired dark active-toolbar pair uses `--cwl-accent: #58a6ff` on `--cwl-accent-soft: #163356` and measures about 5.06:1. When a host overrides any color token, re-check WCAG 2.2 contrast for both body text (`--cwl-fg` on `--cwl-bg`) and active toolbar text (`--cwl-accent` on `--cwl-accent-soft`). Do not edit Inkspan internals. + +`getEditorThemeTokenContrast()` checks only Inkspan catalog values for the requested light/dark/print scheme; it does not read resolved host CSS. After an override, obtain the actual resolved hex colors from the host theme and call `contrastRatioFromHex(actualForegroundHex, actualBackgroundHex)` before shipping that theme. + +```css +.cwl-editor { + --cwl-accent: #0b6e4f; + --cwl-accent-soft: #d8f3e8; +} +``` + +```ts +import { + contrastRatioFromHex, + getEditorThemeTokenContrast, + listEditorThemeTokens, + toDesignTokenFormatGroup, +} from '@contextualwisdomlab/cwl-editor'; + +const tokens = listEditorThemeTokens(); +const dtcgGroup = toDesignTokenFormatGroup(); +const body = getEditorThemeTokenContrast('cwl-fg', 'cwl-bg', 'light'); +const activeDark = getEditorThemeTokenContrast('cwl-accent', 'cwl-accent-soft', 'dark'); +const actualForegroundHex = '#0b6e4f'; +const actualBackgroundHex = '#d8f3e8'; +const overrideRatio = contrastRatioFromHex(actualForegroundHex, actualBackgroundHex); +if (!body.meetsTextContrast || !activeDark.meetsTextContrast || overrideRatio < 4.5) { + throw new Error(activeDark.hostAction); +} +void tokens; +void dtcgGroup; +``` + +The default-theme checks above are active-PR product evidence, not protected-main shipped evidence or a host WCAG certification. The stylesheet remains runtime presentation authority. `toDesignTokenFormatGroup()` is an interchange snapshot aligned to Design Tokens Format Module 2025.10; it is not complete DTCG conformance or Figma Variables sync. + +Preview the repeating objects in Storybook (`pnpm storybook`) using the inventory in [`storybook-inventory.md`](storybook-inventory.md). + +```mermaid +flowchart LR + Host[Host brand CSS] --> Editor[".cwl-editor custom properties"] + Catalog[listEditorThemeTokens] --> Host + Editor --> Toolbar[".cwl-toolbar / .cwl-tb-btn"] + Editor --> Surface[".cwl-editor document surface"] + Storybook[Storybook inventory] --> Toolbar + Storybook --> Surface +``` diff --git a/docs/doctoring/editor-design-tokens.md b/docs/doctoring/editor-design-tokens.md new file mode 100644 index 00000000..17846810 --- /dev/null +++ b/docs/doctoring/editor-design-tokens.md @@ -0,0 +1,56 @@ +# Doctoring record: editor chrome design tokens + +**Date:** 2026-08-16 +**Status:** Active PR / Proposed +**Decision owner:** ContextualWisdomLab +**Scope:** Named `--cwl-*` theme tokens, DTCG 2025.10 interchange snapshot, and Storybook inventory for repeating toolbar/editor objects. + +## Buyer-visible gap + +Hosts embed Inkspan and need to match brand color, radius, and font without forking `src/styles.css`. The protected stylesheet already uses `--cwl-*` custom properties, but buyers have no protected-main typed catalog, interchange snapshot, or Storybook inventory of the repeating toolbar button and editor chrome. Theme work therefore still requires reading CSS internals until this active PR integrates. + +The same inventory exposed an Inkspan-owned protected-main default-theme defect rather than a host-only customization problem: dark `.cwl-tb-btn.is-active` renders `--cwl-accent` text on `--cwl-accent-soft` at about 4.13:1, below the WCAG 2.2 4.5:1 threshold for normal text. This Active PR now carries dark `--cwl-accent: #58a6ff` against unchanged `--cwl-accent-soft: #163356`, producing about 5.06:1 for the active toolbar pair. That repaired value is active-PR evidence, not shipped protected-main truth, until integration. Host overrides must still re-check their own resulting pairs. + +If contrast fails after a re-theme, override only the named tokens on `.cwl-editor` and re-check WCAG 2.2 text contrast for `--cwl-fg` on `--cwl-bg` and `--cwl-accent` on `--cwl-accent-soft`. `getEditorThemeTokenContrast()` reports Inkspan's catalog baseline only; after a host override, pass the actual resolved pair to `contrastRatioFromHex(actualForegroundHex, actualBackgroundHex)` before shipping. Do not disable forced-colors overrides. + +## Decision + +If integrated: + +1. Keep `src/styles.css` as runtime presentation authority. +2. Publish `listEditorThemeTokens()` / `getEditorThemeToken()` / `getEditorThemeTokenContrast()` / `contrastRatioFromHex()` / `toDesignTokenFormatGroup()` as a host-facing catalog of the nine inventoried chrome tokens and theme evidence: the name-based contrast helper evaluates the catalog baseline, while the hex helper evaluates actual resolved host colors. +3. Keep the catalog light/dark/print values synchronized with the stylesheet and require the inventoried active-toolbar text pair to meet the WCAG 2.2 4.5:1 threshold in Inkspan's own default themes. +4. Reject unknown token names with a stable payload-redacted `EditorThemeTokenError`. +5. Preview repeating `.cwl-tb-btn` states, the shipped Toolbar component, and token swatches in Storybook. +6. Add no network, persistence, credential, model, tenant, Figma, or design-tool sync authority. + +## Standards rationale + +The Design Tokens Format Module 2025.10 defines a vendor-neutral JSON interchange for token groups, `$type`, and `$value` (Design Tokens Community Group, 2025). This active PR emits a snapshot of Inkspan CSS custom properties in that shape. The report is a W3C Community Final Specification, not a W3C Standard, so this record does not claim W3C standardization or complete DTCG conformance. + +WCAG 2.2 requires at least 4.5:1 contrast for normal text under Success Criterion 1.4.3 and at least 3:1 for meaningful user-interface component boundaries/states under Success Criterion 1.4.11 (World Wide Web Consortium, 2024). The protected-main failing default is therefore an Inkspan-owned defect; this active PR repairs it at Inkspan's presentation boundary while host overrides remain the host's contrast responsibility. Storybook's React/Vite preview is the proposed inventory surface for repeating chrome (Storybook, n.d.). + +## Test-first evidence + +- Original RED: `src/designTokens.test.ts` failed because `./designTokens.js` did not exist. +- Initial GREEN: the active-PR catalog lists nine inventoried tokens, aligns light/dark/print color values with the matching `src/styles.css` media blocks, reports WCAG 2.2 contrast for inventoried color pairs, rejects unknown names without reflecting caller input, and emits a DTCG 2025.10 group. +- Accessibility RED: exact test-only head `a831359d1509811ab8777e7356f6ebd5f251b5cf` changed the active-chrome contract to require the dark `--cwl-accent` / `--cwl-accent-soft` pair to meet 4.5:1. The protected-main values remained `#4493f8` on `#163356` (about 4.13:1), so the new expectation could not pass without a real default-theme change. +- Accessibility GREEN: the active-PR dark `--cwl-accent` is `#58a6ff` in both the typed catalog and runtime stylesheet; against unchanged `#163356` it measures about 5.06:1 and `meetsTextContrast` is true. +- Override-truth RED: exact-head CI `32149806678` / build-test `95752733876` proved the documentation and API action text still conflated catalog-token contrast with resolved host override contrast. +- Override-truth repair: `getEditorThemeTokenContrast()` is explicitly catalog-only and custom host themes are checked with `contrastRatioFromHex()` using the actual resolved colors. + +## Residual risk + +Print media still remaps the color tokens after a host override. Forced-colors mode only restyles the toolbar focus outline to `CanvasText`; it does not assign `--cwl-*` values. The active-PR repaired dark `.cwl-tb-btn.is-active` pair (`--cwl-accent` on `--cwl-accent-soft`) meets the WCAG 2.2 4.5:1 normal-text threshold and also exceeds the 3:1 non-text threshold; protected main remains at the failing pre-repair pair until integration. `getEditorThemeTokenContrast()` reports only that catalog evidence; after overriding either token, hosts must call `contrastRatioFromHex(actualForegroundHex, actualBackgroundHex)` with the actual resolved values because custom values can reintroduce a contrast failure. The font token snapshot splits a CSS font-family list and does not execute CSS. Storybook success is not Chromium/Firefox/WebKit release evidence. + +## Rollback + +Rollback before integration removes the catalog export, this record, the operator guide, the Storybook inventory/config/stories, ADR 0031, the changelog entry, and the documentation-index rows together. After integration, reverting the compliant dark accent without also reverting the active-pair contrast contract would deliberately recreate a known accessibility defect and is not a valid partial rollback. + +## References (APA 7th edition) + +Design Tokens Community Group. (2025, October 28). *Design Tokens Format Module 2025.10* (Final Community Group Report). World Wide Web Consortium. https://www.w3.org/community/reports/design-tokens/CG-FINAL-format-20251028/ + +Storybook. (n.d.). *Storybook for React with Vite*. Retrieved August 16, 2026, from https://storybook.js.org/docs/get-started/frameworks/react-vite + +World Wide Web Consortium. (2024, December 12). *Web Content Accessibility Guidelines (WCAG) 2.2* (W3C Recommendation). https://www.w3.org/TR/WCAG22/ diff --git a/docs/doctoring/release-draft-asset-inventory.md b/docs/doctoring/release-draft-asset-inventory.md index efee470e..5675b4c2 100644 --- a/docs/doctoring/release-draft-asset-inventory.md +++ b/docs/doctoring/release-draft-asset-inventory.md @@ -1,21 +1,24 @@ # Doctoring record: exact draft release asset inventory -**Decision date:** 2026-08-07 +**Decision date:** 2026-08-07 +**Current contract correction:** 2026-08-18 **Scope:** GitHub Release publication only; no Inkspan runtime, editor, persistence, collaboration, provider, tenant, credential, or database behavior changes. ## Problem Inkspan's release workflow intentionally supports retrying an unpublished GitHub Release draft. The upload command uses `--clobber`, which replaces same-name release assets. A prior failed attempt or operator action can nevertheless leave a differently named asset in the draft. Publishing that draft while immutable releases are enabled would freeze the extra asset into the official release identity. -This is a provenance-completeness problem rather than a checksum-collision problem. The expected npm tarball, Office wheel, and checksum file can each be correctly hashed and attested while an unrelated fourth asset remains attached to the draft. Verifying only the expected local artifacts therefore does not prove that the complete release asset set is the reviewed set. +This is a provenance-completeness problem rather than a checksum-collision problem. The current protected release workflow produces four reviewed top-level assets: one npm tarball, one Inkspan Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`. All four can be correctly generated while an unrelated fifth asset remains attached to the draft. Verifying only the expected local artifacts therefore does not prove that the complete release asset set is the reviewed set. -A second local-boundary failure mode exists before upload. Counting only top-level regular files does not prove that the transfer directory has exactly three entries: an unexpected directory, symlink, socket, or other non-regular top-level entry could coexist with the three expected files. The shell upload glob can expand such an entry even though a `find ... -type f` count ignores it. The local release boundary therefore has to bind both the complete top-level entry set and the regular-file subset before attestation or upload. +A second local-boundary failure mode exists before upload. Counting only top-level regular files does not prove that the transfer directory has exactly four entries: an unexpected directory, symlink, socket, or other non-regular top-level entry could coexist with the four expected files. The shell upload glob can expand such an entry even though a `find ... -type f` count ignores it. The local release boundary therefore has to bind both the complete top-level entry set and the regular-file subset before attestation or upload. + +The original 2026-08-07 record described a three-file inventory because the release workflow at that stage did not yet publish the SPDX SBOM as a top-level release asset. Protected release source and executable contract tests now require `inkspan.spdx.json` as the fourth asset. This record supersedes that stale cardinality while retaining the original threat model and provenance rationale. ## Primary evidence GitHub documents that immutable-release protection begins after publication and that draft releases remain mutable beforehand. GitHub also recommends attaching all intended assets to the draft before publishing it. The GitHub CLI documents `gh release upload --clobber` as deleting and re-uploading an existing asset of the same name; it does not define `--clobber` as pruning differently named assets. The current Releases REST representation exposes each asset's `name`, `state`, and content `digest`, including SHA-256 digests. -Draft retrieval has an important API distinction. GitHub documents **List releases** as returning draft releases to authenticated callers with repository push access, while **Get a release by tag name** is explicitly documented as retrieving a published release. A pre-publication control must therefore not rely on the by-tag endpoint for draft evidence. Inkspan uses the paginated authenticated release listing, selects the exact tag, requires exactly one match, and then validates that match as a draft. +Draft retrieval has an important API distinction. GitHub documents **List releases** as returning draft releases to authenticated callers with repository push access, while **Get a release by tag name** is documented for a published release. A pre-publication control must therefore not rely on the by-tag endpoint for draft evidence. Inkspan uses the paginated authenticated release listing, selects the exact tag, requires exactly one match, and then validates that match as a draft. These properties make the pre-publication draft the last safe point at which Inkspan can verify both set completeness and byte identity without trying to repair an already immutable release. @@ -23,7 +26,7 @@ These properties make the pre-publication draft the last safe point at which Ink After the expected files cross the workflow-artifact privilege boundary and before attestation or upload, and again after upload before `gh release edit ... --draft=false`, the release workflow must: -1. Require the local transfer directory to contain exactly three top-level entries and require all three to be regular files: one `*.tgz`, one `*.whl`, and `SHA256SUMS`. Any extra directory, symlink, socket, device, or other non-regular top-level entry fails closed. +1. Require the local transfer directory to contain exactly four top-level entries and require all four to be regular files: one `*.tgz`, one `*.whl`, `inkspan.spdx.json`, and `SHA256SUMS`. Any extra directory, symlink, socket, device, regular file, or other top-level entry fails closed. 2. Paginate the authenticated GitHub **List releases** REST endpoint and select the exact release tag; require exactly one matching release object. 3. Require the selected remote object to remain a draft. 4. Compare the sorted local and remote asset-name sets for exact equality. @@ -37,14 +40,14 @@ The workflow does not automatically delete an unexpected stale asset. Automatic ### Covered -- A failed prior attempt leaves an obsolete wheel, tarball, checksum file, or other differently named asset in the draft. +- A failed prior attempt leaves an obsolete wheel, tarball, SBOM, checksum file, or other differently named asset in the draft. - A same-name remote asset contains bytes different from the transferred local file. - An upload is incomplete or an asset does not report the `uploaded` state. -- The local artifact directory unexpectedly contains multiple npm tarballs, multiple wheels, missing checksums, another regular file, or any additional non-regular top-level entry. +- The local artifact directory unexpectedly contains multiple npm tarballs, multiple wheels, a missing or duplicate `inkspan.spdx.json`, missing checksums, another regular file, or any additional non-regular top-level entry. - A symlink or directory replaces one of the expected regular files. - The release stops being a draft before the inventory check. - The draft cannot be uniquely identified in the complete authenticated release listing. -- A future refactor accidentally replaces draft-aware list evidence with the published-only by-tag endpoint. +- A future refactor accidentally replaces draft-aware list evidence with the published-only by-tag route. ### Deliberately outside this gate @@ -58,15 +61,17 @@ The comparison uses only public release filenames, asset states, tag identity, f ## Rollback -This change is workflow-only and can be reverted without changing package APIs or stored data. If GitHub changes draft visibility, release asset representation, pagination, or the digest contract, publication must remain fail-closed until the workflow and deterministic repository contract are updated against the new official API. Operators must not bypass the gate by weakening the expected artifact count or entry-type boundary, accepting ambiguous release matches, or accepting missing digest evidence. +This change is release-contract documentation and workflow assurance; it does not change package APIs or stored data. If GitHub changes draft visibility, release asset representation, pagination, or the digest contract, publication must remain fail-closed until the workflow and deterministic repository contract are updated against the new official API. Operators must not bypass the gate by weakening the expected artifact count or entry-type boundary, accepting ambiguous release matches, or accepting missing digest evidence. ## Verification -`src/releaseDraftAssetInventory.test.ts` fixes the security ordering and semantic markers in the permanent release workflow. It requires the exact-inventory check to occur after upload and before publication, requires the bounded three-file artifact set, requires paginated draft-aware list evidence rather than the published-only by-tag route, and requires state and digest validation plus explicit fail-closed diagnostics. On Linux, which is the release-runner class, the same test extracts and executes the exact reviewed shell body with a local fake `gh api` response and the runner's real Bash, `jq`, `find`, `diff`, and `sha256sum`. Deterministic fixtures prove the accepted exact-inventory path and fail-closed behavior for an unexpected remote asset, a digest mismatch, an incomplete upload state, and a release that is no longer a draft without network access or publication authority. +`src/releaseDraftAssetInventory.test.ts` is the executable protected release authority for the inventory. It requires the exact-inventory check to occur after upload and before publication, requires `expected_asset_count=4`, requires one npm tarball, one Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`, uses paginated draft-aware list evidence rather than a published-only by-tag route, and requires state and digest validation plus explicit fail-closed diagnostics. On Linux, which is the release-runner class, the same test extracts and executes the reviewed shell body with a local fake `gh api` response and the runner's real Bash, `jq`, `find`, `diff`, and `sha256sum`. + +`src/releaseDraftAssetEntryType.test.ts` separately exercises the pre-attestation local entry-type boundary. Historical RED `0d905d9b244d36d55317de2237e3e3480c7ece5f` proved that a regular-file-only count could admit an unexpected top-level directory under the then-current inventory. The current contract generalizes that invariant to the exact four-file set: complete top-level cardinality and regular-file cardinality must both match before attestation or upload. -`src/releaseDraftAssetEntryType.test.ts` separately extracts and executes the exact pre-attestation local validation shell. Its RED test commit `0d905d9b244d36d55317de2237e3e3480c7ece5f` proved that the earlier regular-file-only count admitted a fourth top-level directory. The production repair counts the complete top-level entry set as well as the regular-file subset, so the same deterministic fixture now fails closed before attestation or upload while the exact three-file fixture remains accepted. +`src/releaseContractCanonicalConsistency.test.ts` rejects stale three-file wording across `docs/CONTRACTS.md`, `docs/TEST_STRATEGY.md`, `docs/OPERABILITY.md`, and this doctoring record while requiring all canonical documents to name the protected four-file SBOM-inclusive inventory. This prevents documentation from drifting behind executable release behavior again. -The feature branch was created from protected `main` commit `ca49a3249403be88ba3cb7c9589b3652f820e17c`. Test-only commits preceded the corresponding workflow implementations. Exact-current-head CI, security, automated review, independent review, and branch protection remain authoritative; predecessor-head or synthetic-merge evidence is not accepted as completion evidence. +Exact-current-head CI, security, automated review, independent review, and branch protection remain authoritative. Predecessor-head, queued, cancelled, skipped, status-only, or synthetic-merge evidence is not completion evidence. ## References diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md new file mode 100644 index 00000000..239e40f6 --- /dev/null +++ b/docs/storybook-inventory.md @@ -0,0 +1,13 @@ +# Storybook inventory + +Status: Active PR / Proposed + +Run `pnpm storybook` to preview repeating Inkspan chrome before you override tokens in a host. **Active-PR repaired defaults require no host override for the inventoried active-toolbar contrast pair; protected main remains the pre-repair shipped baseline until integration.** Override tokens only when re-theming, and re-check the resulting host palette. The button-state story is a class-level chrome sample. **Editor Chrome / Live Toolbar** mounts the shipped Toolbar component so a token change is visible on the same object buyers already use; the Storybook story and repaired accent remain Active PR / Proposed. These stories do not mount `CwlEditor`. + +| Story | Repeating object | Host next action | +| --- | --- | --- | +| `Editor Chrome / Toolbar Button States` | `.cwl-tb-btn` default, `is-active`, disabled, and `:focus-visible`, plus `.cwl-collaboration-status` and `.cwl-editor__surface` | If re-theming, override `--cwl-accent` and `--cwl-accent-soft` on `.cwl-editor` after checking WCAG 2.2 contrast, including `getEditorThemeTokenContrast('cwl-accent', 'cwl-accent-soft', 'dark')` | +| `Editor Chrome / Live Toolbar` | shipped Toolbar buttons, groups, and disabled/active states | If re-theming, override `--cwl-accent` and `--cwl-accent-soft` on `.cwl-editor` after checking WCAG 2.2 contrast | +| `Editor Chrome / Theme Tokens` | Color, radius, and font tokens | Copy `toDesignTokenFormatGroup()` into a host theme file; do not edit Inkspan internals | + +Storybook is a development preview. It does not authorize documents, persist content, or replace packed-package evidence. diff --git a/package.json b/package.json index 4e55d924..7e861123 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,8 @@ "test": "vitest run", "test:watch": "vitest", "coverage": "vitest run --coverage", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", "test:package-config": "node --test ./scripts/revision-evidence-consumer-config.test.mjs ./scripts/release-metadata.test.mjs ./scripts/javascript-runtime-authority.test.mjs", "verify:package": "pnpm run test:package-config && node ./tests/package/verify-package.mjs && node ./tests/package/verify-editor-placeholder-package.mjs && node ./scripts/verify-canonical-envelope-package.mjs && node ./scripts/verify-revision-evidence-package.mjs && node ./scripts/verify-framework-free-revision-evidence-package.mjs && node ./scripts/verify-framework-free-envelope-identity-package.mjs && node ./tests/package/verify-framework-free-autosave-package.mjs && node ./scripts/verify-text-position-selector-package.mjs && node ./scripts/verify-text-position-selector-subpath-package.mjs && node ./scripts/verify-markdown-subpath-package.mjs" }, @@ -143,6 +145,9 @@ "yjs": "^13.6.30" }, "devDependencies": { + "@storybook/addon-docs": "^10.5.8", + "@storybook/react": "^10.5.8", + "@storybook/react-vite": "^10.5.8", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.5.2", @@ -155,6 +160,7 @@ "jsdom": "^25.0.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "storybook": "^10.5.8", "typescript": "^5.7.3", "vite": "^6.4.3", "vite-plugin-dts": "^4.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a3d3e0e..00b6f66e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,15 @@ importers: specifier: ^13.6.30 version: 13.6.31 devDependencies: + '@storybook/addon-docs': + specifier: ^10.5.8 + version: 10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(esbuild@0.25.12)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(vite@6.4.3(@types/node@22.20.1)) + '@storybook/react': + specifier: ^10.5.8 + version: 10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(typescript@5.9.3) + '@storybook/react-vite': + specifier: ^10.5.8 + version: 10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(esbuild@0.25.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(typescript@5.9.3)(vite@6.4.3(@types/node@22.20.1)) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.9.1 @@ -107,6 +116,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + storybook: + specifier: ^10.5.8 + version: 10.5.8(@types/react@18.3.31)(react@18.3.1) typescript: specifier: ^5.7.3 version: 5.9.3 @@ -251,6 +263,24 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -415,6 +445,15 @@ packages: resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': + resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} + peerDependencies: + typescript: '>= 4.3.x' + vite: ^6.4.3 + peerDependenciesMeta: + typescript: + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -431,6 +470,12 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + '@microsoft/api-extractor-model@7.33.8': resolution: {integrity: sha512-aIcoQggPyer3B6Ze3usz0YWC/oBwUHfRH5ETUsr+oT2BRA6SfTJl7IKPcPZkX4UR+PohowzW4uMxsvjrn8vm+w==} @@ -447,6 +492,246 @@ packages: '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + '@napi-rs/wasm-runtime@1.2.3': + resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 + + '@oxc-parser/binding-android-arm-eabi@0.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.24.2': + resolution: {integrity: sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.24.2': + resolution: {integrity: sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.24.2': + resolution: {integrity: sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.24.2': + resolution: {integrity: sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + resolution: {integrity: sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + resolution: {integrity: sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + resolution: {integrity: sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + resolution: {integrity: sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + resolution: {integrity: sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + resolution: {integrity: sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + resolution: {integrity: sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + resolution: {integrity: sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + resolution: {integrity: sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + resolution: {integrity: sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + resolution: {integrity: sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + resolution: {integrity: sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + resolution: {integrity: sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + resolution: {integrity: sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==} + cpu: [x64] + os: [win32] + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -637,6 +922,90 @@ packages: '@rushstack/ts-command-line@5.3.10': resolution: {integrity: sha512-fwI076HYknC0IrMXdY6UmjDv+PH7NHhNJX3/pY2UblSE5XrXgndXZPiOe/6ZtuFpn6DvVDVNhtkIzQ+Qu/MhVQ==} + '@storybook/addon-docs@10.5.8': + resolution: {integrity: sha512-NlHiMKW/UvW/uL8HXFDCEVwoH3qZeGYZ/qlWax4d7H471b/T54MBq2KcB4ZrdA785FfIH3numAJdBb5jwn00Mg==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.5.8 + peerDependenciesMeta: + '@types/react': + optional: true + + '@storybook/builder-vite@10.5.8': + resolution: {integrity: sha512-UeRnn7yT55WmBlHNOQzLrvN7vsHEvVgIukhKDO+4cMbGXN87wZkbxhx6NstpuXRH8OxGqwKS0SZNVp+SC1ftLQ==} + peerDependencies: + storybook: ^10.5.8 + vite: ^6.4.3 + + '@storybook/csf-plugin@10.5.8': + resolution: {integrity: sha512-/FHiMyOWWEXfwK/lM0WxmkP9GLzbSJJuzGtfeuNWSOVDnvAMbjavitxfHb5wSbWKIQo0XYC1EJ2Y7x91XNYP4w==} + peerDependencies: + esbuild: ^0.25.0 + rollup: '*' + storybook: ^10.5.8 + vite: ^6.4.3 + webpack: '*' + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + + '@storybook/icons@2.1.0': + resolution: {integrity: sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@storybook/react-dom-shim@10.5.8': + resolution: {integrity: sha512-N8D13/Xny+V3kfe1KBgsAHS0nKWXLLdgOOXS9poKdYzVwVCN+CGEGBxWX0zMMtdCptqa6/57em9coPlZMoO+bg==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.5.8 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@storybook/react-vite@10.5.8': + resolution: {integrity: sha512-ioMJGi4YzueGsJBlYio+2+UhfCFB9QV5Bs1lOilkek+a4BZgKJl0D1mVSJl6k96stQBPZmLgI9/l0hLVcUL6Kg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.5.8 + typescript: '>= 4.9.x' + vite: ^6.4.3 + peerDependenciesMeta: + typescript: + optional: true + + '@storybook/react@10.5.8': + resolution: {integrity: sha512-6qqkmqX6imtL+0Z9Uan2tIfYivOI0FiVmWr0zpqqQR15AkJ18JfNcNTQoyjeAlCO0Kei56SWqnu2qLq52TYplg==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.5.8 + typescript: '>= 4.9.x' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + typescript: + optional: true + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -848,6 +1217,9 @@ packages: '@tiptap/starter-kit@2.27.2': resolution: {integrity: sha512-bb0gJvPoDuyRUQ/iuN52j1//EtWWttw+RXAv1uJxfR0uKf8X7uAqzaOOgwjknoCIDC97+1YHwpGdnRjpDkOBxw==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -872,6 +1244,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -884,6 +1259,9 @@ packages: '@types/mdurl@2.0.0': resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/mdx@2.0.14': + resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} + '@types/node@22.20.1': resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} @@ -898,6 +1276,9 @@ packages: '@types/react@18.3.31': resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@types/turndown@5.0.6': resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} @@ -919,6 +1300,9 @@ packages: '@vitest/browser': optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@3.2.7': resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==} @@ -933,6 +1317,9 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@3.2.7': resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} @@ -942,9 +1329,15 @@ packages: '@vitest/snapshot@3.2.7': resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@3.2.7': resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@3.2.7': resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==} @@ -977,6 +1370,9 @@ packages: '@vue/shared@3.5.39': resolution: {integrity: sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==} + '@webcontainer/env@1.1.1': + resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} + acorn@8.17.0: resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} @@ -1045,6 +1441,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + ast-v8-to-istanbul@0.3.12: resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==} @@ -1069,6 +1469,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -1151,6 +1555,18 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1163,6 +1579,10 @@ packages: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -1185,6 +1605,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1229,12 +1653,21 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + expect-type@1.4.0: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} @@ -1294,6 +1727,10 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1352,13 +1789,27 @@ packages: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1418,6 +1869,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} @@ -1449,6 +1903,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1502,6 +1960,9 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -1527,9 +1988,20 @@ packages: nwsapi@2.2.24: resolution: {integrity: sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + oxc-parser@0.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.24.2: + resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -1550,6 +2022,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1647,6 +2123,15 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} + peerDependencies: + typescript: '>= 4.3.x' + + react-docgen@8.0.3: + resolution: {integrity: sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==} + engines: {node: ^20.9.0 || >=22} + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -1663,6 +2148,10 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + recast@0.23.21: + resolution: {integrity: sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==} + engines: {node: '>= 4'} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -1690,6 +2179,10 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1746,6 +2239,21 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + storybook@10.5.8: + resolution: {integrity: sha512-rR4oFMSiWBSqI0lvsJPtcQUPj8+hzj3TkLu+Mw61Wo6YxPSb5FsLSHai0jZnuaIdKIlmu25KCfwlSQl4e1uvnA==} + hasBin: true + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + prettier: ^2 || ^3 + vite-plus: ^0.1.15 || ^0.2.0 + peerDependenciesMeta: + '@types/react': + optional: true + prettier: + optional: true + vite-plus: + optional: true + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -1766,10 +2274,18 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} @@ -1792,6 +2308,9 @@ packages: resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} engines: {node: '>=18'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1832,6 +2351,17 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turndown-plugin-gfm@1.0.2: resolution: {integrity: sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg==} @@ -1857,6 +2387,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -1964,6 +2498,9 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -2007,6 +2544,22 @@ packages: utf-8-validate: optional: true + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -2184,11 +2737,43 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': dependencies: - '@csstools/css-tokenizer': 3.0.4 + tslib: 2.8.1 + optional: true - '@csstools/css-tokenizer@3.0.4': {} + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true '@esbuild/aix-ppc64@0.25.12': optional: true @@ -2279,6 +2864,14 @@ snapshots: '@istanbuljs/schema@0.1.6': {} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@5.9.3)(vite@6.4.3(@types/node@22.20.1))': + dependencies: + glob: 13.0.6 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + vite: 6.4.3(@types/node@22.20.1) + optionalDependencies: + typescript: 5.9.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2298,6 +2891,12 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mdx-js/react@3.1.1(@types/react@18.3.31)(react@18.3.1)': + dependencies: + '@types/mdx': 2.0.14 + '@types/react': 18.3.31 + react: 18.3.1 + '@microsoft/api-extractor-model@7.33.8(@types/node@22.20.1)': dependencies: '@microsoft/tsdoc': 0.16.0 @@ -2335,6 +2934,147 @@ snapshots: '@mixmark-io/domino@2.2.0': {} + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + optional: true + + '@oxc-project/types@0.127.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + optional: true + + '@oxc-resolver/binding-android-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.24.2': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + optional: true + '@pkgjs/parseargs@0.11.0': optional: true @@ -2466,6 +3206,101 @@ snapshots: transitivePeerDependencies: - '@types/node' + '@storybook/addon-docs@10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(esbuild@0.25.12)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(vite@6.4.3(@types/node@22.20.1))': + dependencies: + '@mdx-js/react': 3.1.1(@types/react@18.3.31)(react@18.3.1) + '@storybook/csf-plugin': 10.5.8(esbuild@0.25.12)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(vite@6.4.3(@types/node@22.20.1)) + '@storybook/icons': 2.1.0(react@18.3.1) + '@storybook/react-dom-shim': 10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1)) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + storybook: 10.5.8(@types/react@18.3.31)(react@18.3.1) + ts-dedent: 2.3.0 + optionalDependencies: + '@types/react': 18.3.31 + transitivePeerDependencies: + - '@types/react-dom' + - esbuild + - rollup + - vite + - webpack + + '@storybook/builder-vite@10.5.8(esbuild@0.25.12)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(vite@6.4.3(@types/node@22.20.1))': + dependencies: + '@storybook/csf-plugin': 10.5.8(esbuild@0.25.12)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(vite@6.4.3(@types/node@22.20.1)) + storybook: 10.5.8(@types/react@18.3.31)(react@18.3.1) + ts-dedent: 2.3.0 + vite: 6.4.3(@types/node@22.20.1) + transitivePeerDependencies: + - esbuild + - rollup + - webpack + + '@storybook/csf-plugin@10.5.8(esbuild@0.25.12)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(vite@6.4.3(@types/node@22.20.1))': + dependencies: + storybook: 10.5.8(@types/react@18.3.31)(react@18.3.1) + unplugin: 2.3.11 + optionalDependencies: + esbuild: 0.25.12 + rollup: 4.62.2 + vite: 6.4.3(@types/node@22.20.1) + + '@storybook/global@5.0.0': {} + + '@storybook/icons@2.1.0(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@storybook/react-dom-shim@10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + storybook: 10.5.8(@types/react@18.3.31)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.31 + '@types/react-dom': 18.3.7(@types/react@18.3.31) + + '@storybook/react-vite@10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(esbuild@0.25.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(typescript@5.9.3)(vite@6.4.3(@types/node@22.20.1))': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@6.4.3(@types/node@22.20.1)) + '@rollup/pluginutils': 5.4.0(rollup@4.62.2) + '@storybook/builder-vite': 10.5.8(esbuild@0.25.12)(rollup@4.62.2)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(vite@6.4.3(@types/node@22.20.1)) + '@storybook/react': 10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(typescript@5.9.3) + empathic: 2.0.1 + magic-string: 0.30.21 + react: 18.3.1 + react-docgen: 8.0.3 + react-dom: 18.3.1(react@18.3.1) + resolve: 1.22.12 + storybook: 10.5.8(@types/react@18.3.31)(react@18.3.1) + tsconfig-paths: 4.2.0 + vite: 6.4.3(@types/node@22.20.1) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - esbuild + - rollup + - supports-color + - webpack + + '@storybook/react@10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1))(typescript@5.9.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.5.8(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@10.5.8(@types/react@18.3.31)(react@18.3.1)) + react: 18.3.1 + react-docgen: 8.0.3 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + react-dom: 18.3.1(react@18.3.1) + storybook: 10.5.8(@types/react@18.3.31)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.31 + '@types/react-dom': 18.3.7(@types/react@18.3.31) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.7 @@ -2697,6 +3532,11 @@ snapshots: '@tiptap/extension-text-style': 2.27.2(@tiptap/core@2.27.2(@tiptap/pm@2.27.2)) '@tiptap/pm': 2.27.2 + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/argparse@1.0.38': {} '@types/aria-query@5.0.4': {} @@ -2729,6 +3569,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/doctrine@0.0.9': {} + '@types/estree@1.0.9': {} '@types/linkify-it@5.0.0': {} @@ -2740,6 +3582,8 @@ snapshots: '@types/mdurl@2.0.0': {} + '@types/mdx@2.0.14': {} + '@types/node@22.20.1': dependencies: undici-types: 6.21.0 @@ -2755,6 +3599,8 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/resolve@1.20.6': {} + '@types/turndown@5.0.6': {} '@types/use-sync-external-store@0.0.6': {} @@ -2790,6 +3636,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + '@vitest/expect@3.2.7': dependencies: '@types/chai': 5.2.3 @@ -2806,6 +3660,10 @@ snapshots: optionalDependencies: vite: 6.4.3(@types/node@22.20.1) + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + '@vitest/pretty-format@3.2.7': dependencies: tinyrainbow: 2.0.0 @@ -2822,10 +3680,20 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + '@vitest/spy@3.2.7': dependencies: tinyspy: 4.0.4 + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@vitest/utils@3.2.7': dependencies: '@vitest/pretty-format': 3.2.7 @@ -2877,6 +3745,8 @@ snapshots: '@vue/shared@3.5.39': {} + '@webcontainer/env@1.1.1': {} + acorn@8.17.0: {} agent-base@7.1.4: {} @@ -2924,6 +3794,10 @@ snapshots: assertion-error@2.0.1: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + ast-v8-to-istanbul@0.3.12: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -2948,6 +3822,10 @@ snapshots: node-releases: 2.0.51 update-browserslist-db: 1.2.3(browserslist@4.28.5) + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + cac@6.7.14: {} call-bind-apply-helpers@1.0.2: @@ -3017,12 +3895,25 @@ snapshots: deep-eql@5.0.2: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + delayed-stream@1.0.0: {} dequal@2.0.3: {} diff@8.0.4: {} + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -3041,6 +3932,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.1: {} + entities@4.5.0: {} entities@6.0.1: {} @@ -3097,12 +3990,16 @@ snapshots: escape-string-regexp@4.0.0: {} + esprima@4.0.1: {} + estree-walker@2.0.2: {} estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 + esutils@2.0.3: {} + expect-type@1.4.0: {} exsolve@1.1.0: {} @@ -3168,6 +4065,12 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -3218,10 +4121,20 @@ snapshots: dependencies: hasown: 2.0.4 + is-docker@3.0.0: {} + is-fullwidth-code-point@3.0.0: {} + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-potential-custom-element-name@1.0.1: {} + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isexe@2.0.0: {} isomorphic.js@0.2.5: {} @@ -3295,6 +4208,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@6.2.1: dependencies: universalify: 2.0.1 @@ -3327,6 +4242,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.5.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -3378,6 +4295,8 @@ snapshots: dependencies: brace-expansion: 5.0.9 + minimist@1.2.8: {} + minipass@7.1.3: {} mlly@1.8.2: @@ -3397,8 +4316,62 @@ snapshots: nwsapi@2.2.24: {} + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + orderedmap@2.1.1: {} + oxc-parser@0.127.0: + dependencies: + '@oxc-project/types': 0.127.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + + oxc-resolver@11.24.2: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.24.2 + '@oxc-resolver/binding-android-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-x64': 11.24.2 + '@oxc-resolver/binding-freebsd-x64': 11.24.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.24.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.24.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-musl': 11.24.2 + '@oxc-resolver/binding-openharmony-arm64': 11.24.2 + '@oxc-resolver/binding-wasm32-wasi': 11.24.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.24.2 + package-json-from-dist@1.0.1: {} parse5@7.3.0: @@ -3416,6 +4389,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.3 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + pathe@2.0.3: {} pathval@2.0.1: {} @@ -3557,6 +4535,25 @@ snapshots: quansync@0.2.11: {} + react-docgen-typescript@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + react-docgen@8.0.3: + dependencies: + '@babel/core': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + '@types/doctrine': 0.0.9 + '@types/resolve': 1.20.6 + doctrine: 3.0.0 + resolve: 1.22.12 + strip-indent: 4.1.1 + transitivePeerDependencies: + - supports-color + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -3571,6 +4568,14 @@ snapshots: dependencies: loose-envify: 1.4.0 + recast@0.23.21: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -3622,6 +4627,8 @@ snapshots: rrweb-cssom@0.8.0: {} + run-applescript@7.1.0: {} + safer-buffer@2.1.2: {} saxes@6.0.0: @@ -3658,6 +4665,32 @@ snapshots: std-env@3.10.0: {} + storybook@10.5.8(@types/react@18.3.31)(react@18.3.1): + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.1.0(react@18.3.1) + '@testing-library/dom': 10.4.1 + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/spy': 3.2.4 + '@webcontainer/env': 1.1.1 + esbuild: 0.25.12 + jsonc-parser: 3.3.1 + open: 10.2.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.24.2 + recast: 0.23.21 + semver: 7.8.5 + use-sync-external-store: 1.6.0(react@18.3.1) + ws: 8.21.3 + optionalDependencies: + '@types/react': 18.3.31 + transitivePeerDependencies: + - bufferutil + - react + - utf-8-validate + string-argv@0.3.2: {} string-width@4.2.3: @@ -3680,10 +4713,14 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 + strip-indent@4.1.1: {} + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 @@ -3706,6 +4743,8 @@ snapshots: glob: 10.5.0 minimatch: 10.2.5 + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinyexec@0.3.2: {} @@ -3739,6 +4778,16 @@ snapshots: dependencies: punycode: 2.3.1 + ts-dedent@2.3.0: {} + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + turndown-plugin-gfm@1.0.2: {} turndown@7.2.4: @@ -3755,6 +4804,13 @@ snapshots: universalify@2.0.1: {} + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.17.0 + picomatch: 4.0.5 + webpack-virtual-modules: 0.6.2 + update-browserslist-db@1.2.3(browserslist@4.28.5): dependencies: browserslist: 4.28.5 @@ -3869,6 +4925,8 @@ snapshots: webidl-conversions@7.0.0: {} + webpack-virtual-modules@0.6.2: {} + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 @@ -3903,6 +4961,12 @@ snapshots: ws@8.21.0: {} + ws@8.21.3: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} diff --git a/src/designTokenDocumentation.test.ts b/src/designTokenDocumentation.test.ts new file mode 100644 index 00000000..7337452b --- /dev/null +++ b/src/designTokenDocumentation.test.ts @@ -0,0 +1,94 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +const repositoryPath = (path: string): string => resolve(process.cwd(), path); + +const repositoryFile = (path: string): string => + readFileSync(repositoryPath(path), 'utf8'); + +const normalize = (value: string): string => value.replace(/\s+/gu, ' ').trim(); + +describe('editor design-token documentation contract', () => { + it('tells hosts how to re-theme repeating chrome without editing internals', () => { + const readme = normalize(repositoryFile('README.md')); + const operatorGuide = normalize(repositoryFile('docs/design-tokens.md')); + const inventory = normalize(repositoryFile('docs/storybook-inventory.md')); + const doctoring = repositoryFile('docs/doctoring/editor-design-tokens.md'); + const changelog = normalize(repositoryFile('CHANGELOG.md')); + const index = repositoryFile('docs/README.md'); + const adrIndex = repositoryFile('docs/adr/README.md'); + const adr = repositoryFile('docs/adr/0031-editor-design-tokens-storybook.md'); + + expect(index).toContain('[`design-tokens.md`](design-tokens.md)'); + expect(adrIndex).toContain('[0031](0031-editor-design-tokens-storybook.md) | Proposed'); + expect(repositoryFile('docs/TRD.md')).toContain('theme-token catalog'); + expect(repositoryFile('docs/UML.md')).toContain('Active PR / Proposed'); + expect(repositoryFile('docs/UML.md')).toContain('--cwl-*'); + expect(existsSync(repositoryPath('docs/adr/0027-editor-design-tokens-storybook.md'))).toBe( + false, + ); + expect(repositoryFile('docs/TRACEABILITY.md')).toContain('ADR 0031'); + expect(repositoryFile('docs/TRACEABILITY.md')).not.toContain('ADR 0027'); + expect(doctoring).toContain('ADR 0031'); + expect(doctoring).not.toContain('ADR 0027'); + expect(operatorGuide).toContain('When a host overrides any color token'); + expect(operatorGuide).toContain('Active-PR repaired dark active-toolbar pair uses'); + expect(operatorGuide).not.toContain('shipped dark active-toolbar pair now uses'); + expect(operatorGuide).toContain('getEditorThemeTokenContrast'); + expect(operatorGuide).toContain("getEditorThemeTokenContrast('cwl-accent', 'cwl-accent-soft', 'dark')"); + expect(operatorGuide).toContain('`getEditorThemeTokenContrast()` checks only Inkspan catalog values'); + expect(operatorGuide).toContain( + 'contrastRatioFromHex(actualForegroundHex, actualBackgroundHex)', + ); + expect(operatorGuide).toContain('meetsTextContrast'); + expect(operatorGuide).toContain('contrastRatioFromHex'); + expect(operatorGuide).toContain('Do not edit Inkspan internals'); + expect(readme).toContain('catalog baseline'); + expect(readme).toContain('contrastRatioFromHex'); + expect(inventory).toContain('Editor Chrome / Toolbar Button States'); + expect(inventory).toContain('Editor Chrome / Live Toolbar'); + expect(inventory).toContain('shipped Toolbar'); + expect(inventory).toContain(':focus-visible'); + expect(inventory).toContain('Active-PR repaired defaults require no host override'); + expect(inventory).not.toContain('Shipped defaults require no host override'); + expect(inventory).toContain('If re-theming, override `--cwl-accent`'); + expect(doctoring).toContain('cwl-accent-soft'); + expect(doctoring).toContain('meets the WCAG 2.2 4.5:1'); + expect(doctoring).toContain('This Active PR now carries dark `--cwl-accent: #58a6ff`'); + expect(doctoring).not.toContain('Inkspan now ships dark `--cwl-accent: #58a6ff`'); + expect(doctoring).toContain('host-facing catalog of the nine inventoried chrome tokens'); + expect(doctoring).not.toContain('host-facing catalog of the nine shipped chrome tokens'); + expect(doctoring).toContain('producing about 5.06:1'); + expect(doctoring).toContain('Host overrides must still re-check their own resulting pairs'); + expect(doctoring).toContain('contrastRatioFromHex'); + expect(doctoring).not.toContain( + "Hosts must still call `getEditorThemeTokenContrast('cwl-accent', 'cwl-accent-soft', 'dark')` after overriding either token", + ); + expect(doctoring).toContain('Design Tokens Format Module 2025.10'); + expect(doctoring).toContain( + 'https://www.w3.org/community/reports/design-tokens/CG-FINAL-format-20251028/', + ); + expect(doctoring).toContain('https://www.w3.org/TR/WCAG22/'); + expect(doctoring).toContain('https://storybook.js.org/docs/get-started/frameworks/react-vite'); + expect(adr).toContain('Status: Proposed'); + expect(adr).toContain('If integrated, Inkspan will publish a host-facing theme-token catalog'); + expect(adr).not.toContain('Inkspan publishes a host-facing theme-token catalog'); + expect(changelog).toContain('Named the repeating editor chrome as a host-facing theme-token catalog'); + expect(changelog).toContain('Prepared the active-PR dark active-toolbar accent'); + expect(changelog).not.toContain('Raised the shipped dark active-toolbar accent'); + expect(repositoryFile('.storybook/main.ts')).toContain("@storybook/react-vite"); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain('cwl-tb-btn'); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain('autoFocus'); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain('disabled'); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain('cwl-editor__surface'); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain( + 'cwl-collaboration-status', + ); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain( + "from '../src/components/Toolbar.js'", + ); + expect(inventory).toContain('class-level chrome sample'); + }); +}); diff --git a/src/designTokenFormatImmutability.test.ts b/src/designTokenFormatImmutability.test.ts new file mode 100644 index 00000000..348402dd --- /dev/null +++ b/src/designTokenFormatImmutability.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from 'vitest'; + +import { toDesignTokenFormatGroup } from './designTokens.js'; + +describe('design-token interchange snapshot immutability', () => { + it('freezes nested font-family values before returning them to hosts', () => { + const group = toDesignTokenFormatGroup(); + const fontValue = group.cwl.font.$value; + + expect(Array.isArray(fontValue)).toBe(true); + expect(Object.isFrozen(fontValue)).toBe(true); + expect(() => (fontValue as string[]).push('Host mutation')).toThrow(TypeError); + }); +}); diff --git a/src/designTokens.test.ts b/src/designTokens.test.ts new file mode 100644 index 00000000..3c856b47 --- /dev/null +++ b/src/designTokens.test.ts @@ -0,0 +1,180 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { + EditorThemeTokenContrastError, + EditorThemeTokenError, + contrastRatioFromHex, + getEditorThemeToken, + getEditorThemeTokenContrast, + listEditorThemeTokens, + toDesignTokenFormatGroup, +} from './designTokens.js'; + +const stylesheet = readFileSync(resolve(process.cwd(), 'src/styles.css'), 'utf8'); + +function mediaBlock(css: string, query: string): string { + const marker = `@media ${query}`; + const start = css.indexOf(marker); + expect(start).toBeGreaterThan(-1); + const open = css.indexOf('{', start); + let depth = 0; + for (let index = open; index < css.length; index += 1) { + const character = css[index]; + if (character === '{') { + depth += 1; + } + if (character === '}') { + depth -= 1; + if (depth === 0) { + return css.slice(open + 1, index); + } + } + } + throw new Error(`Unclosed media query: ${query}`); +} + +describe('editor theme token catalog', () => { + it('lists every shipped .cwl-editor custom property hosts can override', () => { + const tokens = listEditorThemeTokens(); + const names = tokens.map((token) => token.name); + + expect(names).toEqual([ + 'cwl-fg', + 'cwl-muted', + 'cwl-border', + 'cwl-bg', + 'cwl-surface', + 'cwl-accent', + 'cwl-accent-soft', + 'cwl-radius', + 'cwl-font', + ]); + + for (const token of tokens) { + expect(stylesheet).toContain(`${token.cssCustomProperty}:`); + expect(token.hostAction).toContain('Override'); + expect(token.hostAction).toContain('.cwl-editor'); + expect(token.hostAction).toContain(token.cssCustomProperty); + } + }); + + it('keeps light, dark, and print color values inside the matching stylesheet media blocks', () => { + const printBlock = mediaBlock(stylesheet, 'print'); + const darkBlock = mediaBlock(stylesheet, '(prefers-color-scheme: dark)'); + const forcedColorsBlock = mediaBlock(stylesheet, '(forced-colors: active)'); + const colorTokens = listEditorThemeTokens().filter((token) => token.role === 'color'); + + expect(forcedColorsBlock).toContain('outline-color: CanvasText'); + + for (const token of colorTokens) { + expect(stylesheet).toContain(`${token.cssCustomProperty}: ${token.lightValue};`); + expect(darkBlock).toContain(`${token.cssCustomProperty}: ${token.darkValue};`); + expect(printBlock).toContain(`${token.cssCustomProperty}: ${token.printValue};`); + expect(forcedColorsBlock).not.toContain(`${token.cssCustomProperty}:`); + } + }); + + it('rejects unknown token names without reflecting caller input', () => { + expect(() => getEditorThemeToken('not-a-theme-token')).toThrow(EditorThemeTokenError); + expect(() => getEditorThemeToken('not-a-theme-token')).toThrow( + /unknown editor theme token/iu, + ); + expect(() => getEditorThemeToken('not-a-theme-token')).not.toThrow(/not-a-theme-token/u); + }); + + it('reports WCAG 2.2 contrast for catalog color pairs so hosts can check overrides', () => { + const light = getEditorThemeTokenContrast('cwl-fg', 'cwl-bg', 'light'); + const dark = getEditorThemeTokenContrast('cwl-fg', 'cwl-bg', 'dark'); + const print = getEditorThemeTokenContrast('cwl-fg', 'cwl-bg', 'print'); + const muted = getEditorThemeTokenContrast('cwl-muted', 'cwl-bg', 'light'); + const lowContrast = getEditorThemeTokenContrast('cwl-border', 'cwl-bg', 'light'); + + expect(light.ratio).toBeCloseTo(15.797619425332647, 8); + expect(dark.ratio).toBeCloseTo(16.016082890827004, 8); + expect(print.ratio).toBeCloseTo(21, 8); + expect(muted.ratio).toBeCloseTo(6.114136455475549, 8); + expect(light.ratio).toBeGreaterThanOrEqual(4.5); + expect(light.meetsTextContrast).toBe(true); + expect(light.meetsNonTextContrast).toBe(true); + expect(light.hostAction).toContain('Override --cwl-fg and --cwl-bg on .cwl-editor'); + expect(light.hostAction).toContain('WCAG 2.2'); + expect(lowContrast.meetsTextContrast).toBe(false); + expect(lowContrast.hostAction).toContain('Catalog light text contrast is below 4.5:1'); + expect(lowContrast.hostAction).toContain('WCAG 2.2'); + expect(lowContrast.hostAction).toContain('Override --cwl-border and --cwl-bg on .cwl-editor'); + }); + + it('requires inventoried active-chrome text contrast to meet WCAG 2.2 AA', () => { + const lightActive = getEditorThemeTokenContrast('cwl-accent', 'cwl-accent-soft', 'light'); + const darkActive = getEditorThemeTokenContrast('cwl-accent', 'cwl-accent-soft', 'dark'); + const printActive = getEditorThemeTokenContrast('cwl-accent', 'cwl-accent-soft', 'print'); + const accentOnBackground = getEditorThemeTokenContrast('cwl-accent', 'cwl-bg', 'light'); + + expect(lightActive.ratio).toBeCloseTo(4.563748387142551, 8); + expect(lightActive.meetsTextContrast).toBe(true); + expect(darkActive.ratio).toBeCloseTo(5.062920561609967, 8); + expect(darkActive.meetsTextContrast).toBe(true); + expect(darkActive.meetsNonTextContrast).toBe(true); + expect(darkActive.hostAction).toContain('--cwl-accent'); + expect(darkActive.hostAction).toContain('--cwl-accent-soft'); + expect(darkActive.hostAction).toContain('contrastRatioFromHex'); + expect(darkActive.hostAction).not.toContain('below 4.5:1'); + expect(printActive.meetsTextContrast).toBe(true); + expect(accentOnBackground.meetsTextContrast).toBe(true); + }); + + it('points accent token host actions at the inventoried active pair', () => { + const accent = getEditorThemeToken('cwl-accent'); + const accentSoft = getEditorThemeToken('cwl-accent-soft'); + const foreground = getEditorThemeToken('cwl-fg'); + + expect(accent.hostAction).toContain('--cwl-accent-soft'); + expect(accentSoft.hostAction).toContain('--cwl-accent'); + expect(foreground.hostAction).toContain('--cwl-bg'); + }); + + it('rejects contrast lookups that are not shipped color tokens without reflecting caller input', () => { + expect(() => getEditorThemeTokenContrast('not-a-theme-token', 'cwl-bg')).toThrow( + EditorThemeTokenError, + ); + expect(() => getEditorThemeTokenContrast('not-a-theme-token', 'cwl-bg')).not.toThrow( + /not-a-theme-token/u, + ); + expect(() => getEditorThemeTokenContrast('cwl-font', 'cwl-bg')).toThrow( + EditorThemeTokenContrastError, + ); + expect(() => getEditorThemeTokenContrast('cwl-font', 'cwl-bg')).toThrow( + /theme token contrast requires color tokens/iu, + ); + expect(() => getEditorThemeTokenContrast('cwl-font', 'cwl-bg')).not.toThrow(/cwl-font/u); + expect(() => + getEditorThemeTokenContrast('cwl-fg', 'cwl-bg', 'solar' as 'light'), + ).toThrow(EditorThemeTokenContrastError); + expect(contrastRatioFromHex('#1f2328', '#ffffff')).toBeCloseTo(15.797619425332647, 8); + expect(contrastRatioFromHex('#000000', '#ffffff')).toBeCloseTo(21, 8); + expect(() => contrastRatioFromHex('not-a-hex', '#ffffff')).toThrow( + EditorThemeTokenContrastError, + ); + expect(() => contrastRatioFromHex('not-a-hex', '#ffffff')).not.toThrow(/not-a-hex/u); + }); + + it('emits DTCG 2025.10-native color and dimension values hosts can copy into a theme file', () => { + const group = toDesignTokenFormatGroup(); + const foreground = group.cwl.fg; + + expect(foreground.$type).toBe('color'); + expect(foreground.$value).toEqual({ + colorSpace: 'srgb', + components: [31 / 255, 35 / 255, 40 / 255], + hex: '#1f2328', + }); + expect(foreground.$description).toContain('Override --cwl-fg on .cwl-editor'); + expect(group.cwl.radius.$type).toBe('dimension'); + expect(group.cwl.radius.$value).toEqual({ value: 8, unit: 'px' }); + expect(group.cwl.font.$type).toBe('fontFamily'); + expect(Array.isArray(group.cwl.font.$value)).toBe(true); + }); +}); diff --git a/src/designTokens.ts b/src/designTokens.ts new file mode 100644 index 00000000..87ef2bf2 --- /dev/null +++ b/src/designTokens.ts @@ -0,0 +1,363 @@ +/** + * Host-facing catalog of the repeating editor chrome tokens shipped in + * `src/styles.css`. + * + * The stylesheet remains the runtime presentation authority. This module names + * those custom properties, maps them to Design Tokens Format Module 2025.10 + * types, and tells a host which property to override on `.cwl-editor`. + */ + +export type EditorThemeTokenRole = 'color' | 'dimension' | 'fontFamily'; + +export type EditorThemeTokenScheme = 'light' | 'dark' | 'print'; + +export type EditorThemeTokenName = + | 'cwl-fg' + | 'cwl-muted' + | 'cwl-border' + | 'cwl-bg' + | 'cwl-surface' + | 'cwl-accent' + | 'cwl-accent-soft' + | 'cwl-radius' + | 'cwl-font'; + +export interface EditorThemeToken { + readonly name: EditorThemeTokenName; + readonly cssCustomProperty: `--${EditorThemeTokenName}`; + readonly role: EditorThemeTokenRole; + readonly lightValue: string; + readonly darkValue?: string; + readonly printValue?: string; + readonly hostAction: string; +} + +export interface EditorThemeTokenContrast { + readonly foreground: EditorThemeTokenName; + readonly background: EditorThemeTokenName; + readonly scheme: EditorThemeTokenScheme; + readonly ratio: number; + readonly meetsTextContrast: boolean; + readonly meetsNonTextContrast: boolean; + readonly hostAction: string; +} + +/** WCAG 2.2 Success Criterion 1.4.3 minimum contrast for regular text. */ +export const WCAG_TEXT_CONTRAST_RATIO = 4.5; + +/** WCAG 2.2 Success Criterion 1.4.11 minimum contrast for UI components. */ +export const WCAG_NON_TEXT_CONTRAST_RATIO = 3; + +/** DTCG 2025.10 sRGB color value used by the interchange snapshot. */ +export interface DesignTokenFormatColorValue { + readonly colorSpace: 'srgb'; + readonly components: readonly [number, number, number]; + readonly hex: string; +} + +/** DTCG 2025.10 dimension value used by the interchange snapshot. */ +export interface DesignTokenFormatDimensionValue { + readonly value: number; + readonly unit: 'px' | 'rem'; +} + +export type DesignTokenFormatValue = + | DesignTokenFormatColorValue + | DesignTokenFormatDimensionValue + | string + | readonly string[]; + +export interface DesignTokenFormatNode { + readonly $type: 'color' | 'dimension' | 'fontFamily'; + readonly $value: DesignTokenFormatValue; + readonly $description: string; +} + +export interface DesignTokenFormatGroup { + readonly cwl: { + readonly fg: DesignTokenFormatNode; + readonly muted: DesignTokenFormatNode; + readonly border: DesignTokenFormatNode; + readonly bg: DesignTokenFormatNode; + readonly surface: DesignTokenFormatNode; + readonly accent: DesignTokenFormatNode; + readonly 'accent-soft': DesignTokenFormatNode; + readonly radius: DesignTokenFormatNode; + readonly font: DesignTokenFormatNode; + }; +} + +const ROLE_TO_FORMAT_TYPE = { + color: 'color', + dimension: 'dimension', + fontFamily: 'fontFamily', +} as const satisfies Record; + +/** Stable fail-closed error for unknown theme-token lookups. */ +export class EditorThemeTokenError extends Error { + readonly code = 'unknown_theme_token'; + + constructor() { + super('Unknown editor theme token.'); + this.name = 'EditorThemeTokenError'; + } +} + +/** Stable fail-closed error when contrast is requested for a non-color token. */ +export class EditorThemeTokenContrastError extends Error { + readonly code = 'non_color_theme_token'; + + constructor() { + super('Theme token contrast requires color tokens.'); + this.name = 'EditorThemeTokenContrastError'; + } +} + +function hostAction( + name: EditorThemeTokenName, + cssCustomProperty: `--${EditorThemeTokenName}`, +): string { + const actions = { + 'cwl-accent': `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 text contrast against --cwl-accent-soft (active toolbar) and --cwl-bg (links). Do not edit Inkspan internals.`, + 'cwl-accent-soft': `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 text contrast against --cwl-accent on active toolbar buttons. Do not edit Inkspan internals.`, + 'cwl-fg': `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 text contrast against --cwl-bg. Do not edit Inkspan internals.`, + 'cwl-muted': `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 text contrast against --cwl-bg. Do not edit Inkspan internals.`, + 'cwl-border': `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 non-text contrast against adjacent chrome. Do not edit Inkspan internals.`, + 'cwl-bg': `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 text contrast of --cwl-fg and --cwl-muted against this background. Do not edit Inkspan internals.`, + 'cwl-surface': `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 text contrast of --cwl-fg and --cwl-muted against this background. Do not edit Inkspan internals.`, + 'cwl-radius': `Override ${cssCustomProperty} on .cwl-editor. Do not edit Inkspan internals.`, + 'cwl-font': `Override ${cssCustomProperty} on .cwl-editor. Do not edit Inkspan internals.`, + } as const satisfies Record; + return actions[name]; +} + +function themeToken( + name: EditorThemeTokenName, + role: EditorThemeTokenRole, + lightValue: string, + darkValue?: string, + printValue?: string, +): EditorThemeToken { + const cssCustomProperty = `--${name}` as const; + return Object.freeze({ + name, + cssCustomProperty, + role, + lightValue, + darkValue, + printValue, + hostAction: hostAction(name, cssCustomProperty), + }); +} + +const EDITOR_THEME_TOKENS: readonly EditorThemeToken[] = Object.freeze([ + themeToken('cwl-fg', 'color', '#1f2328', '#e6edf3', '#000000'), + themeToken('cwl-muted', 'color', '#59636e', '#9198a1', '#444444'), + themeToken('cwl-border', 'color', '#d1d9e0', '#3d444d', '#999999'), + themeToken('cwl-bg', 'color', '#ffffff', '#0d1117', '#ffffff'), + themeToken('cwl-surface', 'color', '#f6f8fa', '#161b22', '#ffffff'), + themeToken('cwl-accent', 'color', '#0969da', '#58a6ff', '#000000'), + themeToken('cwl-accent-soft', 'color', '#ddf4ff', '#163356', '#ffffff'), + themeToken('cwl-radius', 'dimension', '8px'), + themeToken( + 'cwl-font', + 'fontFamily', + "'Noto Sans', 'Noto Sans KR', 'Noto Sans JP', 'Noto Sans SC', 'Noto Sans TC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif", + ), +]); + +const EDITOR_THEME_TOKEN_BY_NAME: ReadonlyMap = new Map( + EDITOR_THEME_TOKENS.map((token) => [token.name, token]), +); + +const TOKEN_GROUP_KEY: Record = { + 'cwl-fg': 'fg', + 'cwl-muted': 'muted', + 'cwl-border': 'border', + 'cwl-bg': 'bg', + 'cwl-surface': 'surface', + 'cwl-accent': 'accent', + 'cwl-accent-soft': 'accent-soft', + 'cwl-radius': 'radius', + 'cwl-font': 'font', +}; + +/** Return the frozen catalog of shipped editor theme tokens. */ +export function listEditorThemeTokens(): readonly EditorThemeToken[] { + return EDITOR_THEME_TOKENS; +} + +/** + * Return one shipped theme token by exact catalog name. + * + * @throws {EditorThemeTokenError} When the name is not a shipped token. + */ +export function getEditorThemeToken(name: string): EditorThemeToken { + const token = EDITOR_THEME_TOKEN_BY_NAME.get(name); + if (!token) { + throw new EditorThemeTokenError(); + } + return token; +} + +function srgbChannel(value: number): number { + const channel = value / 255; + return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4; +} + +function relativeLuminance(hex: string): number { + if (typeof hex !== 'string') { + throw new EditorThemeTokenContrastError(); + } + const match = /^#([0-9a-f]{6})$/iu.exec(hex); + if (!match) { + throw new EditorThemeTokenContrastError(); + } + const integer = Number.parseInt(match[1], 16); + return ( + 0.2126 * srgbChannel((integer >> 16) & 0xff) + + 0.7152 * srgbChannel((integer >> 8) & 0xff) + + 0.0722 * srgbChannel(integer & 0xff) + ); +} + +/** + * Return the WCAG 2.2 contrast ratio for two `#rrggbb` colors. + * + * Use this after a host override so the new hex pair can be checked without + * editing Inkspan internals. The ratio is not a host WCAG certification. + * + * @throws {EditorThemeTokenContrastError} When either value is not `#rrggbb`. + */ +export function contrastRatioFromHex(left: string, right: string): number { + const first = relativeLuminance(left); + const second = relativeLuminance(right); + const lighter = Math.max(first, second); + const darker = Math.min(first, second); + return (lighter + 0.05) / (darker + 0.05); +} + +function colorValueForScheme( + token: EditorThemeToken, + scheme: EditorThemeTokenScheme, +): string { + switch (scheme) { + case 'light': + return token.lightValue; + case 'dark': + return token.darkValue!; + case 'print': + return token.printValue!; + default: { + const exhaustive: never = scheme; + void exhaustive; + throw new EditorThemeTokenContrastError(); + } + } +} + +/** + * Return the WCAG 2.2 contrast ratio for two shipped catalog color tokens. + * + * This helper evaluates Inkspan's catalog values for the requested scheme; it + * cannot observe host CSS overrides. After overriding a pair on `.cwl-editor`, + * pass the resolved `#rrggbb` values to `contrastRatioFromHex()` instead. The + * ratio is not a host WCAG certification. + * + * @throws {EditorThemeTokenError} When either name is not a shipped token. + * @throws {EditorThemeTokenContrastError} When either token is not a color. + */ +export function getEditorThemeTokenContrast( + foregroundName: string, + backgroundName: string, + scheme: EditorThemeTokenScheme = 'light', +): EditorThemeTokenContrast { + const foreground = getEditorThemeToken(foregroundName); + const background = getEditorThemeToken(backgroundName); + if (foreground.role !== 'color' || background.role !== 'color') { + throw new EditorThemeTokenContrastError(); + } + const ratio = contrastRatioFromHex( + colorValueForScheme(foreground, scheme), + colorValueForScheme(background, scheme), + ); + const meetsTextContrast = ratio >= WCAG_TEXT_CONTRAST_RATIO; + const meetsNonTextContrast = ratio >= WCAG_NON_TEXT_CONTRAST_RATIO; + const pairNames = `--${foreground.name} and --${background.name}`; + const resolvedOverrideAction = + 'For host overrides, re-check WCAG 2.2 using the resolved #rrggbb pair with contrastRatioFromHex(actualForegroundHex, actualBackgroundHex) before shipping.'; + return Object.freeze({ + foreground: foreground.name, + background: background.name, + scheme, + ratio, + meetsTextContrast, + meetsNonTextContrast, + hostAction: meetsTextContrast + ? `Catalog ${scheme} contrast is ${ratio.toFixed(2)}:1. ${resolvedOverrideAction} Override ${pairNames} on .cwl-editor only; do not edit Inkspan internals.` + : `Catalog ${scheme} text contrast is below 4.5:1. Override ${pairNames} on .cwl-editor and ${resolvedOverrideAction} Do not edit Inkspan internals.`, + }); +} + +function formatColorTokenValue(value: string): DesignTokenFormatColorValue { + const integer = Number.parseInt(value.slice(1), 16); + return Object.freeze({ + colorSpace: 'srgb', + components: Object.freeze([ + ((integer >> 16) & 0xff) / 255, + ((integer >> 8) & 0xff) / 255, + (integer & 0xff) / 255, + ] as const), + hex: value, + }); +} + +function formatDimensionTokenValue(value: string): DesignTokenFormatDimensionValue { + const [, numericValue, unit] = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(px|rem)$/u.exec(value)!; + return Object.freeze({ + value: Number.parseFloat(numericValue), + unit: unit as DesignTokenFormatDimensionValue['unit'], + }); +} + +function formatTokenValue(token: EditorThemeToken): DesignTokenFormatValue { + switch (token.role) { + case 'color': + return formatColorTokenValue(token.lightValue); + case 'dimension': + return formatDimensionTokenValue(token.lightValue); + case 'fontFamily': + return Object.freeze( + token.lightValue + .split(',') + .map((family) => family.trim().replace(/^['"]|['"]$/gu, '')), + ); + } +} + +function toFormatNode(token: EditorThemeToken): DesignTokenFormatNode { + return Object.freeze({ + $type: ROLE_TO_FORMAT_TYPE[token.role], + $value: formatTokenValue(token), + $description: token.hostAction, + }); +} + +/** + * Return a Design Tokens Format Module 2025.10 group for the shipped chrome. + * + * The light/default snapshot emits DTCG-native color, dimension, and font-family + * value shapes. Dark and print CSS schemes remain available from the token + * catalog and stylesheet authority. This is not Figma Variables sync or a WCAG + * certification for host overrides. + */ +export function toDesignTokenFormatGroup(): DesignTokenFormatGroup { + const cwl = {} as DesignTokenFormatGroup['cwl']; + for (const token of EDITOR_THEME_TOKENS) { + Object.defineProperty(cwl, TOKEN_GROUP_KEY[token.name], { + value: toFormatNode(token), + enumerable: true, + }); + } + return Object.freeze({ cwl: Object.freeze(cwl) }); +} diff --git a/src/designTokensRuntimeInput.test.ts b/src/designTokensRuntimeInput.test.ts new file mode 100644 index 00000000..90abe635 --- /dev/null +++ b/src/designTokensRuntimeInput.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; + +import { + EditorThemeTokenContrastError, + contrastRatioFromHex, +} from './designTokens.js'; + +describe('theme contrast runtime input boundary', () => { + it('rejects non-string colors before caller-controlled coercion', () => { + const privateSentinel = new Error('private theme color coercion sentinel'); + let coercionCalls = 0; + const hostileColor = { + toString(): never { + coercionCalls += 1; + throw privateSentinel; + }, + }; + + let observed: unknown; + try { + contrastRatioFromHex(hostileColor as unknown as string, '#ffffff'); + } catch (error) { + observed = error; + } + + expect(coercionCalls).toBe(0); + expect(observed).toBeInstanceOf(EditorThemeTokenContrastError); + expect(observed).not.toBe(privateSentinel); + }); +}); diff --git a/src/docxHyperlinkDocumentationMaturity.test.ts b/src/docxHyperlinkDocumentationMaturity.test.ts new file mode 100644 index 00000000..ae73b15e --- /dev/null +++ b/src/docxHyperlinkDocumentationMaturity.test.ts @@ -0,0 +1,31 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +const repositoryFile = (path: string): string => + readFileSync(resolve(process.cwd(), path), 'utf8'); + +describe('protected DOCX hyperlink documentation maturity', () => { + it('keeps the technical requirements aligned with accepted protected ADR 0026', () => { + const trd = repositoryFile('docs/TRD.md'); + const adr = repositoryFile( + 'docs/adr/0026-bounded-docx-external-hyperlinks.md', + ); + + expect(adr).toContain('Status: Accepted'); + expect(adr).toContain('implemented on protected `main` through PR #137'); + expect(trd).toContain( + 'Protected `main` implements Accepted ADR 0026 through integrated PR #137', + ); + expect(trd).toContain( + 'bounded DOCX rich-run external hyperlinks, and the OIDC-backed unified stable registry release train are `implemented_on_protected_main`', + ); + expect(trd).not.toContain( + 'Active PR #137 implements the Proposed ADR 0026 extension', + ); + expect(trd).not.toContain( + 'The bounded DOCX rich-run external hyperlink contract in #137 is `implemented_on_active_pr` under Proposed ADR 0026', + ); + }); +}); diff --git a/src/editorFocusStyles.test.ts b/src/editorFocusStyles.test.ts new file mode 100644 index 00000000..5adf7944 --- /dev/null +++ b/src/editorFocusStyles.test.ts @@ -0,0 +1,27 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +const styles = readFileSync(resolve(process.cwd(), 'src/styles.css'), 'utf8'); + +describe('editable surface focus visibility', () => { + it('preserves a visible keyboard focus indicator on the textbox surface', () => { + expect(styles).not.toMatch( + /\.cwl-editor__content:focus\s*\{[^}]*outline:\s*none\s*;/u, + ); + expect(styles).toMatch( + /\.cwl-editor__content:focus-visible\s*\{[^}]*outline:\s*2px\s+solid\s+var\(--cwl-accent\)\s*;[^}]*outline-offset:\s*-?2px\s*;/u, + ); + }); + + it('keeps the editable focus indicator visible in forced-colors mode', () => { + const forcedColorsIndex = styles.indexOf('@media (forced-colors: active)'); + expect(forcedColorsIndex).toBeGreaterThan(-1); + const forcedColorsStyles = styles.slice(forcedColorsIndex); + + expect(forcedColorsStyles).toMatch( + /\.cwl-editor__content:focus-visible\s*\{[^}]*outline-color:\s*CanvasText\s*;/u, + ); + }); +}); diff --git a/src/index.test.ts b/src/index.test.ts index 506096ad..edc46671 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -49,6 +49,18 @@ describe('package entry point', () => { expect(typeof api.htmlToPlainText).toBe('function'); }); + it('re-exports the editor theme token catalog', () => { + expect(typeof api.listEditorThemeTokens).toBe('function'); + expect(typeof api.getEditorThemeToken).toBe('function'); + expect(typeof api.getEditorThemeTokenContrast).toBe('function'); + expect(typeof api.contrastRatioFromHex).toBe('function'); + expect(api.WCAG_TEXT_CONTRAST_RATIO).toBe(4.5); + expect(api.WCAG_NON_TEXT_CONTRAST_RATIO).toBe(3); + expect(typeof api.toDesignTokenFormatGroup).toBe('function'); + expect(typeof api.EditorThemeTokenError).toBe('function'); + expect(typeof api.EditorThemeTokenContrastError).toBe('function'); + }); + it('re-exports the standalone base64 converter', () => { expect(typeof api.bytesToBase64).toBe('function'); expect(typeof api.base64ToBytes).toBe('function'); diff --git a/src/index.ts b/src/index.ts index f7905449..ca2fabe4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -166,6 +166,28 @@ export { } from './markdown/plainText.js'; export type { PlainTextOptions } from './markdown/plainText.js'; +// Host-facing editor chrome theme tokens (CSS remains runtime authority). +export { + EditorThemeTokenContrastError, + EditorThemeTokenError, + WCAG_NON_TEXT_CONTRAST_RATIO, + WCAG_TEXT_CONTRAST_RATIO, + contrastRatioFromHex, + getEditorThemeToken, + getEditorThemeTokenContrast, + listEditorThemeTokens, + toDesignTokenFormatGroup, +} from './designTokens.js'; +export type { + DesignTokenFormatGroup, + DesignTokenFormatNode, + EditorThemeToken, + EditorThemeTokenContrast, + EditorThemeTokenName, + EditorThemeTokenRole, + EditorThemeTokenScheme, +} from './designTokens.js'; + // Standalone, framework-agnostic base64 / data-URI converter. export * from './converter/index.js'; export { diff --git a/src/releaseContractCanonicalConsistency.test.ts b/src/releaseContractCanonicalConsistency.test.ts new file mode 100644 index 00000000..e6ebb384 --- /dev/null +++ b/src/releaseContractCanonicalConsistency.test.ts @@ -0,0 +1,64 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +/** Read one authoritative repository file as UTF-8 text. */ +function repositoryFile(path: string): string { + return readFileSync(resolve(process.cwd(), path), 'utf8'); +} + +describe('canonical release artifact inventory consistency', () => { + it('keeps canonical release documents aligned with the protected four-file boundary', () => { + const changelog = repositoryFile('CHANGELOG.md'); + const contracts = repositoryFile('docs/CONTRACTS.md'); + const operability = repositoryFile('docs/OPERABILITY.md'); + const releaseDoctoring = repositoryFile( + 'docs/doctoring/release-draft-asset-inventory.md', + ); + const releaseSecurity = repositoryFile('docs/release-security.md'); + const testStrategy = repositoryFile('docs/TEST_STRATEGY.md'); + + expect(releaseSecurity).toContain( + 'Each successful GitHub release contains exactly four files', + ); + expect(releaseSecurity).toContain('`inkspan.spdx.json`'); + + expect(contracts).toContain('exactly four regular top-level files'); + expect(contracts).toContain('`inkspan.spdx.json`'); + expect(contracts).toMatch(/release evidence \| exact four-file draft inventory/u); + + expect(testStrategy).toContain('exact four-file inventory violations'); + expect(testStrategy).toContain( + 'exactly one npm tarball, exactly one Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`', + ); + + expect(operability).toContain('build exactly four regular top-level release files'); + expect(operability).toContain( + 'exactly one npm tarball, exactly one Inkspan Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`', + ); + + expect(releaseDoctoring).toContain( + 'exactly four top-level entries and require all four to be regular files', + ); + expect(releaseDoctoring).toContain('`inkspan.spdx.json`'); + + expect(changelog).toContain( + 'one npm tarball, one Office wheel, `inkspan.spdx.json`, and `SHA256SUMS`', + ); + + for (const document of [ + changelog, + contracts, + releaseSecurity, + testStrategy, + operability, + releaseDoctoring, + ]) { + expect(document).not.toContain('exactly three regular top-level files'); + expect(document).not.toContain('exact three-file draft inventory'); + expect(document).not.toContain('exactly three top-level entries'); + expect(document).not.toContain('one npm tarball, one Office wheel, and `SHA256SUMS`'); + } + }); +}); diff --git a/src/styles.css b/src/styles.css index 0970a931..0f650a8a 100644 --- a/src/styles.css +++ b/src/styles.css @@ -41,7 +41,7 @@ --cwl-border: #3d444d; --cwl-bg: #0d1117; --cwl-surface: #161b22; - --cwl-accent: #4493f8; + --cwl-accent: #58a6ff; --cwl-accent-soft: #163356; } } @@ -107,7 +107,8 @@ } @media (forced-colors: active) { - .cwl-tb-btn:focus-visible { + .cwl-tb-btn:focus-visible, + .cwl-editor__content:focus-visible { outline-color: CanvasText; } } @@ -125,8 +126,9 @@ font-size: 15px; } -.cwl-editor__content:focus { - outline: none; +.cwl-editor__content:focus-visible { + outline: 2px solid var(--cwl-accent); + outline-offset: -2px; } .cwl-editor__content > * + * { @@ -296,6 +298,10 @@ widows: 3; } + .cwl-editor__content:focus-visible { + outline: none; + } + .cwl-editor__content .is-editor-empty:first-child::before { content: none; } diff --git a/stories/EditorChrome.stories.tsx b/stories/EditorChrome.stories.tsx new file mode 100644 index 00000000..5f60c904 --- /dev/null +++ b/stories/EditorChrome.stories.tsx @@ -0,0 +1,118 @@ +import { useEffect, useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Editor } from '@tiptap/react'; + +import { Toolbar } from '../src/components/Toolbar.js'; +import { listEditorThemeTokens } from '../src/designTokens.js'; +import { buildExtensions } from '../src/extensions/kit.js'; +import '../src/styles.css'; + +function LiveToolbarPreview() { + const [editor, setEditor] = useState(null); + + useEffect(() => { + const instance = new Editor({ + extensions: buildExtensions({ image: { maxDimension: 0 } }), + content: + '

Override tokens on .cwl-editor after checking WCAG 2.2 contrast.

', + }); + setEditor(instance); + return () => { + instance.destroy(); + }; + }, []); + + if (!editor) { + return

Preparing the shipped toolbar.

; + } + + return ( +
+ +
+
+ This story mounts the shipped Toolbar. Override `--cwl-*` on + `.cwl-editor`. It does not mount CwlEditor. +
+
+
+ ); +} + +const meta = { + title: 'Editor Chrome', + parameters: { + layout: 'centered', + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const ToolbarButtonStates: Story = { + name: 'Toolbar Button States', + render: () => ( +
+
+
+ + + + +
+
+
Idle
+
+
+ Preview the same `.cwl-*` classes buyers ship, including + `:focus-visible`. Forced-colors restyles that outline to CanvasText + and does not assign `--cwl-*`. This is a class-level chrome sample, + not a mounted Toolbar or CwlEditor. +
+
+
+ ), +}; + +export const LiveToolbar: Story = { + name: 'Live Toolbar', + render: () => , +}; + +export const ThemeTokens: Story = { + name: 'Theme Tokens', + render: () => ( +
+

Override these tokens on .cwl-editor after checking WCAG 2.2 contrast.

+
    + {listEditorThemeTokens().map((token) => ( +
  • + {token.cssCustomProperty} + {token.role === 'color' ? ( +
  • + ))} +
+
+ ), +}; diff --git a/tests/browser/playwright.config.ts b/tests/browser/playwright.config.ts index 375056da..d7d4c2a2 100644 --- a/tests/browser/playwright.config.ts +++ b/tests/browser/playwright.config.ts @@ -2,7 +2,7 @@ import { defineConfig, devices } from '@playwright/test'; const HARNESS_ORIGIN = 'http://127.0.0.1:4173'; const HARNESS_URL = `${HARNESS_ORIGIN}/tests/browser/harness.html`; -const ENGINE_BROWSER_SPECS = /(?:clipboard|print)\.browser\.spec\.ts/u; +const ENGINE_BROWSER_SPECS = /(?:clipboard|focus|print)\.browser\.spec\.ts/u; export default defineConfig({ testDir: './specs', @@ -44,4 +44,4 @@ export default defineConfig({ dependencies: ['chromium', 'firefox', 'webkit'], }, ], -}); \ No newline at end of file +}); diff --git a/tests/browser/specs/focus.browser.spec.ts b/tests/browser/specs/focus.browser.spec.ts new file mode 100644 index 00000000..af7719d2 --- /dev/null +++ b/tests/browser/specs/focus.browser.spec.ts @@ -0,0 +1,82 @@ +import { expect, test } from '@playwright/test'; + +const HARNESS_URL = 'http://127.0.0.1:4173/tests/browser/harness.html'; +const STYLES_URL = 'http://127.0.0.1:4173/dist/cwl-editor.css'; + +const allowHarnessRequest = (requestUrl: string): boolean => { + const url = new URL(requestUrl); + return url.hostname === '127.0.0.1' && url.port === '4173'; +}; + +const mountEditableSurface = async (page: import('@playwright/test').Page) => { + const rejectedExternalRequests: string[] = []; + await page.route('**/*', async (route) => { + if (allowHarnessRequest(route.request().url())) { + await route.continue(); + return; + } + rejectedExternalRequests.push(new URL(route.request().url()).origin); + await route.abort('blockedbyclient'); + }); + + await page.goto(HARNESS_URL); + await page.addStyleTag({ url: STYLES_URL }); + await page.locator('#harness').evaluate((element) => { + element.innerHTML = ` +
+
+
Keyboard focus target
+
+
+ `; + }); + expect(rejectedExternalRequests).toEqual([]); + + await page.keyboard.press('Tab'); + const content = page.getByRole('textbox', { name: 'Document' }); + await expect(content).toBeFocused(); + return content; +}; + +test('shows a packed-stylesheet focus indicator on the editable textbox', async ({ + page, +}) => { + const content = await mountEditableSurface(page); + + const focusStyle = await content.evaluate((element) => { + const style = getComputedStyle(element); + return { + outlineStyle: style.outlineStyle, + outlineWidth: style.outlineWidth, + outlineOffset: style.outlineOffset, + }; + }); + + expect(focusStyle).toEqual({ + outlineStyle: 'solid', + outlineWidth: '2px', + outlineOffset: '-2px', + }); +}); + +test('does not print the interactive focus indicator', async ({ page }) => { + const content = await mountEditableSurface(page); + await page.emulateMedia({ media: 'print' }); + expect(await page.evaluate(() => matchMedia('print').matches)).toBe(true); + + const printOutlineStyle = await content.evaluate( + (element) => getComputedStyle(element).outlineStyle, + ); + + // CSS `outline: none` suppresses painting via outline-style. Engines may + // still report the initial computed outline-width (`medium`, typically 3px), + // which is inert while outline-style is `none` and is therefore not evidence + // of printed focus chrome. + expect(printOutlineStyle).toBe('none'); +});