From c9f99c8146ccc8388e15d22a24a553eb1aeea990 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 15:31:32 +0000 Subject: [PATCH 1/8] feat(ui): publish editor chrome tokens and Storybook inventory Give hosts a typed catalog of the repeating --cwl-* chrome tokens, a DTCG 2025.10 interchange snapshot, and Storybook stories for toolbar button states so brand theming happens on .cwl-editor after a WCAG 2.2 contrast check. Co-authored-by: Seongho Bae --- .gitignore | 3 + .storybook/main.ts | 9 + .storybook/preview.ts | 11 + ARCHITECTURE.md | 2 + CHANGELOG.md | 3 + README.md | 15 + docs/CONTRACTS.md | 1 + docs/DOCUMENTATION_FITNESS.md | 2 + docs/PRD.md | 2 + docs/README.md | 1 + docs/TRACEABILITY.md | 5 + .../0027-editor-design-tokens-storybook.md | 42 + docs/adr/README.md | 1 + docs/design-tokens.md | 38 + docs/doctoring/editor-design-tokens.md | 47 + docs/storybook-inventory.md | 12 + package.json | 6 + pnpm-lock.yaml | 1070 ++++++++++++++++- src/designTokenDocumentation.test.ts | 36 + src/designTokens.test.ts | 73 ++ src/designTokens.ts | 173 +++ src/index.test.ts | 7 + src/index.ts | 15 + stories/EditorChrome.stories.tsx | 62 + 24 files changed, 1633 insertions(+), 3 deletions(-) create mode 100644 .storybook/main.ts create mode 100644 .storybook/preview.ts create mode 100644 docs/adr/0027-editor-design-tokens-storybook.md create mode 100644 docs/design-tokens.md create mode 100644 docs/doctoring/editor-design-tokens.md create mode 100644 docs/storybook-inventory.md create mode 100644 src/designTokenDocumentation.test.ts create mode 100644 src/designTokens.test.ts create mode 100644 src/designTokens.ts create mode 100644 stories/EditorChrome.stories.tsx 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..33d83d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ 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. + ## [0.6.0] — 2026-08-10 ### Release diff --git a/README.md b/README.md index f2b02332..67d108fb 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,21 @@ 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. 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..8b79aea9 100644 --- a/docs/CONTRACTS.md +++ b/docs/CONTRACTS.md @@ -152,6 +152,7 @@ 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 | 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 | diff --git a/docs/DOCUMENTATION_FITNESS.md b/docs/DOCUMENTATION_FITNESS.md index cdb52e18..38d37131 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 0027, `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/PRD.md b/docs/PRD.md index 899086f3..62cbe523 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. 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/TRACEABILITY.md b/docs/TRACEABILITY.md index d120cec7..1c830e7c 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; Storybook React/Vite | Active-PR doctoring `docs/doctoring/editor-design-tokens.md`, operator guide, ADR 0027, 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/adr/0027-editor-design-tokens-storybook.md b/docs/adr/0027-editor-design-tokens-storybook.md new file mode 100644 index 00000000..00faaf76 --- /dev/null +++ b/docs/adr/0027-editor-design-tokens-storybook.md @@ -0,0 +1,42 @@ +# ADR 0027: 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/forced-colors values, and buyer next action lived only inside `src/styles.css`. There was no typed catalog, no Design Tokens Format Module interchange snapshot, and no Storybook inventory of the repeating objects. + +## Alternatives considered + +1. **Leave theming as undocumented CSS.** Rejected because hosts would keep reading internals and drift from the shipped 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. **Publish a named catalog plus Storybook inventory while CSS remains runtime authority.** Selected because hosts can override and preview repeating chrome without moving presentation authority or adding a design-tool runtime. + +## Decision + +Inkspan publishes a host-facing theme-token catalog for the nine shipped chrome tokens, a Design Tokens Format Module 2025.10 interchange snapshot, and a Storybook inventory of repeating toolbar/editor objects. Hosts override `--cwl-*` on `.cwl-editor` after checking WCAG 2.2 contrast. Unknown token names fail closed. No Figma, network, persistence, credential, or model authority is added. + +## Consequences and ownership trade-offs + +Hosts gain a copyable token list and a Storybook preview. Inkspan keeps CSS as the runtime source. naruon and other CWL hosts can apply the same overrides through host-owned brand CSS. Complete DTCG conformance, Figma Variables, and host WCAG certification remain out of scope. + +## Failure and recovery + +An unknown token name throws `EditorThemeTokenError` without reflecting caller input. A host override that fails contrast is recovered by changing only the named tokens, not by editing Inkspan internals or disabling forced-colors. + +## Security and privacy impact + +The 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. Existing CSS overrides on `.cwl-editor` continue to work. A later CSS token addition must update the catalog, Storybook inventory, and this ADR together. + +## Verification and acceptance evidence + +Required evidence includes token-catalog tests against `src/styles.css`, 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. This ADR stays Proposed until protected integration. + +## Rollback or supersession + +Rollback removes the catalog export, Storybook inventory/config, operator/doctoring records, and this ADR together. 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..9dcf2787 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 | +| [0027](0027-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..67a734b3 --- /dev/null +++ b/docs/design-tokens.md @@ -0,0 +1,38 @@ +# Editor chrome design tokens + +Status: Active PR / Proposed + +Use this catalog when you need to re-theme Inkspan's repeating toolbar and editor chrome. Override the named custom properties on `.cwl-editor` after checking WCAG 2.2 contrast. Do not edit Inkspan internals. + +```css +.cwl-editor { + --cwl-accent: #0b6e4f; + --cwl-accent-soft: #d8f3e8; +} +``` + +```ts +import { + listEditorThemeTokens, + toDesignTokenFormatGroup, +} from '@contextualwisdomlab/cwl-editor'; + +const tokens = listEditorThemeTokens(); +const dtcgGroup = toDesignTokenFormatGroup(); +void tokens; +void dtcgGroup; +``` + +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, Figma Variables sync, or a host WCAG certification. + +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..a4adfa73 --- /dev/null +++ b/docs/doctoring/editor-design-tokens.md @@ -0,0 +1,47 @@ +# 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 stylesheet already used `--cwl-*` custom properties, but buyers had no typed catalog, no interchange snapshot, and no Storybook inventory of the repeating toolbar button and editor chrome. Theme work therefore required reading CSS internals. + +If contrast fails after a re-theme, override only the named tokens on `.cwl-editor` and re-check WCAG 2.2 contrast against `--cwl-bg`. Do not disable forced-colors overrides. + +## Decision + +1. Keep `src/styles.css` as runtime presentation authority. +2. Publish `listEditorThemeTokens()` / `getEditorThemeToken()` / `toDesignTokenFormatGroup()` as a host-facing catalog of the nine shipped chrome tokens. +3. Reject unknown token names with a stable payload-redacted `EditorThemeTokenError`. +4. Preview repeating `.cwl-tb-btn` and token swatches in Storybook. +5. 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). Inkspan emits a snapshot of its 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 sufficient contrast for text and user-interface components (World Wide Web Consortium, 2024). Host overrides remain the host's contrast responsibility. Storybook's React/Vite preview is the inventory surface for repeating chrome (Storybook, n.d.). + +## Test-first evidence + +- RED: `src/designTokens.test.ts` failed because `./designTokens.js` did not exist. +- GREEN: the catalog lists the nine shipped tokens, aligns light/dark/forced-colors color values with `src/styles.css`, rejects unknown names without reflecting caller input, and emits a DTCG 2025.10 group. + +## Residual risk + +Forced-colors and print media still apply after a host override. 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 must remove the catalog export, this record, the operator guide, the Storybook inventory/config/stories, ADR 0027, the changelog entry, and the documentation-index rows together. + +## 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/storybook-inventory.md b/docs/storybook-inventory.md new file mode 100644 index 00000000..f2c4f71b --- /dev/null +++ b/docs/storybook-inventory.md @@ -0,0 +1,12 @@ +# Storybook inventory + +Status: Active PR / Proposed + +Run `pnpm storybook` to preview repeating Inkspan chrome before you override tokens in a host. Each story uses the shipped `.cwl-*` classes so a token change is visible on the same objects buyers see. + +| Story | Repeating object | Host next action | +| --- | --- | --- | +| `Editor Chrome / Toolbar Button States` | `.cwl-tb-btn` default and `is-active` | 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..235daca9 --- /dev/null +++ b/src/designTokenDocumentation.test.ts @@ -0,0 +1,36 @@ +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'); + +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 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'); + + expect(index).toContain('[`design-tokens.md`](design-tokens.md)'); + expect(adrIndex).toContain('[0027](0027-editor-design-tokens-storybook.md) | Proposed'); + expect(operatorGuide).toContain('Override the named custom properties on `.cwl-editor`'); + expect(operatorGuide).toContain('Do not edit Inkspan internals'); + expect(inventory).toContain('Editor Chrome / Toolbar Button States'); + expect(inventory).toContain('Override `--cwl-accent`'); + 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(changelog).toContain('Named the repeating editor chrome as a host-facing theme-token catalog'); + expect(repositoryFile('.storybook/main.ts')).toContain("@storybook/react-vite"); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain('cwl-tb-btn'); + }); +}); diff --git a/src/designTokens.test.ts b/src/designTokens.test.ts new file mode 100644 index 00000000..0c707803 --- /dev/null +++ b/src/designTokens.test.ts @@ -0,0 +1,73 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { + EditorThemeTokenError, + getEditorThemeToken, + listEditorThemeTokens, + toDesignTokenFormatGroup, +} from './designTokens.js'; + +const stylesheet = readFileSync(resolve(process.cwd(), 'src/styles.css'), 'utf8'); + +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 forced-colors color values aligned with the stylesheet', () => { + const foreground = getEditorThemeToken('cwl-fg'); + + expect(foreground.role).toBe('color'); + expect(foreground.lightValue).toBe('#1f2328'); + expect(foreground.darkValue).toBe('#e6edf3'); + expect(foreground.forcedColorsValue).toBe('#000000'); + expect(stylesheet).toContain(`${foreground.cssCustomProperty}: ${foreground.lightValue};`); + expect(stylesheet).toContain(`${foreground.cssCustomProperty}: ${foreground.darkValue};`); + expect(stylesheet).toContain( + `${foreground.cssCustomProperty}: ${foreground.forcedColorsValue};`, + ); + }); + + 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('emits a DTCG 2025.10 group that hosts can copy into a theme file', () => { + const group = toDesignTokenFormatGroup(); + const foreground = group.cwl.fg; + + expect(foreground.$type).toBe('color'); + expect(foreground.$value).toBe('#1f2328'); + expect(foreground.$description).toContain('Override --cwl-fg on .cwl-editor'); + expect(group.cwl.radius.$type).toBe('dimension'); + 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..99d7a873 --- /dev/null +++ b/src/designTokens.ts @@ -0,0 +1,173 @@ +/** + * 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 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 forcedColorsValue?: string; + readonly hostAction: string; +} + +export interface DesignTokenFormatNode { + readonly $type: 'color' | 'dimension' | 'fontFamily'; + readonly $value: string | readonly string[]; + 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'; + } +} + +function hostAction(cssCustomProperty: `--${EditorThemeTokenName}`): string { + return `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 contrast against --cwl-bg. Do not edit Inkspan internals.`; +} + +function themeToken( + name: EditorThemeTokenName, + role: EditorThemeTokenRole, + lightValue: string, + darkValue?: string, + forcedColorsValue?: string, +): EditorThemeToken { + const cssCustomProperty = `--${name}` as const; + return Object.freeze({ + name, + cssCustomProperty, + role, + lightValue, + darkValue, + forcedColorsValue, + hostAction: hostAction(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', '#4493f8', '#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 formatTokenValue(token: EditorThemeToken): string | readonly string[] { + if (token.role !== 'fontFamily') { + return token.lightValue; + } + return 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. + * + * This is an interchange snapshot of Inkspan's CSS custom properties. It is not + * a claim of complete DTCG conformance, Figma Variables sync, or WCAG contrast + * 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/index.test.ts b/src/index.test.ts index 506096ad..28b51fa6 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -49,6 +49,13 @@ 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.toDesignTokenFormatGroup).toBe('function'); + expect(typeof api.EditorThemeTokenError).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..e0bb5bc1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -166,6 +166,21 @@ export { } from './markdown/plainText.js'; export type { PlainTextOptions } from './markdown/plainText.js'; +// Host-facing editor chrome theme tokens (CSS remains runtime authority). +export { + EditorThemeTokenError, + getEditorThemeToken, + listEditorThemeTokens, + toDesignTokenFormatGroup, +} from './designTokens.js'; +export type { + DesignTokenFormatGroup, + DesignTokenFormatNode, + EditorThemeToken, + EditorThemeTokenName, + EditorThemeTokenRole, +} from './designTokens.js'; + // Standalone, framework-agnostic base64 / data-URI converter. export * from './converter/index.js'; export { diff --git a/stories/EditorChrome.stories.tsx b/stories/EditorChrome.stories.tsx new file mode 100644 index 00000000..1ed63663 --- /dev/null +++ b/stories/EditorChrome.stories.tsx @@ -0,0 +1,62 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { listEditorThemeTokens } from '../src/designTokens.js'; +import '../src/styles.css'; + +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: () => ( +
+
+
+ + +
+
+
+ ), +}; + +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' ? ( +
  • + ))} +
+
+ ), +}; From df748103c087ef703dc7c633e9c3bace77d6b5b4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 00:51:38 +0900 Subject: [PATCH 2/8] fix(docs): renumber design-token ADR to 0031 --- .../0031-editor-design-tokens-storybook.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/adr/0031-editor-design-tokens-storybook.md 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..4ea51cf8 --- /dev/null +++ b/docs/adr/0031-editor-design-tokens-storybook.md @@ -0,0 +1,42 @@ +# 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/forced-colors values, and buyer next action lived only inside `src/styles.css`. There was no typed catalog, no Design Tokens Format Module interchange snapshot, and no Storybook inventory of the repeating objects. + +## Alternatives considered + +1. **Leave theming as undocumented CSS.** Rejected because hosts would keep reading internals and drift from the shipped 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. **Publish a named catalog plus Storybook inventory while CSS remains runtime authority.** Selected because hosts can override and preview repeating chrome without moving presentation authority or adding a design-tool runtime. + +## Decision + +Inkspan publishes a host-facing theme-token catalog for the nine shipped chrome tokens, a Design Tokens Format Module 2025.10 interchange snapshot, and a Storybook inventory of repeating toolbar/editor objects. Hosts override `--cwl-*` on `.cwl-editor` after checking WCAG 2.2 contrast. Unknown token names fail closed. No Figma, network, persistence, credential, or model authority is added. + +## Consequences and ownership trade-offs + +Hosts gain a copyable token list and a Storybook preview. Inkspan keeps CSS as the runtime source. naruon and other CWL hosts can apply the same overrides through host-owned brand CSS. Complete DTCG conformance, Figma Variables, and host WCAG certification remain out of scope. + +## Failure and recovery + +An unknown token name throws `EditorThemeTokenError` without reflecting caller input. A host override that fails contrast is recovered by changing only the named tokens, not by editing Inkspan internals or disabling forced-colors. + +## Security and privacy impact + +The 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. Existing CSS overrides on `.cwl-editor` continue to work. A later CSS token addition must update the catalog, Storybook inventory, and this ADR together. + +## Verification and acceptance evidence + +Required evidence includes token-catalog tests against `src/styles.css`, 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. This ADR stays Proposed until protected integration. + +## Rollback or supersession + +Rollback removes the catalog export, Storybook inventory/config, operator/doctoring records, and this ADR together. Supersession requires a new ADR if CSS ceases to be runtime presentation authority or if a design-tool sync contract is accepted. From 720befffb540470890bde6bfddc6d55702ff64b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 00:52:03 +0900 Subject: [PATCH 3/8] fix(docs): index design-token ADR as 0031 --- docs/adr/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/README.md b/docs/adr/README.md index 9dcf2787..b797812e 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -32,7 +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 | -| [0027](0027-editor-design-tokens-storybook.md) | Proposed | Editor chrome design tokens and Storybook inventory | +| [0031](0031-editor-design-tokens-storybook.md) | Proposed | Editor chrome design tokens and Storybook inventory | ## Decision discipline From 89dd568894dd58355df0ebd4a657139243e21711 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 00:52:22 +0900 Subject: [PATCH 4/8] test(docs): bind design-token contract to ADR 0031 --- src/designTokenDocumentation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/designTokenDocumentation.test.ts b/src/designTokenDocumentation.test.ts index 235daca9..93fdb9a9 100644 --- a/src/designTokenDocumentation.test.ts +++ b/src/designTokenDocumentation.test.ts @@ -18,7 +18,7 @@ describe('editor design-token documentation contract', () => { const adrIndex = repositoryFile('docs/adr/README.md'); expect(index).toContain('[`design-tokens.md`](design-tokens.md)'); - expect(adrIndex).toContain('[0027](0027-editor-design-tokens-storybook.md) | Proposed'); + expect(adrIndex).toContain('[0031](0031-editor-design-tokens-storybook.md) | Proposed'); expect(operatorGuide).toContain('Override the named custom properties on `.cwl-editor`'); expect(operatorGuide).toContain('Do not edit Inkspan internals'); expect(inventory).toContain('Editor Chrome / Toolbar Button States'); From 89dcc2a40d0181d1eddb111f7f23a77408fa5a08 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 00:53:40 +0900 Subject: [PATCH 5/8] fix(docs): align design-token fitness with ADR 0031 --- docs/DOCUMENTATION_FITNESS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DOCUMENTATION_FITNESS.md b/docs/DOCUMENTATION_FITNESS.md index 38d37131..46d9737f 100644 --- a/docs/DOCUMENTATION_FITNESS.md +++ b/docs/DOCUMENTATION_FITNESS.md @@ -56,7 +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 0027, `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. | +| 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. | From d99cf3a18b31a0bbea3d0b18586f1c47e890a9ef Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 16:06:45 +0000 Subject: [PATCH 6/8] fix(ui): retire colliding token ADR 0027 and name print values Delete the leftover 0027 design-token ADR so this lane no longer collides with #141, #248, and #320. Catalog color remaps are print-media values, not forced-colors assignments, and Storybook now shows disabled, surface, and collaboration-status chrome as a class-level sample. Co-authored-by: Seongho Bae --- CHANGELOG.md | 2 +- docs/TRACEABILITY.md | 2 +- docs/TRD.md | 2 + docs/UML.md | 2 + .../0027-editor-design-tokens-storybook.md | 42 ----------------- .../0031-editor-design-tokens-storybook.md | 2 +- docs/doctoring/editor-design-tokens.md | 6 +-- docs/storybook-inventory.md | 4 +- src/designTokenDocumentation.test.ts | 22 ++++++++- src/designTokens.test.ts | 45 ++++++++++++++----- src/designTokens.ts | 6 +-- stories/EditorChrome.stories.tsx | 10 +++++ 12 files changed, 79 insertions(+), 66 deletions(-) delete mode 100644 docs/adr/0027-editor-design-tokens-storybook.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 33d83d26..943ccc4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ 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. +- 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. ## [0.6.0] — 2026-08-10 diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 1c830e7c..7f05cd22 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -21,7 +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; Storybook React/Vite | Active-PR doctoring `docs/doctoring/editor-design-tokens.md`, operator guide, ADR 0027, 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 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; 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 | diff --git a/docs/TRD.md b/docs/TRD.md index 80cec07a..460b9a2c 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -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. 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. diff --git a/docs/UML.md b/docs/UML.md index 5b67b163..4cf90c62 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. CSS remains runtime presentation authority. Storybook previews class-level chrome samples, not a mounted `Toolbar` or `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/0027-editor-design-tokens-storybook.md b/docs/adr/0027-editor-design-tokens-storybook.md deleted file mode 100644 index 00faaf76..00000000 --- a/docs/adr/0027-editor-design-tokens-storybook.md +++ /dev/null @@ -1,42 +0,0 @@ -# ADR 0027: 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/forced-colors values, and buyer next action lived only inside `src/styles.css`. There was no typed catalog, no Design Tokens Format Module interchange snapshot, and no Storybook inventory of the repeating objects. - -## Alternatives considered - -1. **Leave theming as undocumented CSS.** Rejected because hosts would keep reading internals and drift from the shipped 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. **Publish a named catalog plus Storybook inventory while CSS remains runtime authority.** Selected because hosts can override and preview repeating chrome without moving presentation authority or adding a design-tool runtime. - -## Decision - -Inkspan publishes a host-facing theme-token catalog for the nine shipped chrome tokens, a Design Tokens Format Module 2025.10 interchange snapshot, and a Storybook inventory of repeating toolbar/editor objects. Hosts override `--cwl-*` on `.cwl-editor` after checking WCAG 2.2 contrast. Unknown token names fail closed. No Figma, network, persistence, credential, or model authority is added. - -## Consequences and ownership trade-offs - -Hosts gain a copyable token list and a Storybook preview. Inkspan keeps CSS as the runtime source. naruon and other CWL hosts can apply the same overrides through host-owned brand CSS. Complete DTCG conformance, Figma Variables, and host WCAG certification remain out of scope. - -## Failure and recovery - -An unknown token name throws `EditorThemeTokenError` without reflecting caller input. A host override that fails contrast is recovered by changing only the named tokens, not by editing Inkspan internals or disabling forced-colors. - -## Security and privacy impact - -The 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. Existing CSS overrides on `.cwl-editor` continue to work. A later CSS token addition must update the catalog, Storybook inventory, and this ADR together. - -## Verification and acceptance evidence - -Required evidence includes token-catalog tests against `src/styles.css`, 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. This ADR stays Proposed until protected integration. - -## Rollback or supersession - -Rollback removes the catalog export, Storybook inventory/config, operator/doctoring records, and this ADR together. 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/0031-editor-design-tokens-storybook.md b/docs/adr/0031-editor-design-tokens-storybook.md index 4ea51cf8..4790519a 100644 --- a/docs/adr/0031-editor-design-tokens-storybook.md +++ b/docs/adr/0031-editor-design-tokens-storybook.md @@ -4,7 +4,7 @@ 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/forced-colors values, and buyer next action lived only inside `src/styles.css`. There was no typed catalog, no Design Tokens Format Module interchange snapshot, and no Storybook inventory of the repeating objects. +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 lived only inside `src/styles.css`. There was no typed catalog, no Design Tokens Format Module interchange snapshot, and no Storybook inventory of the repeating objects. ## Alternatives considered diff --git a/docs/doctoring/editor-design-tokens.md b/docs/doctoring/editor-design-tokens.md index a4adfa73..01ed21cd 100644 --- a/docs/doctoring/editor-design-tokens.md +++ b/docs/doctoring/editor-design-tokens.md @@ -28,15 +28,15 @@ WCAG 2.2 requires sufficient contrast for text and user-interface components (Wo ## Test-first evidence - RED: `src/designTokens.test.ts` failed because `./designTokens.js` did not exist. -- GREEN: the catalog lists the nine shipped tokens, aligns light/dark/forced-colors color values with `src/styles.css`, rejects unknown names without reflecting caller input, and emits a DTCG 2025.10 group. +- GREEN: the catalog lists the nine shipped tokens, aligns light/dark/print color values with the matching `src/styles.css` media blocks, rejects unknown names without reflecting caller input, and emits a DTCG 2025.10 group. ## Residual risk -Forced-colors and print media still apply after a host override. The font token snapshot splits a CSS font-family list and does not execute CSS. Storybook success is not Chromium/Firefox/WebKit release evidence. +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 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 must remove the catalog export, this record, the operator guide, the Storybook inventory/config/stories, ADR 0027, the changelog entry, and the documentation-index rows together. +Rollback must remove the catalog export, this record, the operator guide, the Storybook inventory/config/stories, ADR 0031, the changelog entry, and the documentation-index rows together. ## References (APA 7th edition) diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index f2c4f71b..277ffdde 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -2,11 +2,11 @@ Status: Active PR / Proposed -Run `pnpm storybook` to preview repeating Inkspan chrome before you override tokens in a host. Each story uses the shipped `.cwl-*` classes so a token change is visible on the same objects buyers see. +Run `pnpm storybook` to preview repeating Inkspan chrome before you override tokens in a host. Each story uses the shipped `.cwl-*` classes so a token change is visible on the same class-level chrome sample buyers style. These stories do not mount `Toolbar` or `CwlEditor`. | Story | Repeating object | Host next action | | --- | --- | --- | -| `Editor Chrome / Toolbar Button States` | `.cwl-tb-btn` default and `is-active` | Override `--cwl-accent` and `--cwl-accent-soft` on `.cwl-editor` after checking WCAG 2.2 contrast | +| `Editor Chrome / Toolbar Button States` | `.cwl-tb-btn` default, `is-active`, and disabled, plus `.cwl-collaboration-status` and `.cwl-editor__surface` | 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/src/designTokenDocumentation.test.ts b/src/designTokenDocumentation.test.ts index 93fdb9a9..fdfe0c81 100644 --- a/src/designTokenDocumentation.test.ts +++ b/src/designTokenDocumentation.test.ts @@ -1,10 +1,12 @@ -import { readFileSync } from 'node:fs'; +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(resolve(process.cwd(), path), 'utf8'); + readFileSync(repositoryPath(path), 'utf8'); const normalize = (value: string): string => value.replace(/\s+/gu, ' ').trim(); @@ -19,6 +21,16 @@ describe('editor design-token documentation contract', () => { 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('Override the named custom properties on `.cwl-editor`'); expect(operatorGuide).toContain('Do not edit Inkspan internals'); expect(inventory).toContain('Editor Chrome / Toolbar Button States'); @@ -32,5 +44,11 @@ describe('editor design-token documentation contract', () => { expect(changelog).toContain('Named the repeating editor chrome as a host-facing theme-token catalog'); 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('disabled'); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain('cwl-editor__surface'); + expect(repositoryFile('stories/EditorChrome.stories.tsx')).toContain( + 'cwl-collaboration-status', + ); + expect(inventory).toContain('class-level chrome sample'); }); }); diff --git a/src/designTokens.test.ts b/src/designTokens.test.ts index 0c707803..80a18524 100644 --- a/src/designTokens.test.ts +++ b/src/designTokens.test.ts @@ -12,6 +12,27 @@ import { 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(); @@ -37,18 +58,20 @@ describe('editor theme token catalog', () => { } }); - it('keeps light, dark, and forced-colors color values aligned with the stylesheet', () => { - const foreground = getEditorThemeToken('cwl-fg'); + 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(foreground.role).toBe('color'); - expect(foreground.lightValue).toBe('#1f2328'); - expect(foreground.darkValue).toBe('#e6edf3'); - expect(foreground.forcedColorsValue).toBe('#000000'); - expect(stylesheet).toContain(`${foreground.cssCustomProperty}: ${foreground.lightValue};`); - expect(stylesheet).toContain(`${foreground.cssCustomProperty}: ${foreground.darkValue};`); - expect(stylesheet).toContain( - `${foreground.cssCustomProperty}: ${foreground.forcedColorsValue};`, - ); + 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', () => { diff --git a/src/designTokens.ts b/src/designTokens.ts index 99d7a873..c12241bd 100644 --- a/src/designTokens.ts +++ b/src/designTokens.ts @@ -26,7 +26,7 @@ export interface EditorThemeToken { readonly role: EditorThemeTokenRole; readonly lightValue: string; readonly darkValue?: string; - readonly forcedColorsValue?: string; + readonly printValue?: string; readonly hostAction: string; } @@ -75,7 +75,7 @@ function themeToken( role: EditorThemeTokenRole, lightValue: string, darkValue?: string, - forcedColorsValue?: string, + printValue?: string, ): EditorThemeToken { const cssCustomProperty = `--${name}` as const; return Object.freeze({ @@ -84,7 +84,7 @@ function themeToken( role, lightValue, darkValue, - forcedColorsValue, + printValue, hostAction: hostAction(cssCustomProperty), }); } diff --git a/stories/EditorChrome.stories.tsx b/stories/EditorChrome.stories.tsx index 1ed63663..c64f5757 100644 --- a/stories/EditorChrome.stories.tsx +++ b/stories/EditorChrome.stories.tsx @@ -26,6 +26,16 @@ export const ToolbarButtonStates: Story = { + + + +
Idle
+
+
+ Preview the same `.cwl-*` classes buyers ship. This is a class-level + chrome sample, not a mounted Toolbar or CwlEditor.
From 7f734e5ff352b94c2d24044c341daeeb4d4f3d67 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 16:09:03 +0000 Subject: [PATCH 7/8] feat(ui): mount shipped Toolbar in Storybook token inventory Add a Live Toolbar story that constructs the real TipTap toolbar so hosts can preview token overrides on the same buttons buyers ship, while the class-level sample stays available and CwlEditor stays unmounted. Co-authored-by: Seongho Bae --- docs/UML.md | 2 +- docs/doctoring/editor-design-tokens.md | 2 +- docs/storybook-inventory.md | 3 +- src/designTokenDocumentation.test.ts | 5 ++++ stories/EditorChrome.stories.tsx | 41 ++++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/docs/UML.md b/docs/UML.md index 4cf90c62..566fbbe6 100644 --- a/docs/UML.md +++ b/docs/UML.md @@ -38,7 +38,7 @@ 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. CSS remains runtime presentation authority. Storybook previews class-level chrome samples, not a mounted `Toolbar` or `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. +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. CSS remains runtime presentation authority. Storybook previews a class-level chrome sample 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 diff --git a/docs/doctoring/editor-design-tokens.md b/docs/doctoring/editor-design-tokens.md index 01ed21cd..c9e71854 100644 --- a/docs/doctoring/editor-design-tokens.md +++ b/docs/doctoring/editor-design-tokens.md @@ -16,7 +16,7 @@ If contrast fails after a re-theme, override only the named tokens on `.cwl-edit 1. Keep `src/styles.css` as runtime presentation authority. 2. Publish `listEditorThemeTokens()` / `getEditorThemeToken()` / `toDesignTokenFormatGroup()` as a host-facing catalog of the nine shipped chrome tokens. 3. Reject unknown token names with a stable payload-redacted `EditorThemeTokenError`. -4. Preview repeating `.cwl-tb-btn` and token swatches in Storybook. +4. Preview repeating `.cwl-tb-btn` states, the shipped Toolbar, and token swatches in Storybook. 5. Add no network, persistence, credential, model, tenant, Figma, or design-tool sync authority. ## Standards rationale diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index 277ffdde..8833303d 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -2,11 +2,12 @@ Status: Active PR / Proposed -Run `pnpm storybook` to preview repeating Inkspan chrome before you override tokens in a host. Each story uses the shipped `.cwl-*` classes so a token change is visible on the same class-level chrome sample buyers style. These stories do not mount `Toolbar` or `CwlEditor`. +Run `pnpm storybook` to preview repeating Inkspan chrome before you override tokens in a host. The button-state story is a class-level chrome sample. **Editor Chrome / Live Toolbar** mounts the shipped Toolbar so a token change is visible on the same objects buyers ship. These stories do not mount `CwlEditor`. | Story | Repeating object | Host next action | | --- | --- | --- | | `Editor Chrome / Toolbar Button States` | `.cwl-tb-btn` default, `is-active`, and disabled, plus `.cwl-collaboration-status` and `.cwl-editor__surface` | Override `--cwl-accent` and `--cwl-accent-soft` on `.cwl-editor` after checking WCAG 2.2 contrast | +| `Editor Chrome / Live Toolbar` | shipped Toolbar buttons, groups, and disabled/active states | 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/src/designTokenDocumentation.test.ts b/src/designTokenDocumentation.test.ts index fdfe0c81..a11a1610 100644 --- a/src/designTokenDocumentation.test.ts +++ b/src/designTokenDocumentation.test.ts @@ -34,6 +34,8 @@ describe('editor design-token documentation contract', () => { expect(operatorGuide).toContain('Override the named custom properties on `.cwl-editor`'); expect(operatorGuide).toContain('Do not edit Inkspan internals'); expect(inventory).toContain('Editor Chrome / Toolbar Button States'); + expect(inventory).toContain('Editor Chrome / Live Toolbar'); + expect(inventory).toContain('shipped Toolbar'); expect(inventory).toContain('Override `--cwl-accent`'); expect(doctoring).toContain('Design Tokens Format Module 2025.10'); expect(doctoring).toContain( @@ -49,6 +51,9 @@ describe('editor design-token documentation contract', () => { 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/stories/EditorChrome.stories.tsx b/stories/EditorChrome.stories.tsx index c64f5757..48a44a75 100644 --- a/stories/EditorChrome.stories.tsx +++ b/stories/EditorChrome.stories.tsx @@ -1,8 +1,44 @@ +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: { @@ -42,6 +78,11 @@ export const ToolbarButtonStates: Story = { ), }; +export const LiveToolbar: Story = { + name: 'Live Toolbar', + render: () => , +}; + export const ThemeTokens: Story = { name: 'Theme Tokens', render: () => ( From c9615d97cb66f432e0a3f2becf6f2d84e6088941 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 16:11:00 +0000 Subject: [PATCH 8/8] feat(ui): publish WCAG contrast checks for editor chrome tokens Hosts can compare shipped color pairs and their own #rrggbb overrides against the WCAG 2.2 4.5:1 text threshold without reading CSS internals or treating the ratio as a certification. Co-authored-by: Seongho Bae --- CHANGELOG.md | 2 +- docs/design-tokens.md | 7 ++ docs/doctoring/editor-design-tokens.md | 4 +- src/designTokenDocumentation.test.ts | 2 + src/designTokens.test.ts | 43 ++++++++++ src/designTokens.ts | 104 +++++++++++++++++++++++++ src/index.test.ts | 3 + src/index.ts | 5 ++ 8 files changed, 167 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 943ccc4e..5d37ffa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ 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. +- 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 shipped color pairs against the 4.5:1 text threshold. ## [0.6.0] — 2026-08-10 diff --git a/docs/design-tokens.md b/docs/design-tokens.md index 67a734b3..b7c78e9f 100644 --- a/docs/design-tokens.md +++ b/docs/design-tokens.md @@ -13,12 +13,19 @@ Use this catalog when you need to re-theme Inkspan's repeating toolbar and edito ```ts import { + contrastRatioFromHex, + getEditorThemeTokenContrast, listEditorThemeTokens, toDesignTokenFormatGroup, } from '@contextualwisdomlab/cwl-editor'; const tokens = listEditorThemeTokens(); const dtcgGroup = toDesignTokenFormatGroup(); +const contrast = getEditorThemeTokenContrast('cwl-fg', 'cwl-bg', 'light'); +const overrideRatio = contrastRatioFromHex('#0b6e4f', '#ffffff'); +if (contrast.ratio < 4.5 || overrideRatio < 4.5) { + throw new Error(contrast.hostAction); +} void tokens; void dtcgGroup; ``` diff --git a/docs/doctoring/editor-design-tokens.md b/docs/doctoring/editor-design-tokens.md index c9e71854..434f278d 100644 --- a/docs/doctoring/editor-design-tokens.md +++ b/docs/doctoring/editor-design-tokens.md @@ -14,7 +14,7 @@ If contrast fails after a re-theme, override only the named tokens on `.cwl-edit ## Decision 1. Keep `src/styles.css` as runtime presentation authority. -2. Publish `listEditorThemeTokens()` / `getEditorThemeToken()` / `toDesignTokenFormatGroup()` as a host-facing catalog of the nine shipped chrome tokens. +2. Publish `listEditorThemeTokens()` / `getEditorThemeToken()` / `getEditorThemeTokenContrast()` / `toDesignTokenFormatGroup()` as a host-facing catalog of the nine shipped chrome tokens. 3. Reject unknown token names with a stable payload-redacted `EditorThemeTokenError`. 4. Preview repeating `.cwl-tb-btn` states, the shipped Toolbar, and token swatches in Storybook. 5. Add no network, persistence, credential, model, tenant, Figma, or design-tool sync authority. @@ -28,7 +28,7 @@ WCAG 2.2 requires sufficient contrast for text and user-interface components (Wo ## Test-first evidence - RED: `src/designTokens.test.ts` failed because `./designTokens.js` did not exist. -- GREEN: the catalog lists the nine shipped tokens, aligns light/dark/print color values with the matching `src/styles.css` media blocks, rejects unknown names without reflecting caller input, and emits a DTCG 2025.10 group. +- GREEN: the catalog lists the nine shipped tokens, aligns light/dark/print color values with the matching `src/styles.css` media blocks, reports WCAG 2.2 contrast for shipped color pairs, rejects unknown names without reflecting caller input, and emits a DTCG 2025.10 group. ## Residual risk diff --git a/src/designTokenDocumentation.test.ts b/src/designTokenDocumentation.test.ts index a11a1610..d0e84018 100644 --- a/src/designTokenDocumentation.test.ts +++ b/src/designTokenDocumentation.test.ts @@ -32,6 +32,8 @@ describe('editor design-token documentation contract', () => { expect(doctoring).toContain('ADR 0031'); expect(doctoring).not.toContain('ADR 0027'); expect(operatorGuide).toContain('Override the named custom properties on `.cwl-editor`'); + expect(operatorGuide).toContain('getEditorThemeTokenContrast'); + expect(operatorGuide).toContain('contrastRatioFromHex'); expect(operatorGuide).toContain('Do not edit Inkspan internals'); expect(inventory).toContain('Editor Chrome / Toolbar Button States'); expect(inventory).toContain('Editor Chrome / Live Toolbar'); diff --git a/src/designTokens.test.ts b/src/designTokens.test.ts index 80a18524..5cd1fce5 100644 --- a/src/designTokens.test.ts +++ b/src/designTokens.test.ts @@ -4,8 +4,11 @@ import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; import { + EditorThemeTokenContrastError, EditorThemeTokenError, + contrastRatioFromHex, getEditorThemeToken, + getEditorThemeTokenContrast, listEditorThemeTokens, toDesignTokenFormatGroup, } from './designTokens.js'; @@ -82,6 +85,46 @@ describe('editor theme token catalog', () => { expect(() => getEditorThemeToken('not-a-theme-token')).not.toThrow(/not-a-theme-token/u); }); + it('reports WCAG 2.2 contrast for shipped 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'); + + 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.hostAction).toContain('Override --cwl-fg and --cwl-bg on .cwl-editor'); + expect(light.hostAction).toContain('WCAG 2.2'); + }); + + 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 a DTCG 2025.10 group that hosts can copy into a theme file', () => { const group = toDesignTokenFormatGroup(); const foreground = group.cwl.fg; diff --git a/src/designTokens.ts b/src/designTokens.ts index c12241bd..70274ffb 100644 --- a/src/designTokens.ts +++ b/src/designTokens.ts @@ -9,6 +9,8 @@ export type EditorThemeTokenRole = 'color' | 'dimension' | 'fontFamily'; +export type EditorThemeTokenScheme = 'light' | 'dark' | 'print'; + export type EditorThemeTokenName = | 'cwl-fg' | 'cwl-muted' @@ -30,6 +32,14 @@ export interface EditorThemeToken { readonly hostAction: string; } +export interface EditorThemeTokenContrast { + readonly foreground: EditorThemeTokenName; + readonly background: EditorThemeTokenName; + readonly scheme: EditorThemeTokenScheme; + readonly ratio: number; + readonly hostAction: string; +} + export interface DesignTokenFormatNode { readonly $type: 'color' | 'dimension' | 'fontFamily'; readonly $value: string | readonly string[]; @@ -66,6 +76,16 @@ export class EditorThemeTokenError extends Error { } } +/** 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(cssCustomProperty: `--${EditorThemeTokenName}`): string { return `Override ${cssCustomProperty} on .cwl-editor after checking WCAG 2.2 contrast against --cwl-bg. Do not edit Inkspan internals.`; } @@ -139,6 +159,90 @@ export function getEditorThemeToken(name: string): EditorThemeToken { 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 { + 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 color tokens. + * + * Use this after a host override to decide whether `--cwl-fg` still meets + * 4.5:1 against `--cwl-bg`. 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(); + } + return Object.freeze({ + foreground: foreground.name, + background: background.name, + scheme, + ratio: contrastRatioFromHex( + colorValueForScheme(foreground, scheme), + colorValueForScheme(background, scheme), + ), + hostAction: `Override --${foreground.name} and --${background.name} on .cwl-editor after checking WCAG 2.2 contrast. Do not edit Inkspan internals.`, + }); +} + function formatTokenValue(token: EditorThemeToken): string | readonly string[] { if (token.role !== 'fontFamily') { return token.lightValue; diff --git a/src/index.test.ts b/src/index.test.ts index 28b51fa6..0ad7fa08 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -52,8 +52,11 @@ describe('package entry point', () => { 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(typeof api.toDesignTokenFormatGroup).toBe('function'); expect(typeof api.EditorThemeTokenError).toBe('function'); + expect(typeof api.EditorThemeTokenContrastError).toBe('function'); }); it('re-exports the standalone base64 converter', () => { diff --git a/src/index.ts b/src/index.ts index e0bb5bc1..d54c99eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -168,8 +168,11 @@ export type { PlainTextOptions } from './markdown/plainText.js'; // Host-facing editor chrome theme tokens (CSS remains runtime authority). export { + EditorThemeTokenContrastError, EditorThemeTokenError, + contrastRatioFromHex, getEditorThemeToken, + getEditorThemeTokenContrast, listEditorThemeTokens, toDesignTokenFormatGroup, } from './designTokens.js'; @@ -177,8 +180,10 @@ export type { DesignTokenFormatGroup, DesignTokenFormatNode, EditorThemeToken, + EditorThemeTokenContrast, EditorThemeTokenName, EditorThemeTokenRole, + EditorThemeTokenScheme, } from './designTokens.js'; // Standalone, framework-agnostic base64 / data-URI converter.