From 7b99db28560f5bc5d1b70efeed9e92825b2823a0 Mon Sep 17 00:00:00 2001 From: bitgorust Date: Mon, 17 Aug 2026 17:01:20 +0200 Subject: [PATCH 01/10] docs: propose portable renderer correctness --- .../change.json | 21 ++++ .../declarative-authoring-preflight/delta.md | 51 +++++++++ .../evidence.md | 13 +++ .../proposal.md | 32 ++++++ .../declarative-authoring-preflight/tasks.md | 8 ++ .../portable-asset-pipeline/change.json | 21 ++++ .../changes/portable-asset-pipeline/delta.md | 76 +++++++++++++ .../changes/portable-asset-pipeline/design.md | 56 ++++++++++ .../portable-asset-pipeline/evidence.md | 19 ++++ .../portable-asset-pipeline/proposal.md | 34 ++++++ .../changes/portable-asset-pipeline/tasks.md | 8 ++ .../renderer-accessibility-i18n/change.json | 21 ++++ .../renderer-accessibility-i18n/delta.md | 54 ++++++++++ .../renderer-accessibility-i18n/evidence.md | 13 +++ .../renderer-accessibility-i18n/proposal.md | 35 ++++++ .../renderer-accessibility-i18n/tasks.md | 9 ++ .../renderer-design-tokens/change.json | 21 ++++ specs/changes/renderer-design-tokens/delta.md | 52 +++++++++ .../renderer-design-tokens/evidence.md | 13 +++ .../renderer-design-tokens/proposal.md | 32 ++++++ specs/changes/renderer-design-tokens/tasks.md | 8 ++ .../renderer-performance-budgets/change.json | 21 ++++ .../renderer-performance-budgets/delta.md | 101 ++++++++++++++++++ .../renderer-performance-budgets/evidence.md | 25 +++++ .../renderer-performance-budgets/proposal.md | 32 ++++++ .../renderer-performance-budgets/tasks.md | 9 ++ 26 files changed, 785 insertions(+) create mode 100644 specs/changes/declarative-authoring-preflight/change.json create mode 100644 specs/changes/declarative-authoring-preflight/delta.md create mode 100644 specs/changes/declarative-authoring-preflight/evidence.md create mode 100644 specs/changes/declarative-authoring-preflight/proposal.md create mode 100644 specs/changes/declarative-authoring-preflight/tasks.md create mode 100644 specs/changes/portable-asset-pipeline/change.json create mode 100644 specs/changes/portable-asset-pipeline/delta.md create mode 100644 specs/changes/portable-asset-pipeline/design.md create mode 100644 specs/changes/portable-asset-pipeline/evidence.md create mode 100644 specs/changes/portable-asset-pipeline/proposal.md create mode 100644 specs/changes/portable-asset-pipeline/tasks.md create mode 100644 specs/changes/renderer-accessibility-i18n/change.json create mode 100644 specs/changes/renderer-accessibility-i18n/delta.md create mode 100644 specs/changes/renderer-accessibility-i18n/evidence.md create mode 100644 specs/changes/renderer-accessibility-i18n/proposal.md create mode 100644 specs/changes/renderer-accessibility-i18n/tasks.md create mode 100644 specs/changes/renderer-design-tokens/change.json create mode 100644 specs/changes/renderer-design-tokens/delta.md create mode 100644 specs/changes/renderer-design-tokens/evidence.md create mode 100644 specs/changes/renderer-design-tokens/proposal.md create mode 100644 specs/changes/renderer-design-tokens/tasks.md create mode 100644 specs/changes/renderer-performance-budgets/change.json create mode 100644 specs/changes/renderer-performance-budgets/delta.md create mode 100644 specs/changes/renderer-performance-budgets/evidence.md create mode 100644 specs/changes/renderer-performance-budgets/proposal.md create mode 100644 specs/changes/renderer-performance-budgets/tasks.md diff --git a/specs/changes/declarative-authoring-preflight/change.json b/specs/changes/declarative-authoring-preflight/change.json new file mode 100644 index 0000000..8eab124 --- /dev/null +++ b/specs/changes/declarative-authoring-preflight/change.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 1, + "id": "declarative-authoring-preflight", + "title": "Preflight declarative authoring errors", + "lane": "standard", + "status": "draft", + "affectedRequirements": ["RENDER-02", "QUAL-02"], + "currentSpecs": ["specs/current/portable-renderer.spec.md"], + "currentSpecsUpdated": false, + "approval": { + "by": "", + "at": "" + }, + "withdrawal": { + "by": "", + "at": "", + "reason": "" + }, + "createdAt": "2026-08-17", + "archivedAt": null +} diff --git a/specs/changes/declarative-authoring-preflight/delta.md b/specs/changes/declarative-authoring-preflight/delta.md new file mode 100644 index 0000000..39cce97 --- /dev/null +++ b/specs/changes/declarative-authoring-preflight/delta.md @@ -0,0 +1,51 @@ +# Specification delta: Preflight declarative authoring errors + +## MODIFIED + +### Requirement: RENDER-02 + +The declarative format validates frontmatter, components, tables, charts, Mermaid, anchors, +task lists, alerts, and asset declarations in one side-effect-free pass. It returns bounded, +ordered diagnostics with stable code, severity, source location, and next action, while the +standalone renderer preserves escaped inline error fallbacks. + +#### Scenario: Normal behavior + +- **Given:** a document with several independent authoring mistakes +- **When:** preflight runs through the CLI or plugin +- **Then:** all detectable errors are returned in source order before any artifact write + +#### Scenario: Failure or refusal + +- **Given:** a diagnostic would include a large or sensitive payload +- **When:** it is formatted +- **Then:** content is redacted/truncated and the error remains actionable + +#### Scenario: Relevant boundary + +- **Given:** diagnostics exceed the count or byte ceiling +- **When:** aggregation reaches the ceiling +- **Then:** output ends with an explicit omitted-count diagnostic rather than partial success + +### Requirement: QUAL-02 + +Preflight and render acceptance share schemas and deterministic fixtures so no public authoring +path bypasses validation and inline fallback behavior cannot silently disagree with preflight. + +#### Scenario: Normal behavior + +- **Given:** the valid and invalid declarative corpus +- **When:** domain, CLI, plugin, and standalone render tests run +- **Then:** each surface reports the same codes and valid documents have no errors + +#### Scenario: Failure or refusal + +- **Given:** a fence kind lacks a preflight or fallback test +- **When:** verification runs +- **Then:** the packet and Phase 2 correctness gate fail + +#### Scenario: Relevant boundary + +- **Given:** the same input runs from different cwd paths and times +- **When:** diagnostics are compared +- **Then:** codes, order, locations, and redaction are identical diff --git a/specs/changes/declarative-authoring-preflight/evidence.md b/specs/changes/declarative-authoring-preflight/evidence.md new file mode 100644 index 0000000..bf83e64 --- /dev/null +++ b/specs/changes/declarative-authoring-preflight/evidence.md @@ -0,0 +1,13 @@ +# Evidence: Preflight declarative authoring errors + +## Requirement: RENDER-02 +- Validation: authors need all actionable schema errors before publication without losing safe fallbacks. +- Verification: planned golden multi-error domain/CLI/plugin/standalone parity tests. +- Result: pending implementation. +- Evidence: [@manual](docs/product-spec.md) + +## Requirement: QUAL-02 +- Validation: every declarative surface must share deterministic validation behavior. +- Verification: planned full valid/invalid corpus and cwd/time independence tests. +- Result: pending implementation. +- Evidence: [@manual](docs/engineering-principles.md) diff --git a/specs/changes/declarative-authoring-preflight/proposal.md b/specs/changes/declarative-authoring-preflight/proposal.md new file mode 100644 index 0000000..eaa1ad2 --- /dev/null +++ b/specs/changes/declarative-authoring-preflight/proposal.md @@ -0,0 +1,32 @@ +# Proposal: Preflight declarative authoring errors + +## Outcome + +Authors receive one bounded, ordered preflight report containing every detectable Markdown, +frontmatter, component, chart, table, Mermaid, asset-reference, and trusted-mode error before +publication, while standalone rendering retains safe inline fallbacks. + +## Context + +The current renderer converts malformed component fences to inline error cards one at a time. +That resilience is useful for reading but inefficient for agent authoring and can allow a +publish attempt before the complete error set is known. + +## Scope + +- In scope: side-effect-free preflight, stable diagnostic codes/severity/source locations, + bounded aggregation and truncation, CLI/plugin results, trusted-HTML disclosure, and inline + error parity. +- Out of scope: arbitrary HTML repair, execution of component code, network validation, or + treating warnings as silent success. + +## Risks and rollback + +- Risk: parser/preflight/render divergence or diagnostics echoing sensitive payloads. +- Rollback: retain inline safe fallbacks and disable the aggregate preflight surface without + accepting invalid content. + +## Validation plan + +Golden multi-error fixtures must produce complete, stable, redacted diagnostics through the +domain, CLI, and plugin before writes; valid corpus pages must have zero error diagnostics. diff --git a/specs/changes/declarative-authoring-preflight/tasks.md b/specs/changes/declarative-authoring-preflight/tasks.md new file mode 100644 index 0000000..8f2c782 --- /dev/null +++ b/specs/changes/declarative-authoring-preflight/tasks.md @@ -0,0 +1,8 @@ +# Tasks: Preflight declarative authoring errors + +- [ ] Confirm proposal validation and human approval. +- [ ] Implement shared typed diagnostics and side-effect-free aggregate preflight. +- [ ] Expose bounded preflight through CLI/plugin while retaining inline fallback parity. +- [ ] Add multi-error, redaction/truncation, valid-corpus, parity, and no-write tests. +- [ ] Record evidence and update `specs/current/portable-renderer.spec.md`. +- [ ] Run repository validation and archive the packet. diff --git a/specs/changes/portable-asset-pipeline/change.json b/specs/changes/portable-asset-pipeline/change.json new file mode 100644 index 0000000..8d2500d --- /dev/null +++ b/specs/changes/portable-asset-pipeline/change.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 1, + "id": "portable-asset-pipeline", + "title": "Add a contained offline asset pipeline", + "lane": "high-risk", + "status": "draft", + "affectedRequirements": ["RENDER-04", "RENDER-05", "SEC-02"], + "currentSpecs": ["specs/current/portable-renderer.spec.md"], + "currentSpecsUpdated": false, + "approval": { + "by": "", + "at": "" + }, + "withdrawal": { + "by": "", + "at": "", + "reason": "" + }, + "createdAt": "2026-08-17", + "archivedAt": null +} diff --git a/specs/changes/portable-asset-pipeline/delta.md b/specs/changes/portable-asset-pipeline/delta.md new file mode 100644 index 0000000..31e3566 --- /dev/null +++ b/specs/changes/portable-asset-pipeline/delta.md @@ -0,0 +1,76 @@ +# Specification delta: Add a contained offline asset pipeline + +## MODIFIED + +### Requirement: RENDER-04 + +The renderer inlines only required runtimes and embeds each declared local asset from an +explicit worktree root after containment, regular-file, content-type, active-content, and size +validation. Missing, external, unsupported, changed-during-read, or unlabelled meaningful +assets fail preflight; strict offline pages never emit a broken remote reference. + +#### Scenario: Normal behavior + +- **Given:** a contained allowlisted image with meaningful alt text +- **When:** Markdown rendering completes +- **Then:** the page contains a hashed data URI and makes no view-time request + +#### Scenario: Failure or refusal + +- **Given:** a missing, external, mislabeled, active, or oversized asset +- **When:** preflight resolves it +- **Then:** publication performs no writes and returns a bounded actionable diagnostic + +#### Scenario: Relevant boundary + +- **Given:** a path or symlink resolves outside the explicit worktree root +- **When:** containment is checked before and after reading +- **Then:** the asset bytes are not returned or embedded + +### Requirement: RENDER-05 + +The 15 MiB default final-page cap applies after data-URI encoding, generated markup, runtimes, +and footer expansion. Source, per-asset, asset-count, aggregate-read, encoded, and final-output +limits are checked before publication without allocating unbounded content. + +#### Scenario: Normal behavior + +- **Given:** all expanded contributions remain within every limit +- **When:** final HTML is assembled +- **Then:** reported byte accounting equals the bytes passed to the lifecycle transaction + +#### Scenario: Failure or refusal + +- **Given:** base64 or footer expansion would exceed the final cap +- **When:** final accounting runs +- **Then:** publication refuses before selecting a new revision + +#### Scenario: Relevant boundary + +- **Given:** input is exactly at a documented hard limit +- **When:** it is encoded deterministically +- **Then:** the boundary is accepted while the next byte is rejected + +### Requirement: SEC-02 + +Asset paths, MIME claims, metadata, SVG content, counts, and sizes are untrusted and bounded. +Resolution uses safe relative syntax plus realpath containment and refuses special files, +encoded separators, symlink changes, and ambiguous types before authority or output expansion. + +#### Scenario: Normal behavior + +- **Given:** a stable regular file beneath the authorized root +- **When:** its declared and detected properties agree +- **Then:** only its exact bounded bytes and safe metadata enter the renderer + +#### Scenario: Failure or refusal + +- **Given:** traversal, a device/FIFO, or content whose bytes contradict its declaration +- **When:** validation runs +- **Then:** access fails closed without reading beyond the configured bound + +#### Scenario: Relevant boundary + +- **Given:** the file identity changes between inspection and read +- **When:** post-read verification runs +- **Then:** the candidate is discarded and no publication mutation occurs diff --git a/specs/changes/portable-asset-pipeline/design.md b/specs/changes/portable-asset-pipeline/design.md new file mode 100644 index 0000000..dc28c67 --- /dev/null +++ b/specs/changes/portable-asset-pipeline/design.md @@ -0,0 +1,56 @@ +# Design: Add a contained offline asset pipeline + +Required for high-risk changes. + +## Context and constraints + +Asset declarations are untrusted input crossing from Markdown into filesystem reads and then +into a strict-CSP portable file. Resolution must be independent of process cwd, never follow +an escaping symlink, never fetch, and account for encoded expansion before publication. The +existing 15 MiB final-page cap and credential scan remain authoritative. + +## Chosen design + +Parse asset references into typed declarations, reject absolute/URL/encoded traversal forms, +resolve from an explicit worktree root, and compare real paths for both the root and regular +file. Read with per-file and aggregate ceilings, identify allowlisted media from bytes, sanitize +active SVG into a constrained generated representation or refuse it, and produce data URIs with +declared MIME, byte hash, source-relative path, and alt-text status. Rendering returns a staged +result; publication applies footer expansion, scans final bytes, enforces the cap, and only then +enters the lifecycle transaction. Errors are bounded and contain paths/metadata, never bytes. + +## Alternatives + +- Emit ordinary relative URLs: rejected because strict CSP/offline removal would break them. +- Fetch or cache HTTP assets: rejected because it adds network authority, nondeterminism, and + provenance/licensing risk. +- Trust filename extensions: rejected because mislabeled active content crosses the boundary. +- Permit raw SVG unchanged: rejected because scripts, external references, and parser features + exceed the Markdown trust model. +- Inline first and check size afterward only: rejected because reads/base64 allocation also need + pre-allocation bounds; both source and final representations are bounded. + +## Trust, privacy, and failure boundaries + +Only the explicit worktree root is readable. Symlinks, non-regular files, device paths, special +files, encoded separators, and changed-during-read identities fail closed. Diagnostics include +relative path, code, size, and next action but no asset body. Data URIs cannot request at view +time; the strict CSP stays unchanged. Private or credential-looking final content remains under +the existing scanner and explicit override contract. Unsupported/missing/external assets block +strict publication rather than yielding broken output. + +## Migration, rollout, and rollback + +Ship behind explicit declarations with no migration of existing HTML. Preflight and browser +offline evidence precede use in canonical fixtures. Rollback removes asset declaration support; +already-generated portable files remain self-contained. No schema/default-enable migration and +no destructive data operation is required. + +## Formal-method decision + +- Decision: bounded property model. +- Property and rationale: for every modeled reference and size boundary, the result is either a + contained allowlisted immutable byte sequence whose encoded contribution is counted exactly, + or a refusal with zero publication writes; no reference yields a view-time request. +- Model/evidence path: `test/model/asset-pipeline-model.ts` plus filesystem/symlink mutation and + offline browser tests to be added during implementation. diff --git a/specs/changes/portable-asset-pipeline/evidence.md b/specs/changes/portable-asset-pipeline/evidence.md new file mode 100644 index 0000000..c10b0ca --- /dev/null +++ b/specs/changes/portable-asset-pipeline/evidence.md @@ -0,0 +1,19 @@ +# Evidence: Add a contained offline asset pipeline + +## Requirement: RENDER-04 +- Validation: Phase 2 requires contained assets and a self-contained offline mixed-content page. +- Verification: planned filesystem, MIME, SVG, offline-browser, and hostile-reference tests. +- Result: pending implementation and browser evidence. +- Evidence: [@manual](docs/roadmap.md) + +## Requirement: RENDER-05 +- Validation: final output must retain the existing 15 MiB safety boundary after expansion. +- Verification: planned exact source/encoded/footer/final boundary tests and byte reports. +- Result: pending implementation. +- Evidence: [@manual](docs/product-spec.md) + +## Requirement: SEC-02 +- Validation: assets add an untrusted filesystem and active-content boundary. +- Verification: planned traversal, symlink race, special-file, content mismatch, and resource tests. +- Result: pending implementation and model evidence. +- Evidence: [@manual](docs/threat-model.md) diff --git a/specs/changes/portable-asset-pipeline/proposal.md b/specs/changes/portable-asset-pipeline/proposal.md new file mode 100644 index 0000000..49d5e32 --- /dev/null +++ b/specs/changes/portable-asset-pipeline/proposal.md @@ -0,0 +1,34 @@ +# Proposal: Add a contained offline asset pipeline + +## Outcome + +Markdown artifacts can declare contained local images, SVG, fonts, and supported media and +receive one self-contained offline HTML file whose final bytes and provenance are verified. + +## Context + +The renderer currently bundles code runtimes but has no declared worktree-asset contract. +Ordinary image references can therefore remain external, break under the strict CSP, escape +the intended root, or bypass the final 15 MiB accounting boundary. + +## Scope + +- In scope: declared worktree-root resolution, realpath containment, regular-file checks, + content-based MIME validation, allowlisted formats, per-asset/count/aggregate bounds, data + URI embedding, alt-text diagnostics, provenance, final-byte accounting, and explicit + missing/external/unsupported failures. +- Out of scope: network fetching, remote import, arbitrary SVG script execution, project CSS, + secret scanning changes, asset optimization that changes bytes, or broad font licensing. + +## Risks and rollback + +- Risk: path/symlink escape, decompression/resource abuse, mislabeled content, SVG active + content, private-file disclosure, and a footer/asset expansion that exceeds the cap. +- Rollback: disable declared assets and return actionable preflight failures; existing pages + remain standalone and the pre-asset Markdown behavior remains available. + +## Validation plan + +Use synthetic contained/escaping/missing/mislabeled/oversized assets, a property model for +resolution and byte accounting, offline browser request observation, final-file secret scan, +and a representative page containing an image, chart, table, and controls. diff --git a/specs/changes/portable-asset-pipeline/tasks.md b/specs/changes/portable-asset-pipeline/tasks.md new file mode 100644 index 0000000..88fb92f --- /dev/null +++ b/specs/changes/portable-asset-pipeline/tasks.md @@ -0,0 +1,8 @@ +# Tasks: Add a contained offline asset pipeline + +- [ ] Confirm proposal validation and human approval. +- [ ] Add the typed declaration/parser, bounded resolver, MIME/active-content validators, and exact byte accounting. +- [ ] Integrate asset preflight and expansion before final scan/cap/lifecycle publication. +- [ ] Add property, traversal/symlink/special-file, boundary, offline-browser, and mixed-page tests. +- [ ] Retain final request/console/byte evidence and update `specs/current/portable-renderer.spec.md`. +- [ ] Run repository validation and archive the packet. diff --git a/specs/changes/renderer-accessibility-i18n/change.json b/specs/changes/renderer-accessibility-i18n/change.json new file mode 100644 index 0000000..1ac9965 --- /dev/null +++ b/specs/changes/renderer-accessibility-i18n/change.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 1, + "id": "renderer-accessibility-i18n", + "title": "Complete renderer accessibility and internationalization", + "lane": "standard", + "status": "draft", + "affectedRequirements": ["RENDER-06", "QUAL-04"], + "currentSpecs": ["specs/current/portable-renderer.spec.md"], + "currentSpecsUpdated": false, + "approval": { + "by": "", + "at": "" + }, + "withdrawal": { + "by": "", + "at": "", + "reason": "" + }, + "createdAt": "2026-08-17", + "archivedAt": null +} diff --git a/specs/changes/renderer-accessibility-i18n/delta.md b/specs/changes/renderer-accessibility-i18n/delta.md new file mode 100644 index 0000000..1386397 --- /dev/null +++ b/specs/changes/renderer-accessibility-i18n/delta.md @@ -0,0 +1,54 @@ +# Specification delta: Complete renderer accessibility and internationalization + +## MODIFIED + +### Requirement: RENDER-06 + +Built-in pages meet WCAG 2.2 AA with meaningful landmarks/headings, chart summaries, table +captions, control names/states, visible focus, logical keyboard order, 200% zoom/reflow, +reduced motion, supported color modes, Unicode, explicit language/direction, and deterministic +locale/time-zone formatting. Decision/comment conflict and recovery flows are keyboard usable +and announced without relying on color. + +#### Scenario: Normal behavior + +- **Given:** a representative page opened at desktop/mobile widths with keyboard only +- **When:** the user navigates content, decisions, comments, tables, and theme controls +- **Then:** order, focus, names, states, notices, and reflow remain operable and understandable + +#### Scenario: Failure or refusal + +- **Given:** a chart lacks a meaningful author summary or data-derived fallback +- **When:** accessibility preflight runs +- **Then:** publication reports the missing semantic equivalent rather than hiding the chart + +#### Scenario: Relevant boundary + +- **Given:** RTL Unicode content at 200% zoom with reduced motion enabled +- **When:** the page renders and interactive state changes +- **Then:** logical reading order, labels, content, and focus remain intact without clipping + +### Requirement: QUAL-04 + +Accessibility changes retain real-surface desktop/mobile-width, keyboard-only, color-mode, +zoom, reduced-motion, automated WCAG, console, and screenshot evidence. Manual screen-reader +results are mandatory for certification and remain explicitly unavailable rather than inferred +from automation. + +#### Scenario: Normal behavior + +- **Given:** the declared accessibility fixture matrix and available assistive technology +- **When:** verification runs +- **Then:** exact browser/tool versions, results, screenshots, and manual observations are retained + +#### Scenario: Failure or refusal + +- **Given:** an automated or manual check fails +- **When:** gate status is reconciled +- **Then:** the affected claim remains failed and content is not removed merely to raise a score + +#### Scenario: Relevant boundary + +- **Given:** supported browsers or screen-reader access is unavailable +- **When:** implementation diagnostics pass +- **Then:** independent work continues but the corresponding Phase 2/certification evidence stays open diff --git a/specs/changes/renderer-accessibility-i18n/evidence.md b/specs/changes/renderer-accessibility-i18n/evidence.md new file mode 100644 index 0000000..2f4ce3e --- /dev/null +++ b/specs/changes/renderer-accessibility-i18n/evidence.md @@ -0,0 +1,13 @@ +# Evidence: Complete renderer accessibility and internationalization + +## Requirement: RENDER-06 +- Validation: Phase 2 requires WCAG, keyboard, zoom, motion, Unicode, locale, and RTL correctness. +- Verification: planned semantic/automated checks plus real desktop/mobile-width keyboard and assistive-technology matrix. +- Result: pending implementation; supported-browser and manual screen-reader access are external inputs. +- Evidence: [@manual](docs/roadmap.md) + +## Requirement: QUAL-04 +- Validation: user-visible accessibility cannot be certified from source assertions alone. +- Verification: planned retained browser versions, console results, screenshots, keyboard traces, and manual outcomes. +- Result: pending real-surface evidence. +- Evidence: [@manual](docs/goal-runbook.md) diff --git a/specs/changes/renderer-accessibility-i18n/proposal.md b/specs/changes/renderer-accessibility-i18n/proposal.md new file mode 100644 index 0000000..7cc36f5 --- /dev/null +++ b/specs/changes/renderer-accessibility-i18n/proposal.md @@ -0,0 +1,35 @@ +# Proposal: Complete renderer accessibility and internationalization + +## Outcome + +Built-in pages expose semantic landmarks, summaries, labels, logical keyboard order, visible +focus, reduced-motion behavior, 200% zoom resilience, Unicode/locale/RTL correctness, and +accessible decision/comment workflows through the real browser surface. + +## Context + +The shell has responsive styling and keyboard-capable controls, but Phase 2 requires a complete +audited contract for charts, tables, forms, comments, color modes, motion, bidirectionality, +dates/numbers, and degraded states. Automated checks cannot substitute for manual assistive +technology evidence. + +## Scope + +- In scope: landmarks/headings, chart text summaries, table captions, control names/states, + skip/focus order, keyboard flows, contrast, zoom/reflow, reduced motion, `lang`/`dir`, Unicode, + locale/time-zone formatting, and print/PDF only where behavior can be justified and tested. +- Out of scope: claiming every author-supplied fact/alt text is correct, broad platform support, + or passing manual screen-reader/mobile evidence that was not collected. + +## Risks and rollback + +- Risk: visually hidden content can diverge from charts; focus repair can disrupt reading order; + locale defaults can make output nondeterministic. +- Rollback: retain semantic static summaries and native document order while disabling only the + faulty enhancement; never remove content to make an automated score pass. + +## Validation plan + +Automated WCAG tooling, semantic assertions, keyboard-only desktop/mobile-width workflows, +200% zoom, reduced-motion, LTR/RTL/Unicode/locale fixtures, console checks, screenshots, and +retained manual screen-reader results or an explicit unavailable gate. diff --git a/specs/changes/renderer-accessibility-i18n/tasks.md b/specs/changes/renderer-accessibility-i18n/tasks.md new file mode 100644 index 0000000..13e72bf --- /dev/null +++ b/specs/changes/renderer-accessibility-i18n/tasks.md @@ -0,0 +1,9 @@ +# Tasks: Complete renderer accessibility and internationalization + +- [ ] Confirm proposal validation and human approval. +- [ ] Implement semantic summaries/captions/labels, focus/keyboard behavior, zoom/reflow, and reduced-motion support. +- [ ] Add explicit language/direction plus deterministic Unicode, locale, time-zone, and RTL behavior. +- [ ] Add semantic/automated checks and real desktop/mobile-width keyboard/color/zoom/motion/console tests. +- [ ] Retain screenshots and available manual screen-reader evidence; keep unavailable cells explicit. +- [ ] Update `specs/current/portable-renderer.spec.md`. +- [ ] Run repository validation and archive the packet. diff --git a/specs/changes/renderer-design-tokens/change.json b/specs/changes/renderer-design-tokens/change.json new file mode 100644 index 0000000..4289561 --- /dev/null +++ b/specs/changes/renderer-design-tokens/change.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 1, + "id": "renderer-design-tokens", + "title": "Add bounded renderer design tokens", + "lane": "standard", + "status": "draft", + "affectedRequirements": ["RENDER-07", "SEC-04"], + "currentSpecs": ["specs/current/portable-renderer.spec.md"], + "currentSpecsUpdated": false, + "approval": { + "by": "", + "at": "" + }, + "withdrawal": { + "by": "", + "at": "", + "reason": "" + }, + "createdAt": "2026-08-17", + "archivedAt": null +} diff --git a/specs/changes/renderer-design-tokens/delta.md b/specs/changes/renderer-design-tokens/delta.md new file mode 100644 index 0000000..c458ef1 --- /dev/null +++ b/specs/changes/renderer-design-tokens/delta.md @@ -0,0 +1,52 @@ +# Specification delta: Add bounded renderer design tokens + +## MODIFIED + +### Requirement: RENDER-07 + +Renderer design tokens use a versioned allowlisted schema and deterministic precedence: +explicit prompt values override the project token file, which overrides a curated theme and +built-in defaults. Each source records provenance; invalid higher-precedence input refuses or +falls back explicitly and no token source can execute code. + +#### Scenario: Normal behavior + +- **Given:** valid values at prompt, project, theme, and default levels +- **When:** tokens resolve +- **Then:** each emitted CSS variable has the highest-precedence value and named provenance + +#### Scenario: Failure or refusal + +- **Given:** an unknown token, unsafe value, invalid contrast pair, or oversized file +- **When:** project tokens load +- **Then:** the source is rejected with no partial application or executable output + +#### Scenario: Relevant boundary + +- **Given:** the same valid token set on different platforms +- **When:** CSS variables are generated +- **Then:** ordering and bytes are deterministic while system-font fallback remains explicit + +### Requirement: SEC-04 + +Markdown design configuration accepts data values only: no selectors, declarations, URLs, +markup, script, imports, expressions, or raw CSS. Values are parsed by type and emitted through +fixed CSS-variable slots without changing the strict CSP. + +#### Scenario: Normal behavior + +- **Given:** allowlisted colors, spacing, radius, density, and font-stack identifiers +- **When:** the style block is generated +- **Then:** only fixed property templates receive validated serialized values + +#### Scenario: Failure or refusal + +- **Given:** a token contains CSS/script breakout or a remote URL +- **When:** schema validation runs +- **Then:** rendering refuses the token source and the payload remains inert text + +#### Scenario: Relevant boundary + +- **Given:** trusted HTML mode also supplies page styles +- **When:** metadata is produced +- **Then:** trusted code remains explicitly distinguished from bounded Markdown tokens diff --git a/specs/changes/renderer-design-tokens/evidence.md b/specs/changes/renderer-design-tokens/evidence.md new file mode 100644 index 0000000..a6953fd --- /dev/null +++ b/specs/changes/renderer-design-tokens/evidence.md @@ -0,0 +1,13 @@ +# Evidence: Add bounded renderer design tokens + +## Requirement: RENDER-07 +- Validation: project/prompt visual choices need safe deterministic precedence. +- Verification: planned schema, provenance, precedence, fallback, contrast, and screenshot tests. +- Result: pending implementation and visual evidence. +- Evidence: [@manual](docs/roadmap.md) + +## Requirement: SEC-04 +- Validation: token configuration must not become raw CSS or an executable Markdown escape hatch. +- Verification: planned CSS/script/URL breakout corpus and unchanged-CSP assertions. +- Result: pending implementation. +- Evidence: [@manual](docs/security.md) diff --git a/specs/changes/renderer-design-tokens/proposal.md b/specs/changes/renderer-design-tokens/proposal.md new file mode 100644 index 0000000..5ceae0f --- /dev/null +++ b/specs/changes/renderer-design-tokens/proposal.md @@ -0,0 +1,32 @@ +# Proposal: Add bounded renderer design tokens + +## Outcome + +Portable pages accept a bounded declarative token set with precedence prompt > project file > +curated theme > built-in defaults, without accepting CSS, URLs, markup, or executable values. + +## Context + +Curated themes exist, but projects cannot safely express a consistent visual system and prompt +choices do not have a formal precedence contract. Arbitrary CSS would weaken CSP, portability, +and predictable accessibility. + +## Scope + +- In scope: versioned token schema, allowlisted color/type/spacing/radius/density values, + bounded project discovery, prompt overrides, provenance, contrast validation, and deterministic + CSS-variable emission. +- Out of scope: arbitrary CSS, JavaScript, remote fonts/assets, selectors, layout escape + hatches, or unbounded token names. + +## Risks and rollback + +- Risk: hostile token strings could break style/script boundaries or create inaccessible + contrast and cross-platform font drift. +- Rollback: ignore/refuse the invalid source and fall back to the last lower-precedence valid + theme without changing stored artifact identity. + +## Validation plan + +Schema boundary tests, injection strings, precedence fixtures, contrast checks, and retained +desktop/mobile screenshots must show deterministic bounded output and useful fallback. diff --git a/specs/changes/renderer-design-tokens/tasks.md b/specs/changes/renderer-design-tokens/tasks.md new file mode 100644 index 0000000..4a05c6c --- /dev/null +++ b/specs/changes/renderer-design-tokens/tasks.md @@ -0,0 +1,8 @@ +# Tasks: Add bounded renderer design tokens + +- [ ] Confirm proposal validation and human approval. +- [ ] Implement the versioned allowlisted token schema, bounded project discovery, and precedence/provenance resolver. +- [ ] Emit fixed CSS-variable slots with contrast and injection validation and unchanged CSP. +- [ ] Add precedence, fallback, hostile-value, deterministic-byte, contrast, and real-surface tests. +- [ ] Retain screenshots and update `specs/current/portable-renderer.spec.md`. +- [ ] Run repository validation and archive the packet. diff --git a/specs/changes/renderer-performance-budgets/change.json b/specs/changes/renderer-performance-budgets/change.json new file mode 100644 index 0000000..aff191c --- /dev/null +++ b/specs/changes/renderer-performance-budgets/change.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 1, + "id": "renderer-performance-budgets", + "title": "Enforce renderer performance budgets", + "lane": "standard", + "status": "draft", + "affectedRequirements": ["PERF-01", "PERF-02", "PERF-03", "PERF-05"], + "currentSpecs": ["specs/current/portable-renderer.spec.md"], + "currentSpecsUpdated": false, + "approval": { + "by": "", + "at": "" + }, + "withdrawal": { + "by": "", + "at": "", + "reason": "" + }, + "createdAt": "2026-08-17", + "archivedAt": null +} diff --git a/specs/changes/renderer-performance-budgets/delta.md b/specs/changes/renderer-performance-budgets/delta.md new file mode 100644 index 0000000..5208d81 --- /dev/null +++ b/specs/changes/renderer-performance-budgets/delta.md @@ -0,0 +1,101 @@ +# Specification delta: Enforce renderer performance budgets + +## MODIFIED + +### Requirement: PERF-01 + +The repository keeps a reproducible versioned harness for no-runtime, one-chart, and +multi-runtime portable pages. Reports identify fixture hashes, CPU/memory profile, browser and +Node versions, cold/warm state, sample count, percentile method, variance/noise disposition, +final bytes, and excluded setup time. + +#### Scenario: Normal behavior + +- **Given:** a comparable reference environment and unchanged fixture hashes +- **When:** the harness completes its configured samples +- **Then:** it emits machine-readable p50/p95, byte, useful-content, and interaction results + +#### Scenario: Failure or refusal + +- **Given:** samples are missing, noisy beyond policy, or environment metadata differs +- **When:** comparison is requested +- **Then:** the result is non-comparable and cannot pass the budget gate + +#### Scenario: Relevant boundary + +- **Given:** a cold dependency install is present +- **When:** render timing is calculated +- **Then:** install time is separately reported and never hidden inside or silently removed + +### Requirement: PERF-02 + +On the documented two-core/4 GiB reference profile, CLI rendering p95 is at most two seconds +for the no-runtime fixture and five seconds for the multi-runtime fixture, excluding only the +separately reported first dependency install. + +#### Scenario: Normal behavior + +- **Given:** valid comparable samples on the reference profile +- **When:** p95 is computed +- **Then:** both fixture classes meet their documented limits + +#### Scenario: Failure or refusal + +- **Given:** either p95 exceeds its limit +- **When:** the Phase 2 correctness gate runs +- **Then:** the performance requirement fails with the full distribution retained + +#### Scenario: Relevant boundary + +- **Given:** p95 equals the exact limit +- **When:** the gate evaluates it +- **Then:** it passes while any greater result fails + +### Requirement: PERF-03 + +On the benchmark browser profile, useful content appears within 1.5 seconds for no-runtime, +three seconds for one-runtime, and five seconds for multi-runtime pages; keyboard interaction +is ready within one additional second. Mobile-width/device results are separate and may not +exceed twice the corresponding desktop limit. + +#### Scenario: Normal behavior + +- **Given:** each workload in the benchmark browser +- **When:** cold navigation and keyboard readiness marks are measured +- **Then:** desktop and mobile p95 results meet their respective limits + +#### Scenario: Failure or refusal + +- **Given:** a runtime error, unexpected request, or missed readiness mark +- **When:** the sample completes +- **Then:** it is a hard failure rather than a discarded timing outlier + +#### Scenario: Relevant boundary + +- **Given:** a mobile result equals twice its desktop-class budget +- **When:** the gate evaluates it +- **Then:** it passes while any greater result fails + +### Requirement: PERF-05 + +No-runtime, one-runtime, and multi-runtime final-page byte budgets are versioned alongside the +15 MiB absolute cap. Runtime and asset contributions are reported separately; warning and hard +thresholds are tested before Phase 2 completion. + +#### Scenario: Normal behavior + +- **Given:** a fixture below its warning and hard thresholds +- **When:** final bytes are written +- **Then:** total and contribution breakdown are reported with remaining capacity + +#### Scenario: Failure or refusal + +- **Given:** final expansion exceeds a workload hard budget or the absolute cap +- **When:** publication or benchmark validation runs +- **Then:** it fails without selecting an oversized artifact revision + +#### Scenario: Relevant boundary + +- **Given:** a fixture crosses only the warning threshold +- **When:** the report is emitted +- **Then:** it remains usable but carries an actionable regression warning diff --git a/specs/changes/renderer-performance-budgets/evidence.md b/specs/changes/renderer-performance-budgets/evidence.md new file mode 100644 index 0000000..f4c67d9 --- /dev/null +++ b/specs/changes/renderer-performance-budgets/evidence.md @@ -0,0 +1,25 @@ +# Evidence: Enforce renderer performance budgets + +## Requirement: PERF-01 +- Validation: comparable percentile claims require a versioned environment, corpus, and noise method. +- Verification: planned deterministic harness/report schema and non-comparable environment tests. +- Result: pending implementation. +- Evidence: [@manual](docs/product-spec.md) + +## Requirement: PERF-02 +- Validation: CLI p95 budgets are already normative Phase 2 limits. +- Verification: planned cold/warm no-runtime and multi-runtime reference-profile samples. +- Result: pending comparable benchmark evidence. +- Evidence: [@manual](docs/product-spec.md) + +## Requirement: PERF-03 +- Validation: useful-content and keyboard readiness budgets are part of portable-page correctness. +- Verification: planned real-browser desktop/mobile samples including runtime errors and request checks. +- Result: pending browser benchmark evidence. +- Evidence: [@manual](docs/product-spec.md) + +## Requirement: PERF-05 +- Validation: workload byte budgets and contribution breakdown prevent silent bundle regressions below the absolute cap. +- Verification: planned warning/hard threshold, final-byte, and no-selected-oversize tests. +- Result: pending implementation. +- Evidence: [@manual](docs/product-spec.md) diff --git a/specs/changes/renderer-performance-budgets/proposal.md b/specs/changes/renderer-performance-budgets/proposal.md new file mode 100644 index 0000000..6ec7d1a --- /dev/null +++ b/specs/changes/renderer-performance-budgets/proposal.md @@ -0,0 +1,32 @@ +# Proposal: Enforce renderer performance budgets + +## Outcome + +A reproducible harness reports render and browser percentiles for no-runtime, one-chart, and +multi-runtime fixtures and fails when the documented time or final-byte budgets regress. + +## Context + +The project enforces a final byte cap but lacks a versioned reference environment, warm/cold +method, noise policy, percentile samples, useful-content/interaction marks, and regression +gate for the Phase 2 workload classes. + +## Scope + +- In scope: synthetic stable fixtures, two-core/4 GiB reference profile, render p50/p95, + browser useful-content and keyboard-interactive marks, desktop/mobile separation, final-byte + budgets, sample metadata, noise handling, and machine-readable reports. +- Out of scope: provider/network latency, hosted load/soak, connector cost, or performance + claims on unavailable target platforms. + +## Risks and rollback + +- Risk: noisy CI or hidden cache state can create false passes/failures and encourage fixture- + specific optimization. +- Rollback: preserve reports, mark the environment non-comparable, and block the performance + claim rather than weakening budgets or deleting a failing fixture. + +## Validation plan + +Repeated cold/warm runs must report environment and variance, meet PERF-02/PERF-03 p95 limits, +stay under final byte caps, and fail deterministically for injected regressions. diff --git a/specs/changes/renderer-performance-budgets/tasks.md b/specs/changes/renderer-performance-budgets/tasks.md new file mode 100644 index 0000000..d21561c --- /dev/null +++ b/specs/changes/renderer-performance-budgets/tasks.md @@ -0,0 +1,9 @@ +# Tasks: Enforce renderer performance budgets + +- [ ] Confirm proposal validation and human approval. +- [ ] Implement versioned fixtures, environment capture, sampling, percentile/noise logic, and machine-readable reports. +- [ ] Measure CLI no-runtime/multi-runtime render budgets and final-byte contributions. +- [ ] Measure real-browser desktop/mobile useful-content and keyboard-ready budgets. +- [ ] Add boundary, injected-regression, unexpected-request/error, and non-comparable-environment tests. +- [ ] Retain benchmark reports and update `specs/current/portable-renderer.spec.md`. +- [ ] Run repository validation and archive the packet. From f403afb762b62e830ada11382b96a8f4f1c81d18 Mon Sep 17 00:00:00 2001 From: bitgorust Date: Mon, 17 Aug 2026 17:02:42 +0200 Subject: [PATCH 02/10] docs: approve portable renderer correctness --- specs/changes/declarative-authoring-preflight/change.json | 6 +++--- specs/changes/portable-asset-pipeline/change.json | 6 +++--- specs/changes/renderer-accessibility-i18n/change.json | 6 +++--- specs/changes/renderer-design-tokens/change.json | 6 +++--- specs/changes/renderer-performance-budgets/change.json | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/specs/changes/declarative-authoring-preflight/change.json b/specs/changes/declarative-authoring-preflight/change.json index 8eab124..3364ef0 100644 --- a/specs/changes/declarative-authoring-preflight/change.json +++ b/specs/changes/declarative-authoring-preflight/change.json @@ -3,13 +3,13 @@ "id": "declarative-authoring-preflight", "title": "Preflight declarative authoring errors", "lane": "standard", - "status": "draft", + "status": "approved", "affectedRequirements": ["RENDER-02", "QUAL-02"], "currentSpecs": ["specs/current/portable-renderer.spec.md"], "currentSpecsUpdated": false, "approval": { - "by": "", - "at": "" + "by": "aaron.zeng", + "at": "2026-08-17T15:02:07Z" }, "withdrawal": { "by": "", diff --git a/specs/changes/portable-asset-pipeline/change.json b/specs/changes/portable-asset-pipeline/change.json index 8d2500d..60508ee 100644 --- a/specs/changes/portable-asset-pipeline/change.json +++ b/specs/changes/portable-asset-pipeline/change.json @@ -3,13 +3,13 @@ "id": "portable-asset-pipeline", "title": "Add a contained offline asset pipeline", "lane": "high-risk", - "status": "draft", + "status": "approved", "affectedRequirements": ["RENDER-04", "RENDER-05", "SEC-02"], "currentSpecs": ["specs/current/portable-renderer.spec.md"], "currentSpecsUpdated": false, "approval": { - "by": "", - "at": "" + "by": "aaron.zeng", + "at": "2026-08-17T15:02:07Z" }, "withdrawal": { "by": "", diff --git a/specs/changes/renderer-accessibility-i18n/change.json b/specs/changes/renderer-accessibility-i18n/change.json index 1ac9965..babd0da 100644 --- a/specs/changes/renderer-accessibility-i18n/change.json +++ b/specs/changes/renderer-accessibility-i18n/change.json @@ -3,13 +3,13 @@ "id": "renderer-accessibility-i18n", "title": "Complete renderer accessibility and internationalization", "lane": "standard", - "status": "draft", + "status": "approved", "affectedRequirements": ["RENDER-06", "QUAL-04"], "currentSpecs": ["specs/current/portable-renderer.spec.md"], "currentSpecsUpdated": false, "approval": { - "by": "", - "at": "" + "by": "aaron.zeng", + "at": "2026-08-17T15:02:07Z" }, "withdrawal": { "by": "", diff --git a/specs/changes/renderer-design-tokens/change.json b/specs/changes/renderer-design-tokens/change.json index 4289561..4f1c414 100644 --- a/specs/changes/renderer-design-tokens/change.json +++ b/specs/changes/renderer-design-tokens/change.json @@ -3,13 +3,13 @@ "id": "renderer-design-tokens", "title": "Add bounded renderer design tokens", "lane": "standard", - "status": "draft", + "status": "approved", "affectedRequirements": ["RENDER-07", "SEC-04"], "currentSpecs": ["specs/current/portable-renderer.spec.md"], "currentSpecsUpdated": false, "approval": { - "by": "", - "at": "" + "by": "aaron.zeng", + "at": "2026-08-17T15:02:07Z" }, "withdrawal": { "by": "", diff --git a/specs/changes/renderer-performance-budgets/change.json b/specs/changes/renderer-performance-budgets/change.json index aff191c..2a6f0cb 100644 --- a/specs/changes/renderer-performance-budgets/change.json +++ b/specs/changes/renderer-performance-budgets/change.json @@ -3,13 +3,13 @@ "id": "renderer-performance-budgets", "title": "Enforce renderer performance budgets", "lane": "standard", - "status": "draft", + "status": "approved", "affectedRequirements": ["PERF-01", "PERF-02", "PERF-03", "PERF-05"], "currentSpecs": ["specs/current/portable-renderer.spec.md"], "currentSpecsUpdated": false, "approval": { - "by": "", - "at": "" + "by": "aaron.zeng", + "at": "2026-08-17T15:02:07Z" }, "withdrawal": { "by": "", From 4ad793cea3cd0faba83f9bf06e50acabfab07602 Mon Sep 17 00:00:00 2001 From: bitgorust Date: Mon, 17 Aug 2026 17:18:42 +0200 Subject: [PATCH 03/10] feat: add contained portable asset preflight --- README.md | 2 + .../governance/redistribution-2026-08-16.md | 2 +- .../goal-3-portable-assets-2026-08-17.md | 59 +++ ...3-portable-assets-chromium-2026-08-17.json | 27 ++ ...-3-portable-assets-chromium-2026-08-17.png | Bin 0 -> 82903 bytes docs/redistribution-inventory.json | 11 +- docs/redistribution-policy.md | 2 +- examples/patterns/portable-mixed.md | 64 +++ scripts/portable-browser-evidence.ts | 122 ++++++ skills/artifact-pages/SKILL.md | 5 + .../portable-asset-pipeline/change.json | 4 +- .../portable-asset-pipeline/evidence.md | 22 +- .../changes/portable-asset-pipeline/tasks.md | 10 +- specs/current/portable-renderer.spec.md | 46 ++ src/assets.ts | 405 ++++++++++++++++++ src/cli.ts | 11 +- src/markdown.ts | 24 +- src/plugin.ts | 23 +- src/render.ts | 23 +- test/assets.test.ts | 212 +++++++++ test/model/asset-pipeline-model.ts | 34 ++ 21 files changed, 1077 insertions(+), 31 deletions(-) create mode 100644 docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md create mode 100644 docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.json create mode 100644 docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.png create mode 100644 examples/patterns/portable-mixed.md create mode 100644 scripts/portable-browser-evidence.ts create mode 100644 specs/current/portable-renderer.spec.md create mode 100644 src/assets.ts create mode 100644 test/assets.test.ts create mode 100644 test/model/asset-pipeline-model.ts diff --git a/README.md b/README.md index 2cb1d9c..879d5b8 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ output stays diff-friendly and cheap in tokens. - **Live reload**: `opencode-artifacts serve` refreshes open pages on every republish - **Sharing**: cost-free public snapshots via GitHub Pages or a user-operated Cloudflare Worker + KV; Cloudflare Access is a manual, unverified perimeter - **Safe by default**: no raw HTML passthrough, credential-pattern scan blocks accidental secret leaks, no external requests at view time +- **Contained local images**: Markdown images beneath the worktree are MIME-checked and embedded as hashed data URIs; missing, external, symlinked, active, oversized, or unlabelled inputs fail before publication ## Install @@ -170,6 +171,7 @@ Full reference: [`docs/component-spec.md`](docs/component-spec.md). Short versio - **Components** (JSON fences): `stats` metric cards, `timeline`, `findings` (severity-coded), `compare` variant cards, `callout` insight cards, `progress`, `diff` (annotated), `copy` (copy-to-session button), `decisions` (workshop rows the session reads back via `artifact_state`) - **Charts/diagrams**: ```` ```vega-lite ```` / ```` ```vega ```` / ```` ```echarts ```` / ```` ```mermaid ```` fences; runtimes inline only when used - **Markdown extras**: GitHub alerts (`> [!WARNING]` etc.), task lists, heading anchors, `##` sections become cards +- **Local images**: ordinary `![meaningful alt](path/to/image.png)` resolves from the worktree root and embeds PNG/JPEG/GIF/WebP or constrained static SVG. Use the exact title `"decorative"` with empty alt only for an intentionally decorative image. URLs, absolute paths, traversal, and symlinks are refused. - Broken specs degrade to inline error boxes; the page always ships Worked examples for every canonical pattern: [`examples/patterns/`](examples/patterns/) with diff --git a/docs/evidence/governance/redistribution-2026-08-16.md b/docs/evidence/governance/redistribution-2026-08-16.md index f70625f..ed23211 100644 --- a/docs/evidence/governance/redistribution-2026-08-16.md +++ b/docs/evidence/governance/redistribution-2026-08-16.md @@ -12,7 +12,7 @@ machine-readable disposition is - Repository-authored source, policy, documentation, examples, fixtures, skills, tests, and generated evidence are covered by the root MIT license. -- All 21 retained binary assets are repository-generated screenshots under `docs/evidence/`. +- All 23 retained binary assets are repository-generated screenshots under `docs/evidence/`. Each entry names its synthetic/repository source, MIT disposition, contributor attribution, and exact SHA-256. - The repository contains zero embedded font files. Renderer CSS selects system fallback diff --git a/docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md b/docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md new file mode 100644 index 0000000..89b43c0 --- /dev/null +++ b/docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md @@ -0,0 +1,59 @@ +# Goal 3 portable asset evidence — 2026-08-17 + +Scope: implementation evidence for `portable-asset-pipeline` on the Goal 3 branch. This is a +renderer observation, not a supported-platform or accessibility certification. + +## Automated boundary evidence + +Environment: official `node:24-bookworm`, Node 24.19.0, npm 11.7.0, container network disabled. + +- `npm run build`: passed. +- `npm test`: 180/180 passed at the first full checkpoint; the focused asset suite passed + 10/10 before the final publication-success assertion was added. +- `npm run check`: all 35 registered structural checks passed. +- `test/assets.test.ts`: contained PNG expansion and hash, explicit decorative semantics, + constrained SVG reconstruction, active SVG refusal, external/traversal/encoded path + refusal, every-symlink refusal, non-regular/missing/MIME mismatch refusal, exact source/count/ + file/final boundaries, descriptor mutation detection, WOFF2 typing, synchronous broken-URL + prevention, plugin no-write refusal, and plugin expanded-byte publication. +- `test/model/asset-pipeline-model.ts`: exhaustively enumerates 32 authority/property masks + across seven byte boundaries. Every result either returns the exact base64 contribution for + a contained stable regular allowlisted sequence or refuses with zero returned source bytes + and zero view-time requests. +- Existing `test/gallery.test.ts` and lifecycle tests retain the footer-expanded 15 MiB refusal + before transactional write. + +Diagnostics carry only a bounded relative path, code, size metadata, and next action. They do +not carry asset bytes. + +## Real offline browser observation + +Fixture: `examples/patterns/portable-mixed.md`, rendered by the built CLI to a 740,456-byte +file containing a local PNG, Vega-Lite chart, table, and decision buttons. + +Harness: `scripts/portable-browser-evidence.ts` against Selenium standalone Chromium 151 at a +1440 × 1600 viewport. CDP network emulation was set offline before navigation. The retained +machine-readable report is +[`goal-3-portable-assets-chromium-2026-08-17.json`](goal-3-portable-assets-chromium-2026-08-17.json) +and the retained visual is +[`goal-3-portable-assets-chromium-2026-08-17.png`](goal-3-portable-assets-chromium-2026-08-17.png). + +Observed: + +- document ready and useful chart content in 492 ms in this single non-benchmark run; +- embedded image complete at natural width 1011 with the expected exact SHA-256; +- one chart visual, one table, and two decision buttons present; +- Enter selected the focused decision button through WebDriver keyboard actions; +- zero browser console entries; +- request inventory contained only the `file:` document and embedded `data:image/png`; zero + HTTP(S) requests occurred; +- on-disk CSP remained `default-src 'none'; script-src 'unsafe-inline'; style-src + 'unsafe-inline'; img-src data:; connect-src 'none'`. + +## Known incomplete boundary + +Contained WOFF/WOFF2 is validated and inlined, but the current CSP blocks font use through +`default-src 'none'`. Adding only `font-src data:` is awaiting the repository's explicit CSP +change approval; no working custom-font claim is made until that decision and browser evidence +land. Mobile, keyboard traversal beyond the exercised control, screen reader, and supported- +browser evidence belong to the later Goal 3 accessibility packet and remain unverified. diff --git a/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.json b/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.json new file mode 100644 index 0000000..32d1d0e --- /dev/null +++ b/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.json @@ -0,0 +1,27 @@ +{ + "capturedAt": "2026-08-17T15:15:03.968Z", + "browser": "Chromium 151 via selenium/standalone-chromium", + "offlineEmulation": true, + "usefulContentMs": 492, + "observations": { + "activeElement": "decision-opt selected", + "chartVisuals": 1, + "csp": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; connect-src 'none'", + "decisionButtons": 2, + "imageComplete": true, + "imageHash": "62f848af2e23c26493d3f9e24b6af4e59516e8d9ce1e0fd2b68169dfc98281dc", + "imageNaturalWidth": 1011, + "imageSource": "data:image/png;base64,iVBORw0KGg", + "keyboardSelected": true, + "pageBytes": 753580, + "readyState": "complete", + "tableCaptions": [], + "tables": 1 + }, + "browserLogs": [], + "requestUrls": [ + "file:///evidence/portable-mixed.html", + "data:image/png;base64,[embedded]" + ], + "networkRequestUrls": [] +} diff --git a/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.png b/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.png new file mode 100644 index 0000000000000000000000000000000000000000..6e4a4764199788f45b65b404d7378df62e654b20 GIT binary patch literal 82903 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 literal 0 HcmV?d00001 diff --git a/docs/redistribution-inventory.json b/docs/redistribution-inventory.json index e56dcbf..2bb5e93 100644 --- a/docs/redistribution-inventory.json +++ b/docs/redistribution-inventory.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "reviewedAt": "2026-08-16", + "reviewedAt": "2026-08-17", "repositoryLicense": "MIT", "authoredScopes": [ { "path": ".github/", "provenance": "repository-authored", "license": "MIT" }, @@ -210,6 +210,15 @@ "attribution": "opencode-artifacts contributors", "status": "approved" }, + { + "path": "docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.png", + "sha256": "de64548e9c0e40b594f31b3bc84cb09d261ecc73c3dd5b756ecd5ac74a7a529d", + "provenance": "repository-generated browser capture", + "source": "examples/patterns/portable-mixed.md rendered offline in Chromium 151", + "license": "MIT", + "attribution": "opencode-artifacts contributors", + "status": "approved" + }, { "path": "docs/evidence/theme-toggle-dark.png", "sha256": "b495dfbb3bc4a8c5b3e7a445cd63e5eb32f3e59ff3f857f0c1d087796033b742", diff --git a/docs/redistribution-policy.md b/docs/redistribution-policy.md index ef06ebb..53032a6 100644 --- a/docs/redistribution-policy.md +++ b/docs/redistribution-policy.md @@ -1,6 +1,6 @@ # Redistribution and attribution inventory -Policy version: 1. Last reviewed: 2026-08-16. +Policy version: 1. Last reviewed: 2026-08-17. The machine-readable [`redistribution-inventory.json`](redistribution-inventory.json) is the complete Phase 0 disposition for repository documentation, examples, retained binary assets, diff --git a/examples/patterns/portable-mixed.md b/examples/patterns/portable-mixed.md new file mode 100644 index 0000000..b7efd9f --- /dev/null +++ b/examples/patterns/portable-mixed.md @@ -0,0 +1,64 @@ +--- +title: Portable release pulse +icon: 🧳 +description: Offline mixed-content fixture for the portable renderer gate +source: repository-authored synthetic release data, 2026-08-17 +--- + +This fixture combines a contained local image, a chart, a semantic table, and keyboard-operable +controls in one strict-CSP file. + +![Deploy-failure dashboard used as a contained local image](docs/evidence/patterns/dashboard.png) + +## Weekly readiness + +```vega-lite +{ + "data": { "values": [ + { "day": "Mon", "ready": 61 }, + { "day": "Tue", "ready": 68 }, + { "day": "Wed", "ready": 76 }, + { "day": "Thu", "ready": 84 }, + { "day": "Fri", "ready": 92 } + ]}, + "mark": { "type": "line", "point": true, "color": "#6d6bd6" }, + "encoding": { + "x": { "field": "day", "type": "ordinal", "title": null }, + "y": { "field": "ready", "type": "quantitative", "title": "readiness %", "scale": { "domain": [0, 100] } } + } +} +``` + +```table +{ + "caption": "Portable gate checks", + "columns": [ + { "key": "surface", "label": "Surface" }, + { "key": "result", "label": "Result" }, + { "key": "bytes", "label": "Bytes", "type": "num" } + ], + "rows": [ + { "surface": "Local image", "result": "embedded", "bytes": 1 }, + { "surface": "Chart runtime", "result": "conditional", "bytes": 2 }, + { "surface": "Viewer requests", "result": "zero", "bytes": 0 } + ] +} +``` + +## Release decision + +```decisions +{ + "title": "Portable candidate", + "questions": [ + { + "id": "candidate", + "question": "Does this candidate preserve the offline contract?", + "options": [ + { "id": "yes", "label": "Yes", "note": "Record the browser evidence" }, + { "id": "no", "label": "No", "note": "Keep the gate failed" } + ] + } + ] +} +``` diff --git a/scripts/portable-browser-evidence.ts b/scripts/portable-browser-evidence.ts new file mode 100644 index 0000000..fd7d590 --- /dev/null +++ b/scripts/portable-browser-evidence.ts @@ -0,0 +1,122 @@ +import { writeFile } from "node:fs/promises"; + +interface WebDriverResponse { + value: T; +} + +const endpoint = process.argv[2] ?? "http://127.0.0.1:4444"; +const pageUrl = process.argv[3]; +const screenshotPath = process.argv[4]; +const reportPath = process.argv[5]; +if (!pageUrl || !screenshotPath || !reportPath) { + throw new Error("usage: portable-browser-evidence "); +} + +async function request(method: string, path: string, body?: unknown): Promise { + const response = await fetch(`${endpoint}${path}`, { + method, + headers: body === undefined ? undefined : { "content-type": "application/json" }, + body: body === undefined ? undefined : JSON.stringify(body), + }); + const text = await response.text(); + if (!response.ok) throw new Error(`WebDriver ${method} ${path} failed (${response.status}): ${text.slice(0, 1000)}`); + return (JSON.parse(text) as WebDriverResponse).value; +} + +const session = await request<{ sessionId: string }>("POST", "/session", { + capabilities: { + alwaysMatch: { + browserName: "chrome", + "goog:loggingPrefs": { browser: "ALL", performance: "ALL" }, + "goog:chromeOptions": { + args: ["--headless=new", "--no-sandbox", "--disable-dev-shm-usage", "--allow-file-access-from-files"], + }, + }, + }, +}); +const sessionId = session.sessionId; +const route = `/session/${sessionId}`; + +try { + await request("POST", `${route}/window/rect`, { width: 1440, height: 1600, x: 0, y: 0 }); + await request("POST", `${route}/goog/cdp/execute`, { + cmd: "Network.enable", + params: {}, + }); + await request("POST", `${route}/goog/cdp/execute`, { + cmd: "Network.emulateNetworkConditions", + params: { offline: true, latency: 0, downloadThroughput: 0, uploadThroughput: 0 }, + }); + const startedAt = Date.now(); + await request("POST", `${route}/url`, { url: pageUrl }); + let ready = false; + for (let attempt = 0; attempt < 100; attempt++) { + ready = await request("POST", `${route}/execute/sync`, { + script: "return document.readyState === 'complete' && !!document.querySelector('.chart svg, .chart canvas');", + args: [], + }); + if (ready) break; + await new Promise((resolve) => setTimeout(resolve, 50)); + } + const usefulContentMs = Date.now() - startedAt; + await request("POST", `${route}/execute/sync`, { + script: "document.querySelector('.decision-opt').focus(); return true;", + args: [], + }); + await request("POST", `${route}/actions`, { + actions: [{ + type: "key", + id: "keyboard", + actions: [ + { type: "keyDown", value: "\uE007" }, + { type: "keyUp", value: "\uE007" }, + ], + }], + }); + const observations = await request>("POST", `${route}/execute/sync`, { + script: ` + const image = document.querySelector('img[data-asset-sha256]'); + const button = document.querySelector('.decision-opt'); + return { + readyState: document.readyState, + imageComplete: !!image && image.complete, + imageNaturalWidth: image ? image.naturalWidth : 0, + imageSource: image ? image.getAttribute('src').slice(0, 32) : null, + imageHash: image ? image.dataset.assetSha256 : null, + chartVisuals: document.querySelectorAll('.chart svg, .chart canvas').length, + tables: document.querySelectorAll('table').length, + tableCaptions: Array.from(document.querySelectorAll('table caption')).map((node) => node.textContent), + decisionButtons: document.querySelectorAll('.decision-opt').length, + keyboardSelected: !!button && button.classList.contains('selected'), + activeElement: document.activeElement ? document.activeElement.className : null, + csp: document.querySelector('meta[http-equiv="Content-Security-Policy"]')?.getAttribute('content'), + pageBytes: new TextEncoder().encode(document.documentElement.outerHTML).length, + }; + `, + args: [], + }); + const browserLogs = await request>("POST", `${route}/log`, { type: "browser" }); + const performanceLogs = await request>("POST", `${route}/log`, { type: "performance" }); + const rawRequestUrls = performanceLogs.flatMap((entry) => { + const parsed = JSON.parse(entry.message) as { message?: { method?: string; params?: { request?: { url?: string } } } }; + const url = parsed.message?.method === "Network.requestWillBeSent" ? parsed.message.params?.request?.url : undefined; + return url === undefined ? [] : [url]; + }); + const requestUrls = rawRequestUrls.map((url) => url.startsWith("data:") ? `${url.slice(0, url.indexOf(",") + 1)}[embedded]` : url); + const screenshot = await request("GET", `${route}/screenshot`); + await writeFile(screenshotPath, Buffer.from(screenshot, "base64")); + const report = { + capturedAt: new Date().toISOString(), + browser: "Chromium 151 via selenium/standalone-chromium", + offlineEmulation: true, + usefulContentMs, + observations, + browserLogs, + requestUrls, + networkRequestUrls: rawRequestUrls.filter((url) => /^https?:/i.test(url)), + }; + await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8"); + console.log(JSON.stringify(report, null, 2)); +} finally { + await request("DELETE", route); +} diff --git a/skills/artifact-pages/SKILL.md b/skills/artifact-pages/SKILL.md index c3dc8d5..843022e 100644 --- a/skills/artifact-pages/SKILL.md +++ b/skills/artifact-pages/SKILL.md @@ -54,6 +54,8 @@ an over-styled page is worse than a plain one. 3. Every chart's title states the finding, not the axes 4. Components carry `tone` where attention matters 5. No error boxes survive to publish — fix and re-render first +6. Every local Markdown image has meaningful alt text; use `![](path "decorative")` only when + the image genuinely adds no information ## Naming (adapted from Claude Code's artifact-design skill) @@ -94,3 +96,6 @@ Grown from observed failures; add new ones as they bite: and republish rather than working around the box. - `serve`-mode extras (live reload, decisions persistence, comments) exist only on served pages; a `file://` artifact keeps its strict CSP and localStorage-only state. +- Markdown image paths are worktree-root-relative. The publisher embeds allowlisted local + bytes and refuses URLs, absolute/traversal paths, symlinks, missing alt text, and unsupported + or oversized content before asking permission. diff --git a/specs/changes/portable-asset-pipeline/change.json b/specs/changes/portable-asset-pipeline/change.json index 60508ee..f12f548 100644 --- a/specs/changes/portable-asset-pipeline/change.json +++ b/specs/changes/portable-asset-pipeline/change.json @@ -3,10 +3,10 @@ "id": "portable-asset-pipeline", "title": "Add a contained offline asset pipeline", "lane": "high-risk", - "status": "approved", + "status": "implementing", "affectedRequirements": ["RENDER-04", "RENDER-05", "SEC-02"], "currentSpecs": ["specs/current/portable-renderer.spec.md"], - "currentSpecsUpdated": false, + "currentSpecsUpdated": true, "approval": { "by": "aaron.zeng", "at": "2026-08-17T15:02:07Z" diff --git a/specs/changes/portable-asset-pipeline/evidence.md b/specs/changes/portable-asset-pipeline/evidence.md index c10b0ca..a2461fb 100644 --- a/specs/changes/portable-asset-pipeline/evidence.md +++ b/specs/changes/portable-asset-pipeline/evidence.md @@ -2,18 +2,22 @@ ## Requirement: RENDER-04 - Validation: Phase 2 requires contained assets and a self-contained offline mixed-content page. -- Verification: planned filesystem, MIME, SVG, offline-browser, and hostile-reference tests. -- Result: pending implementation and browser evidence. -- Evidence: [@manual](docs/roadmap.md) +- Verification: `test/assets.test.ts` covers filesystem, MIME, SVG, alt, refusal/no-write, + and expansion behavior; the mixed fixture was exercised offline in real Chromium 151. +- Result: image/SVG behavior passed; WOFF/WOFF2 loading awaits explicit approval for the + narrow `font-src data:` CSP directive and is not yet claimed. +- Evidence: [@test](test/assets.test.ts) [@manual](docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md) ## Requirement: RENDER-05 - Validation: final output must retain the existing 15 MiB safety boundary after expansion. -- Verification: planned exact source/encoded/footer/final boundary tests and byte reports. -- Result: pending implementation. -- Evidence: [@manual](docs/product-spec.md) +- Verification: exact source, count, per-file, aggregate, encoded-contribution, rendered, and + existing footer-expanded publication boundaries run in the automated suite. +- Result: passed; the offline mixed fixture rendered to 740,456 bytes below the 15 MiB cap. +- Evidence: [@test](test/assets.test.ts) [@test](test/gallery.test.ts) [@manual](docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md) ## Requirement: SEC-02 - Validation: assets add an untrusted filesystem and active-content boundary. -- Verification: planned traversal, symlink race, special-file, content mismatch, and resource tests. -- Result: pending implementation and model evidence. -- Evidence: [@manual](docs/threat-model.md) +- Verification: traversal, encoded separator, every-symlink, non-regular, content mismatch, + changed-descriptor, bounded diagnostic, and exhaustive modeled authority cases. +- Result: passed; refused plugin inputs made no permission request and no publication write. +- Evidence: [@test](test/assets.test.ts) [@test](test/model/asset-pipeline-model.ts) diff --git a/specs/changes/portable-asset-pipeline/tasks.md b/specs/changes/portable-asset-pipeline/tasks.md index 88fb92f..8ad5130 100644 --- a/specs/changes/portable-asset-pipeline/tasks.md +++ b/specs/changes/portable-asset-pipeline/tasks.md @@ -1,8 +1,8 @@ # Tasks: Add a contained offline asset pipeline -- [ ] Confirm proposal validation and human approval. -- [ ] Add the typed declaration/parser, bounded resolver, MIME/active-content validators, and exact byte accounting. -- [ ] Integrate asset preflight and expansion before final scan/cap/lifecycle publication. -- [ ] Add property, traversal/symlink/special-file, boundary, offline-browser, and mixed-page tests. -- [ ] Retain final request/console/byte evidence and update `specs/current/portable-renderer.spec.md`. +- [x] Confirm proposal validation and human approval. +- [x] Add the typed declaration/parser, bounded resolver, MIME/active-content validators, and exact byte accounting. +- [ ] Integrate asset preflight and expansion before final scan/cap/lifecycle publication (font loading awaits the explicit CSP directive decision). +- [x] Add property, traversal/symlink/special-file, boundary, offline-browser, and mixed-page tests. +- [x] Retain final request/console/byte evidence and update `specs/current/portable-renderer.spec.md`. - [ ] Run repository validation and archive the packet. diff --git a/specs/current/portable-renderer.spec.md b/specs/current/portable-renderer.spec.md new file mode 100644 index 0000000..5a2c4d2 --- /dev/null +++ b/specs/current/portable-renderer.spec.md @@ -0,0 +1,46 @@ +# Portable renderer correctness + +## Current behavior + +- `RENDER-04`: Markdown images published through the CLI or plugin resolve only from the + explicit project worktree. PNG, JPEG, GIF, WebP, and a conservatively reconstructed SVG + subset become hashed data URIs. External, absolute, traversal, encoded-separator, missing, + symlinked, non-regular, mislabeled, active, changed-during-read, or unlabelled image inputs + fail before the permission prompt or any publication write. An exact image title of + `decorative` explicitly selects empty-alt presentation semantics. +- `RENDER-04`: optional frontmatter `font:` declarations accept contained WOFF/WOFF2 bytes + under the same resolver and generate a fixed `@font-face`; the required narrow CSP font + directive remains pending explicit approval, so font loading is not yet claimed. +- `RENDER-05`: Markdown source, declaration count, each source asset, aggregate decoded asset + bytes, encoded contributions, rendered HTML, and footer-expanded publication bytes are + bounded. The final default limit remains 15 MiB and is enforced before lifecycle commit. +- `SEC-02`: resolution is independent of process cwd after the caller supplies its worktree + root. It checks every path segment, opens regular files without following the final symlink, + compares descriptor identity across a bounded read, repeats realpath containment, detects + MIME from bytes, and never fetches. + +## Limits + +| Boundary | Default | +|---|---:| +| Markdown source | 1 MiB | +| Declared assets | 64 | +| One decoded asset | 4 MiB | +| All decoded assets | 10 MiB | +| Final footer-expanded page | 15 MiB | + +Exact limits are accepted; the next byte or declaration is refused. SVG accepts only a +generated allowlist of static geometry/text elements and attributes. It rejects entities, +processing instructions, external references, style, event handlers, active elements, and +malformed or unknown markup. + +## Evidence boundary + +- `test/assets.test.ts` and `test/model/asset-pipeline-model.ts` cover the resolver, exact + accounting, mutation, refusal/no-write, and no-view-time-request properties. +- `examples/patterns/portable-mixed.md` plus + `docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md` retain the real Chromium + offline mixed-page observation. +- This evidence is one Linux/Chromium observation. It does not certify a browser, OS, mobile, + assistive-technology, or font-loading cell, and it does not complete the remaining Goal 3 + declarative, token, accessibility, or performance packets. diff --git a/src/assets.ts b/src/assets.ts new file mode 100644 index 0000000..6c33a25 --- /dev/null +++ b/src/assets.ts @@ -0,0 +1,405 @@ +import { constants as fsConstants } from "node:fs"; +import { open, lstat, realpath } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import { isAbsolute, relative, resolve, sep } from "node:path"; +import MarkdownIt from "markdown-it"; + +export const DEFAULT_MAX_ASSET_COUNT = 64; +export const DEFAULT_MAX_ASSET_BYTES = 4 * 1024 * 1024; +export const DEFAULT_MAX_ASSET_TOTAL_BYTES = 10 * 1024 * 1024; +export const DEFAULT_MAX_MARKDOWN_BYTES = 1024 * 1024; + +export type AssetKind = "image" | "font"; + +export interface AssetLimits { + maxAssetCount?: number; + maxAssetBytes?: number; + maxTotalBytes?: number; + maxMarkdownBytes?: number; +} + +export interface AssetResolutionHooks { + /** Test/fault-injection seam invoked after the descriptor identity is captured. */ + afterOpen?: (resolvedPath: string) => Promise; +} + +export interface PortableAsset { + source: string; + relativePath: string; + kind: AssetKind; + mime: string; + bytes: number; + encodedBytes: number; + sha256: string; + dataUri: string; + alt: string | undefined; + decorative: boolean; +} + +export interface PortableAssets { + bySource: ReadonlyMap; + font: PortableAsset | undefined; + sourceBytes: number; + assetBytes: number; + encodedBytes: number; +} + +export type AssetErrorCode = + | "source-too-large" + | "too-many-assets" + | "invalid-path" + | "external-asset" + | "missing-asset" + | "unsafe-path" + | "not-regular" + | "unsupported-type" + | "type-mismatch" + | "active-content" + | "asset-too-large" + | "assets-too-large" + | "changed-during-read" + | "missing-alt"; + +function bounded(value: string): string { + return value.length <= 200 ? value : `${value.slice(0, 197)}...`; +} + +export class AssetPreflightError extends Error { + readonly code: AssetErrorCode; + readonly assetPath: string | undefined; + readonly nextAction: string; + + constructor(code: AssetErrorCode, message: string, assetPath: string | undefined, nextAction: string) { + super(`${message}${assetPath === undefined ? "" : `: ${bounded(assetPath)}`}. ${nextAction}`); + this.name = "AssetPreflightError"; + this.code = code; + this.assetPath = assetPath === undefined ? undefined : bounded(assetPath); + this.nextAction = nextAction; + } +} + +interface AssetDeclaration { + source: string; + kind: AssetKind; + alt?: string; + decorative?: boolean; +} + +function frontmatterFont(source: string): string | undefined { + const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/); + if (!match) return undefined; + for (const line of match[1].split(/\r?\n/)) { + const field = line.match(/^font\s*:\s*(.*?)\s*$/i); + if (field?.[1]) return field[1]; + } + return undefined; +} + +function imageDeclarations(source: string): AssetDeclaration[] { + const md = new MarkdownIt({ html: false, linkify: true }); + const declarations: AssetDeclaration[] = []; + const visit = (tokens: readonly { type: string; children?: unknown; attrGet(name: string): string | null; content: string }[]): void => { + for (const token of tokens) { + if (token.type === "image") { + const assetSource = token.attrGet("src"); + if (assetSource !== null) { + const title = token.attrGet("title"); + declarations.push({ + source: assetSource, + kind: "image", + alt: token.content.trim(), + decorative: title?.trim().toLowerCase() === "decorative", + }); + } + } + if (Array.isArray(token.children)) { + visit(token.children as readonly { type: string; children?: unknown; attrGet(name: string): string | null; content: string }[]); + } + } + }; + visit(md.parse(source, {})); + return declarations; +} + +function declarations(source: string): AssetDeclaration[] { + const result = imageDeclarations(source); + const font = frontmatterFont(source); + if (font !== undefined) result.push({ source: font, kind: "font" }); + return result; +} + +function safeRelativePath(source: string): string { + if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(source)) { + throw new AssetPreflightError("external-asset", "external assets are not imported", source, "use a contained worktree-local file"); + } + if (source.includes("\0") || source.includes("\\") || /%2f|%5c/i.test(source)) { + throw new AssetPreflightError("invalid-path", "asset path contains an encoded or platform-dependent separator", source, "use forward-slash relative path segments"); + } + if (source.includes("?") || source.includes("#")) { + throw new AssetPreflightError("invalid-path", "asset paths may not contain URL query or fragment syntax", source, "use the plain worktree-relative file path"); + } + let decoded: string; + try { + decoded = decodeURIComponent(source); + } catch { + throw new AssetPreflightError("invalid-path", "asset path has malformed percent encoding", source, "use a valid relative path"); + } + if (isAbsolute(decoded) || /^[a-z]:/i.test(decoded)) { + throw new AssetPreflightError("invalid-path", "absolute asset paths are not allowed", source, "use a path relative to the worktree root"); + } + const segments = decoded.replace(/^\.\//, "").split("/"); + if (segments.length === 0 || segments.some((segment) => segment === "" || segment === "." || segment === "..")) { + throw new AssetPreflightError("invalid-path", "asset path contains an empty or traversal segment", source, "use a direct relative path beneath the worktree root"); + } + return segments.join(sep); +} + +function contained(root: string, candidate: string): boolean { + const child = relative(root, candidate); + return child !== "" && !child.startsWith(`..${sep}`) && child !== ".." && !isAbsolute(child); +} + +async function rejectSymlinkSegments(root: string, path: string, source: string): Promise { + let cursor = root; + const segments = relative(root, path).split(sep); + for (const [index, segment] of segments.entries()) { + cursor = resolve(cursor, segment); + let info; + try { + info = await lstat(cursor); + } catch (error) { + if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") { + throw new AssetPreflightError("missing-asset", "asset does not exist", source, "create the file or correct the declaration"); + } + throw error; + } + if (info.isSymbolicLink()) { + throw new AssetPreflightError("unsafe-path", "asset paths may not contain symbolic links", source, "use a regular file stored directly beneath the worktree root"); + } + if (index === segments.length - 1 && !info.isFile()) { + throw new AssetPreflightError("not-regular", "asset is not a regular file", source, "use a regular file"); + } + } +} + +function declaredMime(path: string, kind: AssetKind): string { + const lower = path.toLowerCase(); + const entries: ReadonlyArray = [ + [".png", "image/png", "image"], + [".jpg", "image/jpeg", "image"], + [".jpeg", "image/jpeg", "image"], + [".gif", "image/gif", "image"], + [".webp", "image/webp", "image"], + [".svg", "image/svg+xml", "image"], + [".woff", "font/woff", "font"], + [".woff2", "font/woff2", "font"], + ]; + const match = entries.find(([extension]) => lower.endsWith(extension)); + if (!match || match[2] !== kind) { + throw new AssetPreflightError("unsupported-type", `unsupported ${kind} asset type`, path, kind === "font" ? "use a WOFF or WOFF2 file" : "use PNG, JPEG, GIF, WebP, or a constrained SVG"); + } + return match[1]; +} + +function detectedMime(bytes: Buffer): string | undefined { + if (bytes.length >= 8 && bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) return "image/png"; + if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return "image/jpeg"; + if (bytes.length >= 6 && (bytes.subarray(0, 6).toString("ascii") === "GIF87a" || bytes.subarray(0, 6).toString("ascii") === "GIF89a")) return "image/gif"; + if (bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP") return "image/webp"; + if (bytes.length >= 4 && bytes.subarray(0, 4).toString("ascii") === "wOFF") return "font/woff"; + if (bytes.length >= 4 && bytes.subarray(0, 4).toString("ascii") === "wOF2") return "font/woff2"; + try { + const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes).trim(); + if (text.startsWith("")) return "image/svg+xml"; + } catch { + return undefined; + } + return undefined; +} + +const SVG_ELEMENTS = new Set(["svg", "g", "path", "rect", "circle", "ellipse", "line", "polyline", "polygon", "title", "desc", "text", "tspan"]); +const SVG_ATTRIBUTES = new Set(["xmlns", "viewBox", "width", "height", "role", "aria-label", "fill", "stroke", "stroke-width", "opacity", "transform", "d", "x", "y", "x1", "x2", "y1", "y2", "cx", "cy", "r", "rx", "ry", "points", "text-anchor", "font-size", "font-family", "font-weight"]); + +function escapeXml(value: string): string { + return value.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +} + +function safeSvgAttribute(name: string, value: string): boolean { + if (name === "xmlns") return value === "http://www.w3.org/2000/svg"; + if (name === "role") return value === "img" || value === "presentation"; + if (name === "aria-label" || name === "font-family") return /^[\p{L}\p{N} _.,-]{1,120}$/u.test(value); + if (name === "fill" || name === "stroke") return value === "none" || value === "currentColor" || /^#[0-9a-f]{3,8}$/i.test(value) || /^[a-z]{1,24}$/i.test(value); + if (name === "d") return /^[MmZzLlHhVvCcSsQqTtAaEe0-9.,+\-\s]+$/.test(value); + if (name === "transform") return /^(?:(?:matrix|translate|scale|rotate|skewX|skewY)\([0-9.,+\-\s]+\)\s*)+$/.test(value); + if (name === "viewBox" || name === "points") return /^[0-9.,+\-\s]+$/.test(value); + if (name === "text-anchor") return value === "start" || value === "middle" || value === "end"; + return /^[0-9.+\-%]{1,32}$/.test(value) || /^[a-z]{1,16}$/i.test(value); +} + +export function sanitizeSvg(bytes: Buffer, source: string): Buffer { + let input: string; + try { + input = new TextDecoder("utf-8", { fatal: true }).decode(bytes).trim(); + } catch { + throw new AssetPreflightError("active-content", "SVG is not valid UTF-8", source, "export a static UTF-8 SVG using the supported subset"); + } + if (/]*>/g; + const stack: string[] = []; + const output: string[] = []; + let cursor = 0; + for (const match of input.matchAll(tag)) { + const index = match.index; + const text = input.slice(cursor, index); + if (text.includes("<") || text.includes(">")) throw new AssetPreflightError("active-content", "SVG markup is malformed", source, "export a static SVG using the supported subset"); + if (text !== "") output.push(escapeXml(text)); + const raw = match[0]; + const closing = raw.startsWith(""); + const nameMatch = raw.match(/^<\/?([A-Za-z][A-Za-z0-9-]*)/); + const name = nameMatch?.[1]; + if (name === undefined || !SVG_ELEMENTS.has(name)) throw new AssetPreflightError("active-content", "SVG contains an unsupported element", source, "export a static SVG using supported geometry and text elements"); + if (closing) { + if (raw !== `` || stack.pop() !== name) throw new AssetPreflightError("active-content", "SVG element nesting is invalid", source, "export a well-formed static SVG"); + output.push(raw); + } else { + const attributesText = raw.slice(name.length + 1, raw.length - (selfClosing ? 2 : 1)); + const attributes: string[] = []; + let attrCursor = 0; + const attr = /\s+([A-Za-z][A-Za-z0-9:-]*)\s*=\s*("([^"]*)"|'([^']*)')/gy; + while (attrCursor < attributesText.length) { + attr.lastIndex = attrCursor; + const attribute = attr.exec(attributesText); + if (!attribute) { + if (attributesText.slice(attrCursor).trim() === "") break; + throw new AssetPreflightError("active-content", "SVG contains malformed attributes", source, "export a static SVG with quoted supported attributes"); + } + const attributeName = attribute[1]; + const value = attribute[3] ?? attribute[4] ?? ""; + if (!SVG_ATTRIBUTES.has(attributeName) || !safeSvgAttribute(attributeName, value)) throw new AssetPreflightError("active-content", `SVG attribute ${attributeName} is unsupported or unsafe`, source, "remove active styling and external references"); + attributes.push(` ${attributeName}="${escapeXml(value)}"`); + attrCursor = attr.lastIndex; + } + output.push(`<${name}${attributes.join("")}${selfClosing ? "/>" : ">"}`); + if (!selfClosing) stack.push(name); + } + cursor = index + raw.length; + } + if (cursor !== input.length) { + const tail = input.slice(cursor); + if (tail.includes("<") || tail.includes(">")) throw new AssetPreflightError("active-content", "SVG markup is malformed", source, "export a well-formed static SVG"); + output.push(escapeXml(tail)); + } + if (stack.length !== 0 || output.length === 0 || !output[0].startsWith(" { + const handle = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW); + try { + const before = await handle.stat(); + if (!before.isFile()) throw new AssetPreflightError("not-regular", "asset is not a regular file", source, "use a regular file"); + if (before.size > maxBytes) throw new AssetPreflightError("asset-too-large", `asset is ${before.size} bytes; limit is ${maxBytes}`, source, "reduce or optimize the asset"); + await hooks.afterOpen?.(path); + const buffer = Buffer.alloc(before.size); + let offset = 0; + while (offset < buffer.length) { + const result = await handle.read(buffer, offset, buffer.length - offset, offset); + if (result.bytesRead === 0) break; + offset += result.bytesRead; + } + const extra = Buffer.alloc(1); + const extraRead = await handle.read(extra, 0, 1, offset); + const after = await handle.stat(); + const identity = `${before.dev}:${before.ino}:${before.size}:${before.mtimeMs}`; + const afterIdentity = `${after.dev}:${after.ino}:${after.size}:${after.mtimeMs}`; + if (offset !== before.size || extraRead.bytesRead !== 0 || identity !== afterIdentity) throw new AssetPreflightError("changed-during-read", "asset changed while it was being read", source, "retry after writers have finished"); + return { bytes: buffer, identity }; + } finally { + await handle.close(); + } +} + +async function resolveAsset(root: string, declaration: AssetDeclaration, maxBytes: number, hooks: AssetResolutionHooks): Promise { + if (declaration.kind === "image" && declaration.alt?.trim() === "" && declaration.decorative !== true) { + throw new AssetPreflightError("missing-alt", "image is missing meaningful alt text", declaration.source, "add alt text, or use the exact title \"decorative\" for a decorative image"); + } + const relativePath = safeRelativePath(declaration.source); + const candidate = resolve(root, relativePath); + if (!contained(root, candidate)) throw new AssetPreflightError("unsafe-path", "asset resolves outside the worktree root", declaration.source, "use a contained relative path"); + await rejectSymlinkSegments(root, candidate, declaration.source); + const resolvedBefore = await realpath(candidate); + if (!contained(root, resolvedBefore)) throw new AssetPreflightError("unsafe-path", "asset realpath escapes the worktree root", declaration.source, "store the file directly beneath the worktree root"); + const declared = declaredMime(relativePath, declaration.kind); + const read = await boundedRead(resolvedBefore, declaration.source, maxBytes, hooks); + const resolvedAfter = await realpath(candidate); + if (resolvedAfter !== resolvedBefore) throw new AssetPreflightError("changed-during-read", "asset path changed while it was being read", declaration.source, "retry after filesystem changes have finished"); + const detected = detectedMime(read.bytes); + if (detected !== declared) throw new AssetPreflightError("type-mismatch", `asset bytes do not match declared ${declared} type`, declaration.source, "correct the extension or provide an allowlisted file"); + const safeBytes = declared === "image/svg+xml" ? sanitizeSvg(read.bytes, declaration.source) : read.bytes; + const encoded = safeBytes.toString("base64"); + const dataUri = `data:${declared};base64,${encoded}`; + return { + source: declaration.source, + relativePath: relativePath.split(sep).join("/"), + kind: declaration.kind, + mime: declared, + bytes: safeBytes.length, + encodedBytes: Buffer.byteLength(dataUri, "utf8"), + sha256: createHash("sha256").update(safeBytes).digest("hex"), + dataUri, + alt: declaration.alt, + decorative: declaration.decorative === true, + }; +} + +export async function resolvePortableAssets( + markdown: string, + worktreeRoot: string, + limits: AssetLimits = {}, + hooks: AssetResolutionHooks = {}, +): Promise { + const sourceBytes = Buffer.byteLength(markdown, "utf8"); + const maxMarkdownBytes = limits.maxMarkdownBytes ?? DEFAULT_MAX_MARKDOWN_BYTES; + if (sourceBytes > maxMarkdownBytes) throw new AssetPreflightError("source-too-large", `Markdown source is ${sourceBytes} bytes; limit is ${maxMarkdownBytes}`, undefined, "reduce the authoring source"); + const requestedRoot = resolve(worktreeRoot); + const requestedRootInfo = await lstat(requestedRoot); + if (!requestedRootInfo.isDirectory() || requestedRootInfo.isSymbolicLink()) throw new AssetPreflightError("unsafe-path", "worktree root is not a real directory", worktreeRoot, "select a real worktree directory"); + const root = await realpath(requestedRoot); + const rootInfo = await lstat(root); + if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new AssetPreflightError("unsafe-path", "worktree root is not a real directory", worktreeRoot, "select a real worktree directory"); + const parsed = declarations(markdown); + const maxAssetCount = limits.maxAssetCount ?? DEFAULT_MAX_ASSET_COUNT; + if (parsed.length > maxAssetCount) throw new AssetPreflightError("too-many-assets", `document declares ${parsed.length} assets; limit is ${maxAssetCount}`, undefined, "reduce the number of declared assets"); + for (const declaration of parsed) { + if (declaration.kind === "image" && declaration.alt?.trim() === "" && declaration.decorative !== true) { + throw new AssetPreflightError("missing-alt", "image is missing meaningful alt text", declaration.source, "add alt text, or use the exact title \"decorative\" for a decorative image"); + } + } + const unique = new Map(); + for (const declaration of parsed) { + const existing = unique.get(`${declaration.kind}:${declaration.source}`); + if (existing?.alt && declaration.alt && existing.alt !== declaration.alt) { + // The bytes are shared, but each rendered token retains its own Markdown alt text. + continue; + } + unique.set(`${declaration.kind}:${declaration.source}`, declaration); + } + const bySource = new Map(); + let font: PortableAsset | undefined; + let assetBytes = 0; + let encodedBytes = 0; + const maxAssetBytes = limits.maxAssetBytes ?? DEFAULT_MAX_ASSET_BYTES; + const maxTotalBytes = limits.maxTotalBytes ?? DEFAULT_MAX_ASSET_TOTAL_BYTES; + for (const declaration of unique.values()) { + const asset = await resolveAsset(root, declaration, maxAssetBytes, hooks); + assetBytes += asset.bytes; + encodedBytes += asset.encodedBytes; + if (assetBytes > maxTotalBytes) throw new AssetPreflightError("assets-too-large", `asset bytes total ${assetBytes}; limit is ${maxTotalBytes}`, declaration.source, "reduce or remove assets"); + if (declaration.kind === "font") font = asset; + else bySource.set(declaration.source, asset); + } + return { bySource, font, sourceBytes, assetBytes, encodedBytes }; +} diff --git a/src/cli.ts b/src/cli.ts index c609de7..dbd1dd8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node import { lstat, mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; -import { renderArtifact, renderRawHtml } from "./render.ts"; +import { renderPortableArtifact, renderRawHtml } from "./render.ts"; import { FilePublisher, slugify } from "./publisher.ts"; import { GitHubPagesPublisher } from "./github-pages.ts"; import { CloudflarePublisher } from "./cloudflare-publisher.ts"; @@ -107,8 +107,15 @@ async function renderCommand(args: string[]): Promise { process.exit(1); } const rendered = - format === "html" ? renderRawHtml(markdown, title ? { title } : {}) : renderArtifact(markdown); + format === "html" ? renderRawHtml(markdown, title ? { title } : {}) : await renderPortableArtifact(markdown, process.cwd()); const finalTitle = title ?? rendered.meta.title ?? "Artifact"; + const finalFindings = scanSensitive(rendered.html); + if (finalFindings.length > 0 && !force) { + console.error( + `publish blocked: final portable bytes contain credential-looking strings: ${formatFindings(finalFindings)}. Re-run with --force to publish anyway.`, + ); + process.exit(1); + } let outPath: string; if (out) { diff --git a/src/markdown.ts b/src/markdown.ts index 2a327fd..9475dfa 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -1,5 +1,6 @@ import MarkdownIt from "markdown-it"; import { COMPONENT_KINDS, type ComponentKind } from "./components.ts"; +import type { PortableAsset } from "./assets.ts"; export interface Frontmatter { title?: string; @@ -7,6 +8,7 @@ export interface Frontmatter { description?: string; theme?: string; source?: string; + font?: string; } export type ChartKind = "vega-lite" | "vega" | "echarts"; @@ -30,6 +32,10 @@ export interface ParsedDocument { warnings: string[]; } +export interface ParseDocumentOptions { + assets?: ReadonlyMap; +} + const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/; const KEY_VALUE_RE = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/; @@ -51,12 +57,13 @@ function parseFrontmatter(source: string, warnings: string[]): { meta: Frontmatt else if (key === "description") meta.description = value; else if (key === "theme") meta.theme = value; else if (key === "source") meta.source = value; + else if (key === "font") meta.font = value; else warnings.push(`frontmatter key ignored: ${key}`); } return { meta, body: source.slice(match[0].length) }; } -export function parseDocument(source: string): ParsedDocument { +export function parseDocument(source: string, options: ParseDocumentOptions = {}): ParsedDocument { const warnings: string[] = []; const { meta, body } = parseFrontmatter(source, warnings); const charts: ChartSpec[] = []; @@ -65,6 +72,21 @@ export function parseDocument(source: string): ParsedDocument { const md = new MarkdownIt({ html: false, linkify: true }); const escapeHtml = md.utils.escapeHtml; + md.renderer.rules.image = (tokens, idx, renderOptions, env, renderer) => { + const token = tokens[idx]; + const assetSource = token.attrGet("src") ?? ""; + const asset = options.assets?.get(assetSource); + if (asset === undefined) { + return `Asset preflight required: ${escapeHtml(assetSource)}`; + } + const alt = renderer.renderInlineAsText(token.children ?? [], renderOptions, env); + const decorative = token.attrGet("title")?.trim().toLowerCase() === "decorative"; + const title = token.attrGet("title"); + const titleAttribute = title !== null && !decorative ? ` title="${escapeHtml(title)}"` : ""; + const decorativeAttributes = decorative ? ' role="presentation"' : ""; + return `${decorative ? `; + }; + md.renderer.rules.fence = (tokens, idx) => { const token = tokens[idx]; const info = token.info.trim().split(/\s+/)[0] ?? ""; diff --git a/src/plugin.ts b/src/plugin.ts index 45e2c1a..bda1550 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -5,10 +5,12 @@ import { lstat, mkdir, readFile, writeFile } from "node:fs/promises"; import { tool, type Hooks, type Plugin } from "@opencode-ai/plugin"; import { ArtifactTooLargeError, - renderArtifact, + renderPortableArtifact, renderRawHtml, type RenderedArtifact, } from "./render.ts"; +import { AssetPreflightError } from "./assets.ts"; +import { parseDocument } from "./markdown.ts"; import { FilePublisher, slugify, StaleArtifactError } from "./publisher.ts"; import { GitHubPagesPublisher } from "./github-pages.ts"; import { CloudflarePublisher } from "./cloudflare-publisher.ts"; @@ -187,17 +189,21 @@ export const ArtifactsPlugin: Plugin = async (_input, options) => { async execute(args, ctx) { let slug = "artifact"; try { - const rendered: RenderedArtifact = - args.format === "html" - ? renderRawHtml(args.markdown, args.title ? { title: args.title } : {}) - : renderArtifact(args.markdown); - const title = args.title ?? rendered.meta.title ?? "Artifact"; + const parsedTitle = args.format === "html" ? undefined : parseDocument(args.markdown).meta.title; + const title = args.title ?? parsedTitle ?? "Artifact"; slug = slugify(title); - const findings = scanSensitive(`${args.markdown}\n${title}`); if (findings.length > 0 && args.force !== true) { return `Publish blocked: the content contains credential-looking strings: ${formatFindings(findings)}. If these are intentional (e.g. redacted examples), call again with force: true.`; } + const rendered: RenderedArtifact = + args.format === "html" + ? renderRawHtml(args.markdown, args.title ? { title: args.title } : {}) + : await renderPortableArtifact(args.markdown, workRoot(ctx)); + const finalFindings = scanSensitive(rendered.html); + if (finalFindings.length > 0 && args.force !== true) { + return `Publish blocked: the final portable bytes contain credential-looking strings: ${formatFindings(finalFindings)}. If these are intentional, call again with force: true.`; + } await ctx.ask({ permission: "artifact_publish", @@ -281,6 +287,9 @@ export const ArtifactsPlugin: Plugin = async (_input, options) => { if (err instanceof ArtifactTooLargeError) { return `Artifact too large: ${err.message}`; } + if (err instanceof AssetPreflightError) { + return JSON.stringify({ error: err.code, path: err.assetPath, message: err.message, nextAction: err.nextAction }, null, 2); + } if (err instanceof StaleArtifactError) { const currentPath = join(workRoot(ctx), ".opencode", "artifacts", `${slug}.html`); let current = ""; diff --git a/src/render.ts b/src/render.ts index 649a298..bb46987 100644 --- a/src/render.ts +++ b/src/render.ts @@ -3,6 +3,7 @@ import { parseDocument, type ChartSpec, type Frontmatter } from "./markdown.ts"; import { renderComponent } from "./components.ts"; import { runtimeBundle, type RuntimeName } from "./runtime.ts"; import { escapeHtmlText, slugify } from "./text.ts"; +import { resolvePortableAssets, type AssetLimits, type PortableAssets } from "./assets.ts"; export { escapeHtmlText } from "./text.ts"; @@ -22,6 +23,11 @@ export class ArtifactTooLargeError extends Error { export interface RenderOptions { maxBytes?: number; + assets?: PortableAssets; +} + +export interface PortableRenderOptions extends AssetLimits { + maxBytes?: number; } export interface RenderedArtifact { @@ -687,6 +693,7 @@ interface AssembleInput { needsBoot: boolean; needsMermaid: boolean; maxBytes: number; + assetCss?: string; } function assemblePage(input: AssembleInput): string { @@ -716,7 +723,7 @@ function assemblePage(input: AssembleInput): string { ? `` : "", `${escapeHtmlText(input.title)}`, - ``, + ``, "", "", `
${escapeHtmlText(input.icon)}

${escapeHtmlText(input.title)}

`, @@ -746,7 +753,7 @@ function assemblePage(input: AssembleInput): string { } export function renderArtifact(markdown: string, options: RenderOptions = {}): RenderedArtifact { - const doc = parseDocument(markdown); + const doc = parseDocument(markdown, { assets: options.assets?.bySource }); let bodyHtml = doc.bodyHtml; let needsBoot = false; @@ -769,10 +776,22 @@ export function renderArtifact(markdown: string, options: RenderOptions = {}): R needsBoot, needsMermaid, maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES, + assetCss: options.assets?.font === undefined + ? undefined + : `@font-face{font-family:"Artifact Project";src:url(${options.assets.font.dataUri}) format("${options.assets.font.mime === "font/woff2" ? "woff2" : "woff"}");font-display:swap}body{font-family:"Artifact Project",system-ui,-apple-system,"Segoe UI",sans-serif}`, }); return { html, meta: doc.meta, chartCount: doc.charts.length }; } +export async function renderPortableArtifact( + markdown: string, + worktreeRoot: string, + options: PortableRenderOptions = {}, +): Promise { + const assets = await resolvePortableAssets(markdown, worktreeRoot, options); + return renderArtifact(markdown, { maxBytes: options.maxBytes, assets }); +} + export function renderRawHtml( bodyHtml: string, meta: Frontmatter = {}, diff --git a/test/assets.test.ts b/test/assets.test.ts new file mode 100644 index 0000000..b9920d0 --- /dev/null +++ b/test/assets.test.ts @@ -0,0 +1,212 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import type { PluginInput, ToolContext } from "@opencode-ai/plugin"; +import { + AssetPreflightError, + resolvePortableAssets, +} from "../src/assets.ts"; +import { ArtifactTooLargeError, renderArtifact, renderPortableArtifact } from "../src/render.ts"; +import { ArtifactsPlugin } from "../src/plugin.ts"; +import { modelAsset } from "./model/asset-pipeline-model.ts"; + +const PNG = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=", "base64"); + +async function withRoot(run: (root: string) => Promise): Promise { + const root = await mkdtemp(join(tmpdir(), "portable-assets-")); + try { + await run(root); + } finally { + await rm(root, { recursive: true, force: true }); + } +} + +test("contained PNG references become hashed data URIs with exact accounting", async () => { + await withRoot(async (root) => { + await mkdir(join(root, "media")); + await writeFile(join(root, "media", "pixel.png"), PNG); + const markdown = "![A one-pixel sample](media/pixel.png)"; + const assets = await resolvePortableAssets(markdown, root); + const asset = assets.bySource.get("media/pixel.png"); + assert.ok(asset); + assert.equal(asset.bytes, PNG.length); + assert.equal(asset.encodedBytes, Buffer.byteLength(asset.dataUri)); + assert.equal(assets.assetBytes, PNG.length); + assert.equal(assets.encodedBytes, asset.encodedBytes); + assert.match(asset.sha256, /^[a-f0-9]{64}$/); + + const rendered = await renderPortableArtifact(markdown, root); + assert.match(rendered.html, /src="data:image\/png;base64,/); + assert.match(rendered.html, /alt="A one-pixel sample"/); + assert.match(rendered.html, /data-asset-sha256="[a-f0-9]{64}"/); + assert.ok(!rendered.html.includes('src="media/pixel.png"')); + }); +}); + +test("explicit decorative images retain empty alt and presentation semantics", async () => { + await withRoot(async (root) => { + await writeFile(join(root, "pixel.png"), PNG); + const rendered = await renderPortableArtifact('![](pixel.png "decorative")', root); + assert.match(rendered.html, /alt="" role="presentation"/); + await assert.rejects( + resolvePortableAssets("![](pixel.png)", root), + (error: unknown) => error instanceof AssetPreflightError && error.code === "missing-alt", + ); + }); +}); + +test("safe SVG is reconstructed and active SVG is refused", async () => { + await withRoot(async (root) => { + const safe = 'Dot'; + await writeFile(join(root, "safe.svg"), safe); + const assets = await resolvePortableAssets("![Dot](safe.svg)", root); + const decoded = Buffer.from(assets.bySource.get("safe.svg")?.dataUri.split(",")[1] ?? "", "base64").toString("utf8"); + assert.equal(decoded, safe); + + await writeFile(join(root, "active.svg"), ''); + await assert.rejects( + resolvePortableAssets("![Bad](active.svg)", root), + (error: unknown) => error instanceof AssetPreflightError && error.code === "active-content", + ); + }); +}); + +test("preflight rejects external, traversal, encoded-separator, symlink, special, missing, and mislabeled assets", async () => { + await withRoot(async (root) => { + await writeFile(join(root, "target.png"), PNG); + await symlink(join(root, "target.png"), join(root, "linked.png")); + await mkdir(join(root, "directory.png")); + await writeFile(join(root, "fake.png"), "not a png"); + const cases: ReadonlyArray = [ + ["![x](https://example.com/a.png)", "external-asset"], + ["![x](../outside-portable.png)", "invalid-path"], + ["![x](media%2fpixel.png)", "invalid-path"], + ["![x](linked.png)", "unsafe-path"], + ["![x](directory.png)", "not-regular"], + ["![x](missing.png)", "missing-asset"], + ["![x](fake.png)", "type-mismatch"], + ]; + for (const [markdown, code] of cases) { + await assert.rejects( + resolvePortableAssets(markdown, root), + (error: unknown) => error instanceof AssetPreflightError && error.code === code && !error.message.includes("not a png"), + `${markdown} should fail with ${code}`, + ); + } + }); +}); + +test("per-file, aggregate, source, and count boundaries accept the limit and reject the next unit", async () => { + await withRoot(async (root) => { + await writeFile(join(root, "pixel.png"), PNG); + await resolvePortableAssets("![x](pixel.png)", root, { maxAssetBytes: PNG.length, maxTotalBytes: PNG.length, maxMarkdownBytes: 100, maxAssetCount: 1 }); + await assert.rejects(resolvePortableAssets("![x](pixel.png)", root, { maxAssetBytes: PNG.length - 1 }), (error: unknown) => error instanceof AssetPreflightError && error.code === "asset-too-large"); + await assert.rejects(resolvePortableAssets("![x](pixel.png)\n\n![y](pixel.png)", root, { maxAssetCount: 1 }), (error: unknown) => error instanceof AssetPreflightError && error.code === "too-many-assets"); + await assert.rejects(resolvePortableAssets("![x](pixel.png)", root, { maxMarkdownBytes: Buffer.byteLength("![x](pixel.png)") - 1 }), (error: unknown) => error instanceof AssetPreflightError && error.code === "source-too-large"); + await assert.rejects(renderPortableArtifact("![x](pixel.png)", root, { maxBytes: 500 }), ArtifactTooLargeError); + }); +}); + +test("a file mutated after descriptor inspection is discarded", async () => { + await withRoot(async (root) => { + const path = join(root, "pixel.png"); + await writeFile(path, PNG); + await assert.rejects( + resolvePortableAssets("![x](pixel.png)", root, {}, { + afterOpen: async () => { await writeFile(path, Buffer.concat([PNG, Buffer.from([0])])); }, + }), + (error: unknown) => error instanceof AssetPreflightError && error.code === "changed-during-read", + ); + }); +}); + +test("font declarations are typed, hashed, and embedded without trusting the extension alone", async () => { + await withRoot(async (root) => { + const font = Buffer.concat([Buffer.from("wOF2"), Buffer.alloc(32)]); + await writeFile(join(root, "project.woff2"), font); + const markdown = "---\ntitle: Font\nfont: project.woff2\n---\nText"; + const rendered = await renderPortableArtifact(markdown, root); + assert.match(rendered.html, /@font-face/); + assert.match(rendered.html, /data:font\/woff2;base64,/); + await writeFile(join(root, "wrong.woff2"), PNG); + await assert.rejects(resolvePortableAssets("---\nfont: wrong.woff2\n---\nx", root), (error: unknown) => error instanceof AssetPreflightError && error.code === "type-mismatch"); + }); +}); + +test("synchronous rendering never emits an unresolved network or file URL", () => { + const html = renderArtifact("![Remote](https://example.com/a.png)").html; + assert.match(html, /Asset preflight required/); + assert.ok(!html.includes('src="https://example.com/a.png"')); +}); + +test("plugin asset refusal performs no permission request or publication write", async () => { + await withRoot(async (root) => { + const publish = (await ArtifactsPlugin({} as unknown as PluginInput)).tool?.artifact_publish; + assert.ok(publish); + let asked = false; + const context: ToolContext = { + sessionID: "asset-test", + messageID: "asset-test", + agent: "test", + directory: root, + worktree: root, + abort: new AbortController().signal, + metadata: () => {}, + ask: async () => { asked = true; }, + }; + const result = await publish.execute({ markdown: "![Remote](https://example.com/a.png)" }, context); + assert.match(String(result), /external-asset/); + assert.equal(asked, false); + await assert.rejects(readFile(join(root, ".opencode", "artifacts", "manifest.json"))); + }); +}); + +test("plugin publication writes the expanded asset bytes rather than a worktree URL", async () => { + await withRoot(async (root) => { + await writeFile(join(root, "pixel.png"), PNG); + const publish = (await ArtifactsPlugin({} as unknown as PluginInput)).tool?.artifact_publish; + assert.ok(publish); + const context: ToolContext = { + sessionID: "asset-success", + messageID: "asset-success", + agent: "test", + directory: root, + worktree: root, + abort: new AbortController().signal, + metadata: () => {}, + ask: async () => {}, + }; + const result = await publish.execute({ markdown: "---\ntitle: Asset page\n---\n![Pixel](pixel.png)" }, context); + assert.match(String(result), /Artifact published/); + const html = await readFile(join(root, ".opencode", "artifacts", "asset-page.html"), "utf8"); + assert.match(html, /src="data:image\/png;base64,/); + assert.ok(!html.includes('src="pixel.png"')); + }); +}); + +test("bounded model either embeds exact base64 contribution or refuses without returned bytes or requests", () => { + for (let mask = 0; mask < 32; mask++) { + for (const bytes of [0, 1, 2, 3, 4, 15, 16]) { + const result = modelAsset({ + relativeSyntax: (mask & 1) !== 0, + contained: (mask & 2) !== 0, + regular: (mask & 4) !== 0, + stable: (mask & 8) !== 0, + allowlisted: (mask & 16) !== 0, + bytes, + maxBytes: 15, + mimePrefixBytes: 22, + }); + assert.equal(result.viewTimeRequests, 0); + if (mask === 31 && bytes <= 15) { + assert.equal(result.result, "embedded"); + assert.equal(result.sourceBytesReturned, bytes); + assert.equal(result.encodedBytes, 22 + 4 * Math.ceil(bytes / 3)); + } else { + assert.deepEqual(result, { result: "refused", sourceBytesReturned: 0, encodedBytes: 0, viewTimeRequests: 0 }); + } + } + } +}); diff --git a/test/model/asset-pipeline-model.ts b/test/model/asset-pipeline-model.ts new file mode 100644 index 0000000..e7e28b2 --- /dev/null +++ b/test/model/asset-pipeline-model.ts @@ -0,0 +1,34 @@ +export interface ModeledAssetInput { + relativeSyntax: boolean; + contained: boolean; + regular: boolean; + stable: boolean; + allowlisted: boolean; + bytes: number; + maxBytes: number; + mimePrefixBytes: number; +} + +export interface ModeledAssetResult { + result: "embedded" | "refused"; + sourceBytesReturned: number; + encodedBytes: number; + viewTimeRequests: number; +} + +export function modelAsset(input: ModeledAssetInput): ModeledAssetResult { + const accepted = input.relativeSyntax + && input.contained + && input.regular + && input.stable + && input.allowlisted + && input.bytes >= 0 + && input.bytes <= input.maxBytes; + if (!accepted) return { result: "refused", sourceBytesReturned: 0, encodedBytes: 0, viewTimeRequests: 0 }; + return { + result: "embedded", + sourceBytesReturned: input.bytes, + encodedBytes: input.mimePrefixBytes + 4 * Math.ceil(input.bytes / 3), + viewTimeRequests: 0, + }; +} From ed2bded96bee9a6127fe1d0cfd1e31cfab88d5f9 Mon Sep 17 00:00:00 2001 From: bitgorust Date: Mon, 17 Aug 2026 17:28:34 +0200 Subject: [PATCH 04/10] feat: load embedded fonts under strict CSP --- README.md | 3 +- docs/engineering-principles.md | 3 +- .../governance/redistribution-2026-08-16.md | 2 +- .../goal-3-portable-assets-2026-08-17.md | 32 +++++++++------- ...3-portable-assets-chromium-2026-08-17.json | 11 ++++-- ...l-3-portable-font-chromium-2026-08-17.json | 35 ++++++++++++++++++ ...al-3-portable-font-chromium-2026-08-17.png | Bin 0 -> 33686 bytes docs/redistribution-inventory.json | 9 +++++ docs/redistribution-policy.md | 4 +- scripts/portable-browser-evidence.ts | 33 +++++++++++------ skills/artifact-pages/SKILL.md | 2 + .../change.json | 14 +++++-- .../delta.md | 0 .../design.md | 0 .../evidence.md | 5 ++- .../proposal.md | 0 .../tasks.md | 4 +- specs/current/portable-renderer.spec.md | 12 +++--- src/assets.ts | 6 ++- src/render.ts | 10 ++++- test/assets.test.ts | 6 +++ test/render.test.ts | 2 +- 22 files changed, 143 insertions(+), 50 deletions(-) create mode 100644 docs/evidence/renderer/goal-3-portable-font-chromium-2026-08-17.json create mode 100644 docs/evidence/renderer/goal-3-portable-font-chromium-2026-08-17.png rename specs/{changes/portable-asset-pipeline => archive/2026-08-17-portable-asset-pipeline}/change.json (62%) rename specs/{changes/portable-asset-pipeline => archive/2026-08-17-portable-asset-pipeline}/delta.md (100%) rename specs/{changes/portable-asset-pipeline => archive/2026-08-17-portable-asset-pipeline}/design.md (100%) rename specs/{changes/portable-asset-pipeline => archive/2026-08-17-portable-asset-pipeline}/evidence.md (85%) rename specs/{changes/portable-asset-pipeline => archive/2026-08-17-portable-asset-pipeline}/proposal.md (100%) rename specs/{changes/portable-asset-pipeline => archive/2026-08-17-portable-asset-pipeline}/tasks.md (67%) diff --git a/README.md b/README.md index 879d5b8..7ae5610 100644 --- a/README.md +++ b/README.md @@ -167,11 +167,12 @@ opencode-artifacts import ./bundle Full reference: [`docs/component-spec.md`](docs/component-spec.md). Short version: -- **Frontmatter**: `title`, `icon` (emoji favicon), `description` (gallery subtitle) +- **Frontmatter**: `title`, `icon` (emoji favicon), `description` (gallery subtitle), and an optional worktree-relative `font` (WOFF/WOFF2/TTF/OTF embedded under `font-src data:`) - **Components** (JSON fences): `stats` metric cards, `timeline`, `findings` (severity-coded), `compare` variant cards, `callout` insight cards, `progress`, `diff` (annotated), `copy` (copy-to-session button), `decisions` (workshop rows the session reads back via `artifact_state`) - **Charts/diagrams**: ```` ```vega-lite ```` / ```` ```vega ```` / ```` ```echarts ```` / ```` ```mermaid ```` fences; runtimes inline only when used - **Markdown extras**: GitHub alerts (`> [!WARNING]` etc.), task lists, heading anchors, `##` sections become cards - **Local images**: ordinary `![meaningful alt](path/to/image.png)` resolves from the worktree root and embeds PNG/JPEG/GIF/WebP or constrained static SVG. Use the exact title `"decorative"` with empty alt only for an intentionally decorative image. URLs, absolute paths, traversal, and symlinks are refused. +- **Local fonts**: `font: path/to/project.woff2` uses the same contained, MIME-checked pipeline and a generated `@font-face`; it never permits a viewer network request or arbitrary CSS. - Broken specs degrade to inline error boxes; the page always ships Worked examples for every canonical pattern: [`examples/patterns/`](examples/patterns/) with diff --git a/docs/engineering-principles.md b/docs/engineering-principles.md index 537f01f..2b9c7ce 100644 --- a/docs/engineering-principles.md +++ b/docs/engineering-principles.md @@ -48,7 +48,8 @@ collectively exhausted by [`docs/product-spec.md`](product-spec.md), not repeate - Markdown uses `markdown-it` with `html: false`; raw HTML never passes through Markdown mode. Trusted HTML is a separately permissioned execution surface, never an implicit fallback. - Every emitted page carries the strict on-disk CSP: `default-src 'none'; script-src - 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; connect-src 'none'`. Served or + 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'`. + The `font-src` allowance is confined to embedded bytes and grants no network authority. Served or hosted copies may relax `connect-src` only to the documented self boundary; the portable file never changes. No `unsafe-eval`, ever—Vega uses its `ast: true` interpreter. [check:csp-no-unsafe-eval] [check:vega-interpreter] diff --git a/docs/evidence/governance/redistribution-2026-08-16.md b/docs/evidence/governance/redistribution-2026-08-16.md index ed23211..4fa43c3 100644 --- a/docs/evidence/governance/redistribution-2026-08-16.md +++ b/docs/evidence/governance/redistribution-2026-08-16.md @@ -12,7 +12,7 @@ machine-readable disposition is - Repository-authored source, policy, documentation, examples, fixtures, skills, tests, and generated evidence are covered by the root MIT license. -- All 23 retained binary assets are repository-generated screenshots under `docs/evidence/`. +- All 24 retained binary assets are repository-generated screenshots under `docs/evidence/`. Each entry names its synthetic/repository source, MIT disposition, contributor attribution, and exact SHA-256. - The repository contains zero embedded font files. Renderer CSS selects system fallback diff --git a/docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md b/docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md index 89b43c0..e74bf05 100644 --- a/docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md +++ b/docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md @@ -8,13 +8,12 @@ renderer observation, not a supported-platform or accessibility certification. Environment: official `node:24-bookworm`, Node 24.19.0, npm 11.7.0, container network disabled. - `npm run build`: passed. -- `npm test`: 180/180 passed at the first full checkpoint; the focused asset suite passed - 10/10 before the final publication-success assertion was added. +- `npm test`: 181/181 passed at the final packet gate; the focused asset suite passed 11/11. - `npm run check`: all 35 registered structural checks passed. - `test/assets.test.ts`: contained PNG expansion and hash, explicit decorative semantics, constrained SVG reconstruction, active SVG refusal, external/traversal/encoded path refusal, every-symlink refusal, non-regular/missing/MIME mismatch refusal, exact source/count/ - file/final boundaries, descriptor mutation detection, WOFF2 typing, synchronous broken-URL + file/final boundaries, descriptor mutation detection, WOFF/WOFF2/TTF/OTF typing, synchronous broken-URL prevention, plugin no-write refusal, and plugin expanded-byte publication. - `test/model/asset-pipeline-model.ts`: exhaustively enumerates 32 authority/property masks across seven byte boundaries. Every result either returns the exact base64 contribution for @@ -33,27 +32,34 @@ file containing a local PNG, Vega-Lite chart, table, and decision buttons. Harness: `scripts/portable-browser-evidence.ts` against Selenium standalone Chromium 151 at a 1440 × 1600 viewport. CDP network emulation was set offline before navigation. The retained -machine-readable report is +mixed-page machine-readable report is [`goal-3-portable-assets-chromium-2026-08-17.json`](goal-3-portable-assets-chromium-2026-08-17.json) and the retained visual is [`goal-3-portable-assets-chromium-2026-08-17.png`](goal-3-portable-assets-chromium-2026-08-17.png). +The loaded-font report and visual are +[`goal-3-portable-font-chromium-2026-08-17.json`](goal-3-portable-font-chromium-2026-08-17.json) +and +[`goal-3-portable-font-chromium-2026-08-17.png`](goal-3-portable-font-chromium-2026-08-17.png). Observed: -- document ready and useful chart content in 492 ms in this single non-benchmark run; +- document ready and useful chart content in 669 ms in the final single non-benchmark run; - embedded image complete at natural width 1011 with the expected exact SHA-256; - one chart visual, one table, and two decision buttons present; - Enter selected the focused decision button through WebDriver keyboard actions; - zero browser console entries; - request inventory contained only the `file:` document and embedded `data:image/png`; zero HTTP(S) requests occurred; -- on-disk CSP remained `default-src 'none'; script-src 'unsafe-inline'; style-src - 'unsafe-inline'; img-src data:; connect-src 'none'`. +- on-disk CSP was `default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; + img-src data:; font-src data:; connect-src 'none'`. -## Known incomplete boundary +## Embedded-font observation -Contained WOFF/WOFF2 is validated and inlined, but the current CSP blocks font use through -`default-src 'none'`. Adding only `font-src data:` is awaiting the repository's explicit CSP -change approval; no working custom-font claim is made until that decision and browser evidence -land. Mobile, keyboard traversal beyond the exercised control, screen reader, and supported- -browser evidence belong to the later Goal 3 accessibility packet and remain unverified. +After explicit CSP-change approval, a separate temporary fixture embedded the OS-packaged +DejaVu Sans Mono TTF from an explicit read-only font root. The font bytes were not retained in +the repository. Offline Chromium reported the `Artifact Project` font face as `loaded`, used +it as the computed body family, emitted zero console entries, and requested only the `file:` +document plus its `data:font/ttf` resource; HTTP(S) requests remained zero. + +Mobile, keyboard traversal beyond the exercised control, screen reader, and supported-browser +evidence belong to the later Goal 3 accessibility packet and remain unverified. diff --git a/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.json b/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.json index 32d1d0e..3e555ff 100644 --- a/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.json +++ b/docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.json @@ -1,19 +1,22 @@ { - "capturedAt": "2026-08-17T15:15:03.968Z", + "capturedAt": "2026-08-17T15:25:25.160Z", "browser": "Chromium 151 via selenium/standalone-chromium", "offlineEmulation": true, - "usefulContentMs": 492, + "usefulContentMs": 669, "observations": { "activeElement": "decision-opt selected", + "bodyFontFamily": "system-ui, -apple-system, \"Segoe UI\", sans-serif", "chartVisuals": 1, - "csp": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; connect-src 'none'", + "csp": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'", "decisionButtons": 2, + "fontSetStatus": "loaded", "imageComplete": true, "imageHash": "62f848af2e23c26493d3f9e24b6af4e59516e8d9ce1e0fd2b68169dfc98281dc", "imageNaturalWidth": 1011, "imageSource": "data:image/png;base64,iVBORw0KGg", "keyboardSelected": true, - "pageBytes": 753580, + "pageBytes": 753596, + "projectFontFaces": [], "readyState": "complete", "tableCaptions": [], "tables": 1 diff --git a/docs/evidence/renderer/goal-3-portable-font-chromium-2026-08-17.json b/docs/evidence/renderer/goal-3-portable-font-chromium-2026-08-17.json new file mode 100644 index 0000000..f5afad4 --- /dev/null +++ b/docs/evidence/renderer/goal-3-portable-font-chromium-2026-08-17.json @@ -0,0 +1,35 @@ +{ + "capturedAt": "2026-08-17T15:25:13.232Z", + "browser": "Chromium 151 via selenium/standalone-chromium", + "offlineEmulation": true, + "usefulContentMs": 629, + "observations": { + "activeElement": "", + "bodyFontFamily": "\"Artifact Project\", system-ui, -apple-system, \"Segoe UI\", sans-serif", + "chartVisuals": 0, + "csp": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'", + "decisionButtons": 0, + "fontSetStatus": "loaded", + "imageComplete": false, + "imageHash": null, + "imageNaturalWidth": 0, + "imageSource": null, + "keyboardSelected": false, + "pageBytes": 489511, + "projectFontFaces": [ + { + "family": "Artifact Project", + "status": "loaded" + } + ], + "readyState": "complete", + "tableCaptions": [], + "tables": 1 + }, + "browserLogs": [], + "requestUrls": [ + "file:///evidence/portable-font.html", + "data:font/ttf;base64,[embedded]" + ], + "networkRequestUrls": [] +} diff --git a/docs/evidence/renderer/goal-3-portable-font-chromium-2026-08-17.png b/docs/evidence/renderer/goal-3-portable-font-chromium-2026-08-17.png new file mode 100644 index 0000000000000000000000000000000000000000..2fd8bac57a477c3913705f5328fc5b591b3f0232 GIT binary patch literal 33686 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 LniT$aEBfC6G0Orm literal 0 HcmV?d00001 diff --git a/docs/redistribution-inventory.json b/docs/redistribution-inventory.json index 2bb5e93..8e16538 100644 --- a/docs/redistribution-inventory.json +++ b/docs/redistribution-inventory.json @@ -219,6 +219,15 @@ "attribution": "opencode-artifacts contributors", "status": "approved" }, + { + "path": "docs/evidence/renderer/goal-3-portable-font-chromium-2026-08-17.png", + "sha256": "98ab1a439c223319cf6b31c85790429751559cd448eb0ff35d640c53ebf3e97a", + "provenance": "repository-generated browser capture", + "source": "temporary offline fixture using the OS-packaged DejaVu Sans Mono font; font bytes are not retained", + "license": "MIT", + "attribution": "opencode-artifacts contributors", + "status": "approved" + }, { "path": "docs/evidence/theme-toggle-dark.png", "sha256": "b495dfbb3bc4a8c5b3e7a445cd63e5eb32f3e59ff3f857f0c1d087796033b742", diff --git a/docs/redistribution-policy.md b/docs/redistribution-policy.md index 53032a6..3659c2d 100644 --- a/docs/redistribution-policy.md +++ b/docs/redistribution-policy.md @@ -9,7 +9,9 @@ documentation, fixtures, skills, tests, and generated screenshots are distribute root [MIT license](../LICENSE). Every retained binary asset is bound to exact bytes by SHA-256 and names its source and attribution. -No font file is embedded or redistributed. The renderer uses system font-family fallbacks. +No font file is retained or redistributed by this repository. The renderer uses system +fallbacks by default; a user-declared worktree font may be embedded in that user's generated +page and remains subject to the user's own redistribution authority. Runtime dependency terms and the three exceptional branch choices are governed by [`license-dispositions.json`](license-dispositions.json) and the exact candidate evidence in [`renderer-remediation-2026-08-16.md`](evidence/governance/renderer-remediation-2026-08-16.md). diff --git a/scripts/portable-browser-evidence.ts b/scripts/portable-browser-evidence.ts index fd7d590..bdd7b88 100644 --- a/scripts/portable-browser-evidence.ts +++ b/scripts/portable-browser-evidence.ts @@ -52,27 +52,33 @@ try { let ready = false; for (let attempt = 0; attempt < 100; attempt++) { ready = await request("POST", `${route}/execute/sync`, { - script: "return document.readyState === 'complete' && !!document.querySelector('.chart svg, .chart canvas');", + script: "return document.readyState === 'complete' && (!document.querySelector('.chart') || !!document.querySelector('.chart svg, .chart canvas'));", args: [], }); if (ready) break; await new Promise((resolve) => setTimeout(resolve, 50)); } const usefulContentMs = Date.now() - startedAt; - await request("POST", `${route}/execute/sync`, { - script: "document.querySelector('.decision-opt').focus(); return true;", + await request("POST", `${route}/execute/async`, { + script: "const done = arguments[arguments.length - 1]; document.fonts.ready.then(() => done(true), () => done(false));", args: [], }); - await request("POST", `${route}/actions`, { - actions: [{ - type: "key", - id: "keyboard", - actions: [ - { type: "keyDown", value: "\uE007" }, - { type: "keyUp", value: "\uE007" }, - ], - }], + const hasDecision = await request("POST", `${route}/execute/sync`, { + script: "const button = document.querySelector('.decision-opt'); if (button) button.focus(); return !!button;", + args: [], }); + if (hasDecision) { + await request("POST", `${route}/actions`, { + actions: [{ + type: "key", + id: "keyboard", + actions: [ + { type: "keyDown", value: "\uE007" }, + { type: "keyUp", value: "\uE007" }, + ], + }], + }); + } const observations = await request>("POST", `${route}/execute/sync`, { script: ` const image = document.querySelector('img[data-asset-sha256]'); @@ -90,6 +96,9 @@ try { keyboardSelected: !!button && button.classList.contains('selected'), activeElement: document.activeElement ? document.activeElement.className : null, csp: document.querySelector('meta[http-equiv="Content-Security-Policy"]')?.getAttribute('content'), + fontSetStatus: document.fonts.status, + projectFontFaces: Array.from(document.fonts).filter((face) => face.family.includes('Artifact Project')).map((face) => ({ family: face.family, status: face.status })), + bodyFontFamily: getComputedStyle(document.body).fontFamily, pageBytes: new TextEncoder().encode(document.documentElement.outerHTML).length, }; `, diff --git a/skills/artifact-pages/SKILL.md b/skills/artifact-pages/SKILL.md index 843022e..5f68ea4 100644 --- a/skills/artifact-pages/SKILL.md +++ b/skills/artifact-pages/SKILL.md @@ -99,3 +99,5 @@ Grown from observed failures; add new ones as they bite: - Markdown image paths are worktree-root-relative. The publisher embeds allowlisted local bytes and refuses URLs, absolute/traversal paths, symlinks, missing alt text, and unsupported or oversized content before asking permission. +- A project font is opt-in via worktree-root-relative frontmatter `font:` and must be WOFF, + WOFF2, TTF, or OTF. The renderer generates the font rule; never add CSS to Markdown. diff --git a/specs/changes/portable-asset-pipeline/change.json b/specs/archive/2026-08-17-portable-asset-pipeline/change.json similarity index 62% rename from specs/changes/portable-asset-pipeline/change.json rename to specs/archive/2026-08-17-portable-asset-pipeline/change.json index f12f548..1ae5fca 100644 --- a/specs/changes/portable-asset-pipeline/change.json +++ b/specs/archive/2026-08-17-portable-asset-pipeline/change.json @@ -3,9 +3,15 @@ "id": "portable-asset-pipeline", "title": "Add a contained offline asset pipeline", "lane": "high-risk", - "status": "implementing", - "affectedRequirements": ["RENDER-04", "RENDER-05", "SEC-02"], - "currentSpecs": ["specs/current/portable-renderer.spec.md"], + "status": "archived", + "affectedRequirements": [ + "RENDER-04", + "RENDER-05", + "SEC-02" + ], + "currentSpecs": [ + "specs/current/portable-renderer.spec.md" + ], "currentSpecsUpdated": true, "approval": { "by": "aaron.zeng", @@ -17,5 +23,5 @@ "reason": "" }, "createdAt": "2026-08-17", - "archivedAt": null + "archivedAt": "2026-08-17" } diff --git a/specs/changes/portable-asset-pipeline/delta.md b/specs/archive/2026-08-17-portable-asset-pipeline/delta.md similarity index 100% rename from specs/changes/portable-asset-pipeline/delta.md rename to specs/archive/2026-08-17-portable-asset-pipeline/delta.md diff --git a/specs/changes/portable-asset-pipeline/design.md b/specs/archive/2026-08-17-portable-asset-pipeline/design.md similarity index 100% rename from specs/changes/portable-asset-pipeline/design.md rename to specs/archive/2026-08-17-portable-asset-pipeline/design.md diff --git a/specs/changes/portable-asset-pipeline/evidence.md b/specs/archive/2026-08-17-portable-asset-pipeline/evidence.md similarity index 85% rename from specs/changes/portable-asset-pipeline/evidence.md rename to specs/archive/2026-08-17-portable-asset-pipeline/evidence.md index a2461fb..c073f6e 100644 --- a/specs/changes/portable-asset-pipeline/evidence.md +++ b/specs/archive/2026-08-17-portable-asset-pipeline/evidence.md @@ -4,8 +4,9 @@ - Validation: Phase 2 requires contained assets and a self-contained offline mixed-content page. - Verification: `test/assets.test.ts` covers filesystem, MIME, SVG, alt, refusal/no-write, and expansion behavior; the mixed fixture was exercised offline in real Chromium 151. -- Result: image/SVG behavior passed; WOFF/WOFF2 loading awaits explicit approval for the - narrow `font-src data:` CSP directive and is not yet claimed. +- Result: passed for images, constrained SVG, and WOFF/WOFF2/TTF/OTF. After explicit approval, + real offline Chromium loaded the embedded TTF under the narrow `font-src data:` directive + with zero console entries and zero HTTP(S) requests. - Evidence: [@test](test/assets.test.ts) [@manual](docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md) ## Requirement: RENDER-05 diff --git a/specs/changes/portable-asset-pipeline/proposal.md b/specs/archive/2026-08-17-portable-asset-pipeline/proposal.md similarity index 100% rename from specs/changes/portable-asset-pipeline/proposal.md rename to specs/archive/2026-08-17-portable-asset-pipeline/proposal.md diff --git a/specs/changes/portable-asset-pipeline/tasks.md b/specs/archive/2026-08-17-portable-asset-pipeline/tasks.md similarity index 67% rename from specs/changes/portable-asset-pipeline/tasks.md rename to specs/archive/2026-08-17-portable-asset-pipeline/tasks.md index 8ad5130..6118ac8 100644 --- a/specs/changes/portable-asset-pipeline/tasks.md +++ b/specs/archive/2026-08-17-portable-asset-pipeline/tasks.md @@ -2,7 +2,7 @@ - [x] Confirm proposal validation and human approval. - [x] Add the typed declaration/parser, bounded resolver, MIME/active-content validators, and exact byte accounting. -- [ ] Integrate asset preflight and expansion before final scan/cap/lifecycle publication (font loading awaits the explicit CSP directive decision). +- [x] Integrate asset preflight and expansion before final scan/cap/lifecycle publication. - [x] Add property, traversal/symlink/special-file, boundary, offline-browser, and mixed-page tests. - [x] Retain final request/console/byte evidence and update `specs/current/portable-renderer.spec.md`. -- [ ] Run repository validation and archive the packet. +- [x] Run repository validation and archive the packet. diff --git a/specs/current/portable-renderer.spec.md b/specs/current/portable-renderer.spec.md index 5a2c4d2..8bb92b3 100644 --- a/specs/current/portable-renderer.spec.md +++ b/specs/current/portable-renderer.spec.md @@ -8,9 +8,9 @@ symlinked, non-regular, mislabeled, active, changed-during-read, or unlabelled image inputs fail before the permission prompt or any publication write. An exact image title of `decorative` explicitly selects empty-alt presentation semantics. -- `RENDER-04`: optional frontmatter `font:` declarations accept contained WOFF/WOFF2 bytes - under the same resolver and generate a fixed `@font-face`; the required narrow CSP font - directive remains pending explicit approval, so font loading is not yet claimed. +- `RENDER-04`: optional frontmatter `font:` declarations accept contained WOFF, WOFF2, TTF, + or OTF bytes under the same resolver and generate a fixed `@font-face`. The on-disk CSP adds + only `font-src data:`, so embedded fonts load without granting network authority. - `RENDER-05`: Markdown source, declaration count, each source asset, aggregate decoded asset bytes, encoded contributions, rendered HTML, and footer-expanded publication bytes are bounded. The final default limit remains 15 MiB and is enforced before lifecycle commit. @@ -40,7 +40,7 @@ malformed or unknown markup. accounting, mutation, refusal/no-write, and no-view-time-request properties. - `examples/patterns/portable-mixed.md` plus `docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md` retain the real Chromium - offline mixed-page observation. -- This evidence is one Linux/Chromium observation. It does not certify a browser, OS, mobile, - assistive-technology, or font-loading cell, and it does not complete the remaining Goal 3 + offline mixed-page and loaded-font observations. +- This evidence is one Linux/Chromium observation. It does not certify a supported browser, + OS, mobile, or assistive-technology cell, and it does not complete the remaining Goal 3 declarative, token, accessibility, or performance packets. diff --git a/src/assets.ts b/src/assets.ts index 6c33a25..cf3df0a 100644 --- a/src/assets.ts +++ b/src/assets.ts @@ -193,10 +193,12 @@ function declaredMime(path: string, kind: AssetKind): string { [".svg", "image/svg+xml", "image"], [".woff", "font/woff", "font"], [".woff2", "font/woff2", "font"], + [".ttf", "font/ttf", "font"], + [".otf", "font/otf", "font"], ]; const match = entries.find(([extension]) => lower.endsWith(extension)); if (!match || match[2] !== kind) { - throw new AssetPreflightError("unsupported-type", `unsupported ${kind} asset type`, path, kind === "font" ? "use a WOFF or WOFF2 file" : "use PNG, JPEG, GIF, WebP, or a constrained SVG"); + throw new AssetPreflightError("unsupported-type", `unsupported ${kind} asset type`, path, kind === "font" ? "use a WOFF, WOFF2, TTF, or OTF file" : "use PNG, JPEG, GIF, WebP, or a constrained SVG"); } return match[1]; } @@ -208,6 +210,8 @@ function detectedMime(bytes: Buffer): string | undefined { if (bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP") return "image/webp"; if (bytes.length >= 4 && bytes.subarray(0, 4).toString("ascii") === "wOFF") return "font/woff"; if (bytes.length >= 4 && bytes.subarray(0, 4).toString("ascii") === "wOF2") return "font/woff2"; + if (bytes.length >= 4 && bytes.subarray(0, 4).equals(Buffer.from([0, 1, 0, 0]))) return "font/ttf"; + if (bytes.length >= 4 && bytes.subarray(0, 4).toString("ascii") === "OTTO") return "font/otf"; try { const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes).trim(); if (text.startsWith("")) return "image/svg+xml"; diff --git a/src/render.ts b/src/render.ts index bb46987..20a7376 100644 --- a/src/render.ts +++ b/src/render.ts @@ -49,6 +49,7 @@ export const CSP = [ "script-src 'unsafe-inline'", "style-src 'unsafe-inline'", "img-src data:", + "font-src data:", "connect-src 'none'", ].join("; "); @@ -778,11 +779,18 @@ export function renderArtifact(markdown: string, options: RenderOptions = {}): R maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES, assetCss: options.assets?.font === undefined ? undefined - : `@font-face{font-family:"Artifact Project";src:url(${options.assets.font.dataUri}) format("${options.assets.font.mime === "font/woff2" ? "woff2" : "woff"}");font-display:swap}body{font-family:"Artifact Project",system-ui,-apple-system,"Segoe UI",sans-serif}`, + : `@font-face{font-family:"Artifact Project";src:url(${options.assets.font.dataUri}) format("${fontFormat(options.assets.font.mime)}");font-display:swap}body{font-family:"Artifact Project",system-ui,-apple-system,"Segoe UI",sans-serif}`, }); return { html, meta: doc.meta, chartCount: doc.charts.length }; } +function fontFormat(mime: string): string { + if (mime === "font/woff2") return "woff2"; + if (mime === "font/woff") return "woff"; + if (mime === "font/ttf") return "truetype"; + return "opentype"; +} + export async function renderPortableArtifact( markdown: string, worktreeRoot: string, diff --git a/test/assets.test.ts b/test/assets.test.ts index b9920d0..c8c7c86 100644 --- a/test/assets.test.ts +++ b/test/assets.test.ts @@ -132,6 +132,12 @@ test("font declarations are typed, hashed, and embedded without trusting the ext assert.match(rendered.html, /data:font\/woff2;base64,/); await writeFile(join(root, "wrong.woff2"), PNG); await assert.rejects(resolvePortableAssets("---\nfont: wrong.woff2\n---\nx", root), (error: unknown) => error instanceof AssetPreflightError && error.code === "type-mismatch"); + const ttf = Buffer.concat([Buffer.from([0, 1, 0, 0]), Buffer.alloc(32)]); + await writeFile(join(root, "project.ttf"), ttf); + const ttfRendered = await renderPortableArtifact("---\nfont: project.ttf\n---\nx", root); + assert.match(ttfRendered.html, /data:font\/ttf;base64,/); + assert.match(ttfRendered.html, /format\("truetype"\)/); + assert.match(ttfRendered.html, /font-src data:/); }); }); diff --git a/test/render.test.ts b/test/render.test.ts index 6312b26..073888a 100644 --- a/test/render.test.ts +++ b/test/render.test.ts @@ -29,7 +29,7 @@ test("plain markdown artifact carries CSP and escaped title, no chart runtimes", const { html, meta, chartCount } = renderArtifact("---\ntitle: My Report\n---\n# Hi\n"); assert.equal(meta.title, "My Report"); assert.equal(chartCount, 0); - assert.match(html, /Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; connect-src 'none'"/); + assert.match(html, /Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'"/); assert.match(html, /My <b>Report<\/b><\/title>/); assert.match(html, /<h1 id="hi">Hi<\/h1>/); assert.ok(!html.includes("runtime:vega")); From 8760baece1116cc8f142fe72977bc2a90809a9d0 Mon Sep 17 00:00:00 2001 From: bitgorust <bitgorust@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:40:25 +0200 Subject: [PATCH 05/10] feat: preflight declarative authoring --- README.md | 4 +- docs/component-spec.md | 4 +- docs/engineering-principles.md | 3 + skills/artifact-pages/SKILL.md | 5 +- skills/artifact-pages/reference/components.md | 5 +- .../change.json | 15 +- .../delta.md | 0 .../evidence.md | 22 ++ .../proposal.md | 0 .../tasks.md | 8 + .../evidence.md | 13 -- .../declarative-authoring-preflight/tasks.md | 8 - specs/current/portable-renderer.spec.md | 15 ++ src/cli.ts | 19 +- src/components.ts | 128 +++++++++++ src/plugin.ts | 23 +- src/preflight.ts | 198 ++++++++++++++++++ src/render.ts | 28 +-- test/preflight.test.ts | 178 ++++++++++++++++ 19 files changed, 625 insertions(+), 51 deletions(-) rename specs/{changes/declarative-authoring-preflight => archive/2026-08-17-declarative-authoring-preflight}/change.json (59%) rename specs/{changes/declarative-authoring-preflight => archive/2026-08-17-declarative-authoring-preflight}/delta.md (100%) create mode 100644 specs/archive/2026-08-17-declarative-authoring-preflight/evidence.md rename specs/{changes/declarative-authoring-preflight => archive/2026-08-17-declarative-authoring-preflight}/proposal.md (100%) create mode 100644 specs/archive/2026-08-17-declarative-authoring-preflight/tasks.md delete mode 100644 specs/changes/declarative-authoring-preflight/evidence.md delete mode 100644 specs/changes/declarative-authoring-preflight/tasks.md create mode 100644 src/preflight.ts create mode 100644 test/preflight.test.ts diff --git a/README.md b/README.md index 7ae5610..f04289b 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,9 @@ Full reference: [`docs/component-spec.md`](docs/component-spec.md). Short versio - **Markdown extras**: GitHub alerts (`> [!WARNING]` etc.), task lists, heading anchors, `##` sections become cards - **Local images**: ordinary `![meaningful alt](path/to/image.png)` resolves from the worktree root and embeds PNG/JPEG/GIF/WebP or constrained static SVG. Use the exact title `"decorative"` with empty alt only for an intentionally decorative image. URLs, absolute paths, traversal, and symlinks are refused. - **Local fonts**: `font: path/to/project.woff2` uses the same contained, MIME-checked pipeline and a generated `@font-face`; it never permits a viewer network request or arbitrary CSS. -- Broken specs degrade to inline error boxes; the page always ships +- CLI/plugin publication preflights the whole document and refuses all detected errors before + permission or writes. Standalone rendering still degrades broken specs to escaped inline + error boxes for resilient inspection; warnings remain visible on successful publication. Worked examples for every canonical pattern: [`examples/patterns/`](examples/patterns/) with browser-verified screenshots in [`docs/evidence/patterns/`](docs/evidence/patterns/). diff --git a/docs/component-spec.md b/docs/component-spec.md index 7fff3bb..6bdab93 100644 --- a/docs/component-spec.md +++ b/docs/component-spec.md @@ -109,7 +109,9 @@ header toggle cycles system → dark → light and persists to localStorage - GitHub alerts: `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`, `> [!IMPORTANT]`, `> [!CAUTION]` → styled callout boxes (post-process rendered `<blockquote>` HTML). - Task lists: `- [ ]` / `- [x]` render as styled checkboxes (read-only). -- Invalid JSON in any component fence → inline error box (existing behavior, reused). +- CLI/plugin publication aggregates invalid component JSON with other authoring diagnostics + and refuses before permission or writes. Standalone rendering retains the escaped inline + error box fallback. ## Mapping to the documented Claude patterns diff --git a/docs/engineering-principles.md b/docs/engineering-principles.md index 2b9c7ce..a114e90 100644 --- a/docs/engineering-principles.md +++ b/docs/engineering-principles.md @@ -118,6 +118,9 @@ collectively exhausted by [`docs/product-spec.md`](product-spec.md), not repeate conditionals scattered across established units. - The model authors declarative specifications. Arbitrary per-page JavaScript exists only in explicit trusted-HTML mode and does not inherit fixed-renderer guarantees. +- Declarative publication validates the complete document before permission or writes. Its + component/chart schemas are shared with the renderer, diagnostics are bounded and redacted, + and standalone rendering retains escaped inline fallbacks for inspection. - The portable page is the long-term compatibility layer. Services are progressive enhancement; no page-view dependency, account, package runtime, or network is required. - Schemas, CLI/tool contracts, component syntax, exports, routes, and host adapters evolve by diff --git a/skills/artifact-pages/SKILL.md b/skills/artifact-pages/SKILL.md index 5f68ea4..38b8e53 100644 --- a/skills/artifact-pages/SKILL.md +++ b/skills/artifact-pages/SKILL.md @@ -92,8 +92,9 @@ Grown from observed failures; add new ones as they bite: - Vega-Lite `text` channels need `{"field": "..."}` objects, not bare strings. - Area charts under layered marks need `"clip": true` or the fill escapes the plot. - A chart spec containing `</script>` is safe (payload is `\u003c`-escaped) — don't "fix" it. -- Malformed component JSON renders an inline error box and the page still ships; fix the spec - and republish rather than working around the box. +- Malformed component JSON is returned with all other detected authoring errors before the + publish permission or any write. The standalone renderer retains escaped inline error boxes + for inspection; fix every reported spec and publish again. - `serve`-mode extras (live reload, decisions persistence, comments) exist only on served pages; a `file://` artifact keeps its strict CSP and localStorage-only state. - Markdown image paths are worktree-root-relative. The publisher embeds allowlisted local diff --git a/skills/artifact-pages/reference/components.md b/skills/artifact-pages/reference/components.md index f58c0f4..2ca6cba 100644 --- a/skills/artifact-pages/reference/components.md +++ b/skills/artifact-pages/reference/components.md @@ -51,8 +51,9 @@ gives pan/zoom. Title the finding, not the axes. GitHub alerts — `> [!NOTE]` `[!TIP]` `[!IMPORTANT]` `[!WARNING]` `[!CAUTION]` — become toned callout boxes. `- [ ]` / `- [x]` become styled checkboxes. Headings get id anchors. Raw HTML -is never passed through; broken component specs render as inline error boxes (the page still -ships — fix and republish). +is never passed through. CLI/plugin publication reports broken component specs together and +refuses before permission or writes; standalone rendering retains escaped inline error boxes +for inspection. ## Themes (optional) diff --git a/specs/changes/declarative-authoring-preflight/change.json b/specs/archive/2026-08-17-declarative-authoring-preflight/change.json similarity index 59% rename from specs/changes/declarative-authoring-preflight/change.json rename to specs/archive/2026-08-17-declarative-authoring-preflight/change.json index 3364ef0..fb3593e 100644 --- a/specs/changes/declarative-authoring-preflight/change.json +++ b/specs/archive/2026-08-17-declarative-authoring-preflight/change.json @@ -3,10 +3,15 @@ "id": "declarative-authoring-preflight", "title": "Preflight declarative authoring errors", "lane": "standard", - "status": "approved", - "affectedRequirements": ["RENDER-02", "QUAL-02"], - "currentSpecs": ["specs/current/portable-renderer.spec.md"], - "currentSpecsUpdated": false, + "status": "archived", + "affectedRequirements": [ + "RENDER-02", + "QUAL-02" + ], + "currentSpecs": [ + "specs/current/portable-renderer.spec.md" + ], + "currentSpecsUpdated": true, "approval": { "by": "aaron.zeng", "at": "2026-08-17T15:02:07Z" @@ -17,5 +22,5 @@ "reason": "" }, "createdAt": "2026-08-17", - "archivedAt": null + "archivedAt": "2026-08-17" } diff --git a/specs/changes/declarative-authoring-preflight/delta.md b/specs/archive/2026-08-17-declarative-authoring-preflight/delta.md similarity index 100% rename from specs/changes/declarative-authoring-preflight/delta.md rename to specs/archive/2026-08-17-declarative-authoring-preflight/delta.md diff --git a/specs/archive/2026-08-17-declarative-authoring-preflight/evidence.md b/specs/archive/2026-08-17-declarative-authoring-preflight/evidence.md new file mode 100644 index 0000000..fd90e4d --- /dev/null +++ b/specs/archive/2026-08-17-declarative-authoring-preflight/evidence.md @@ -0,0 +1,22 @@ +# Evidence: Preflight declarative authoring errors + +## Requirement: RENDER-02 +- Validation: authors need all actionable schema errors before publication without losing safe fallbacks. +- Verification: the golden fixture exercises frontmatter, component, Mermaid, anchor, and alert + diagnostics through the domain, CLI, and plugin; count/byte ceilings, redaction, repeated + asset locations, no-write refusal, visible warnings, and trusted-mode permission metadata + are asserted directly. +- Result: passed. Reports are source ordered and stable, overflow ends with an exact omitted + marker, CLI/plugin errors precede permission and writes, and standalone rendering retains + escaped inline error boxes. +- Evidence: [@test](test/preflight.test.ts) [@test](test/assets.test.ts) + +## Requirement: QUAL-02 +- Validation: every declarative surface must share deterministic validation behavior. +- Verification: every registered component kind and invalid Vega-Lite input exercises the + shared validator/renderer fallback; every checked-in example has zero preflight errors. + The complete repository suite, TypeScript build, all 35 structural checks, package dry run, + and diff whitespace check passed under Node 24 with network disabled. +- Result: passed with 192/192 tests; diagnostics compare exactly across repeated runs and the + valid corpus remains accepted. +- Evidence: [@test](test/preflight.test.ts) [@test](test/components.test.ts) [@test](test/render.test.ts) diff --git a/specs/changes/declarative-authoring-preflight/proposal.md b/specs/archive/2026-08-17-declarative-authoring-preflight/proposal.md similarity index 100% rename from specs/changes/declarative-authoring-preflight/proposal.md rename to specs/archive/2026-08-17-declarative-authoring-preflight/proposal.md diff --git a/specs/archive/2026-08-17-declarative-authoring-preflight/tasks.md b/specs/archive/2026-08-17-declarative-authoring-preflight/tasks.md new file mode 100644 index 0000000..617f5cb --- /dev/null +++ b/specs/archive/2026-08-17-declarative-authoring-preflight/tasks.md @@ -0,0 +1,8 @@ +# Tasks: Preflight declarative authoring errors + +- [x] Confirm proposal validation and human approval. +- [x] Implement shared typed diagnostics and side-effect-free aggregate preflight. +- [x] Expose bounded preflight through CLI/plugin while retaining inline fallback parity. +- [x] Add multi-error, redaction/truncation, valid-corpus, parity, and no-write tests. +- [x] Record evidence and update `specs/current/portable-renderer.spec.md`. +- [x] Run repository validation and archive the packet. diff --git a/specs/changes/declarative-authoring-preflight/evidence.md b/specs/changes/declarative-authoring-preflight/evidence.md deleted file mode 100644 index bf83e64..0000000 --- a/specs/changes/declarative-authoring-preflight/evidence.md +++ /dev/null @@ -1,13 +0,0 @@ -# Evidence: Preflight declarative authoring errors - -## Requirement: RENDER-02 -- Validation: authors need all actionable schema errors before publication without losing safe fallbacks. -- Verification: planned golden multi-error domain/CLI/plugin/standalone parity tests. -- Result: pending implementation. -- Evidence: [@manual](docs/product-spec.md) - -## Requirement: QUAL-02 -- Validation: every declarative surface must share deterministic validation behavior. -- Verification: planned full valid/invalid corpus and cwd/time independence tests. -- Result: pending implementation. -- Evidence: [@manual](docs/engineering-principles.md) diff --git a/specs/changes/declarative-authoring-preflight/tasks.md b/specs/changes/declarative-authoring-preflight/tasks.md deleted file mode 100644 index 8f2c782..0000000 --- a/specs/changes/declarative-authoring-preflight/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks: Preflight declarative authoring errors - -- [ ] Confirm proposal validation and human approval. -- [ ] Implement shared typed diagnostics and side-effect-free aggregate preflight. -- [ ] Expose bounded preflight through CLI/plugin while retaining inline fallback parity. -- [ ] Add multi-error, redaction/truncation, valid-corpus, parity, and no-write tests. -- [ ] Record evidence and update `specs/current/portable-renderer.spec.md`. -- [ ] Run repository validation and archive the packet. diff --git a/specs/current/portable-renderer.spec.md b/specs/current/portable-renderer.spec.md index 8bb92b3..938fd0a 100644 --- a/specs/current/portable-renderer.spec.md +++ b/specs/current/portable-renderer.spec.md @@ -2,6 +2,18 @@ ## Current behavior +- `RENDER-02`: CLI and plugin Markdown publication runs one side-effect-free preflight before + permission or writes. Frontmatter, component/table, chart, Mermaid, heading-anchor, + task-marker, alert, and asset diagnostics carry stable codes, severity, line/column, and a + next action in source order. Errors refuse publication; warnings remain visible on success. +- `RENDER-02`: reports retain at most 50 diagnostics and 16 KiB of diagnostic JSON by default. + Sensitive-looking values are redacted, messages/actions are truncated, and any overflow + ends in a `diagnostics-omitted` error with the exact omitted count. Raw HTML remains an + explicit trusted mode and produces a visible `trusted-html-mode` warning plus permission + metadata. +- `QUAL-02`: component and chart acceptance is shared by preflight and rendering. Standalone + rendering preserves escaped inline error boxes, while every checked-in pattern must pass + preflight without errors and CLI/plugin refusals are verified as no-write behavior. - `RENDER-04`: Markdown images published through the CLI or plugin resolve only from the explicit project worktree. PNG, JPEG, GIF, WebP, and a conservatively reconstructed SVG subset become hashed data URIs. External, absolute, traversal, encoded-separator, missing, @@ -38,6 +50,9 @@ malformed or unknown markup. - `test/assets.test.ts` and `test/model/asset-pipeline-model.ts` cover the resolver, exact accounting, mutation, refusal/no-write, and no-view-time-request properties. +- `test/preflight.test.ts` covers ordered multi-error reports, redaction, count/byte ceilings, + repeated asset locations, every component kind, chart fallback parity, valid examples, + CLI/plugin no-write refusal, visible warnings, and trusted-mode disclosure. - `examples/patterns/portable-mixed.md` plus `docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md` retain the real Chromium offline mixed-page and loaded-font observations. diff --git a/src/cli.ts b/src/cli.ts index dbd1dd8..99224f1 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,9 @@ #!/usr/bin/env node import { lstat, mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; -import { renderPortableArtifact, renderRawHtml } from "./render.ts"; +import { renderArtifact, renderRawHtml } from "./render.ts"; +import type { PortableAssets } from "./assets.ts"; +import { formatPreflight, preflightDocument, trustedHtmlDiagnostic } from "./preflight.ts"; import { FilePublisher, slugify } from "./publisher.ts"; import { GitHubPagesPublisher } from "./github-pages.ts"; import { CloudflarePublisher } from "./cloudflare-publisher.ts"; @@ -106,8 +108,21 @@ async function renderCommand(args: string[]): Promise<void> { ); process.exit(1); } + let assets: PortableAssets | undefined; + if (format === "html") { + console.error(`warning: ${trustedHtmlDiagnostic().message}`); + } else { + const preflight = await preflightDocument(markdown, { worktreeRoot: process.cwd() }); + const errors = preflight.diagnostics.filter((item) => item.severity === "error"); + if (errors.length > 0 || preflight.omitted > 0) { + console.error(formatPreflight(preflight)); + process.exit(1); + } + for (const warning of preflight.diagnostics) console.error(`warning: ${warning.code} at ${warning.line}:${warning.column}: ${warning.message}`); + assets = preflight.assets; + } const rendered = - format === "html" ? renderRawHtml(markdown, title ? { title } : {}) : await renderPortableArtifact(markdown, process.cwd()); + format === "html" ? renderRawHtml(markdown, title ? { title } : {}) : renderArtifact(markdown, { assets }); const finalTitle = title ?? rendered.meta.title ?? "Artifact"; const finalFindings = scanSensitive(rendered.html); if (finalFindings.length > 0 && !force) { diff --git a/src/components.ts b/src/components.ts index 5a4bff1..7ef7d47 100644 --- a/src/components.ts +++ b/src/components.ts @@ -55,6 +55,132 @@ const STAT_TONES = ["good", "bad", "warn", "neutral"] as const; const ITEM_TONES = ["good", "bad", "warn", "info", "neutral"] as const; const SEVERITIES = ["critical", "high", "medium", "low"] as const; +export interface ComponentIssue { + code: string; + reason: string; + nextAction: string; +} + +function issue(code: string, reason: string, nextAction: string): ComponentIssue[] { + return [{ code, reason, nextAction }]; +} + +function recordEntries(value: unknown, kind: string): ComponentIssue[] { + if (!Array.isArray(value)) return issue(`${kind}-shape`, "expected a JSON array", "provide the documented array schema"); + return value.flatMap((entry, index) => + asRecord(entry) === undefined + ? issue(`${kind}-entry`, `entry ${index + 1} must be an object`, "replace non-object entries") + : [], + ); +} + +function requiredStrings(value: unknown[], kind: string, fields: string[]): ComponentIssue[] { + const issues: ComponentIssue[] = []; + for (const [index, entry] of value.entries()) { + const item = asRecord(entry); + if (!item) continue; + for (const field of fields) { + if (typeof item[field] !== "string" || item[field] === "") { + issues.push(...issue(`${kind}-${field}`, `entry ${index + 1} requires string '${field}'`, `add ${field} to every ${kind} entry`)); + } + } + } + return issues; +} + +export function validateComponent(kind: ComponentKind, source: string): ComponentIssue[] { + if (kind === "diff") return source.trim() === "" ? issue("diff-empty", "diff source is empty", "add unified diff lines") : []; + if (kind === "mermaid") return source.trim() === "" ? issue("mermaid-empty", "Mermaid source is empty", "add a Mermaid diagram") : []; + let value: unknown; + try { + value = JSON.parse(source); + } catch { + return issue(`${kind}-json`, "component JSON is invalid", "provide valid JSON matching the documented schema"); + } + if (["stats", "timeline", "findings", "compare"].includes(kind)) { + const issues = recordEntries(value, kind); + if (!Array.isArray(value)) return issues; + const required = kind === "stats" ? ["label", "value"] : kind === "timeline" ? ["time", "title"] : ["title"]; + issues.push(...requiredStrings(value, kind, required)); + for (const [index, entry] of value.entries()) { + const item = asRecord(entry); + if (!item) continue; + if (kind === "findings" && !SEVERITIES.includes(str(item, "severity") as typeof SEVERITIES[number])) { + issues.push(...issue("findings-severity", `entry ${index + 1} needs an allowlisted severity`, "use critical, high, medium, or low")); + } + if (kind === "stats") { + const direction = str(item, "direction"); + if (direction !== undefined && direction !== "up" && direction !== "down") { + issues.push(...issue("stats-direction", `entry ${index + 1} direction must be up or down`, "correct or remove direction")); + } + } + if (kind === "compare") { + const annotations = item["annotations"]; + if (annotations !== undefined && (!Array.isArray(annotations) || annotations.some((note) => typeof note !== "string"))) { + issues.push(...issue("compare-annotations", `entry ${index + 1} annotations must be strings`, "replace non-string annotations")); + } + } + } + return issues; + } + const item = asRecord(value); + if (!item) return issue(`${kind}-shape`, "expected a JSON object", "provide the documented object schema"); + if (kind === "callout") { + const tone = str(item, "tone"); + return tone !== undefined && !ITEM_TONES.includes(tone as typeof ITEM_TONES[number]) ? issue("callout-tone", "tone is not allowlisted", "use good, bad, warn, info, or neutral") : []; + } + if (kind === "progress") { + const done = num(item, "done"); + const total = num(item, "total"); + return done === undefined || total === undefined || total <= 0 || done < 0 || done > total ? issue("progress-range", "done and total must describe a finite range", "use 0 <= done <= total and total > 0") : []; + } + if (kind === "copy") return typeof item["text"] !== "string" ? issue("copy-text", "copy text is missing", "add string 'text'") : []; + if (kind === "decisions") { + if (!Array.isArray(item["questions"])) return issue("decisions-questions", "questions must be an array", "add the documented questions array"); + const ids = new Set<string>(); + const issues: ComponentIssue[] = []; + for (const [index, questionValue] of item["questions"].entries()) { + const question = asRecord(questionValue); + if (!question || !str(question, "id") || !str(question, "question") || !Array.isArray(question["options"])) { + issues.push(...issue("decisions-question", `question ${index + 1} needs id, question, and options`, "complete every question object")); + continue; + } + const id = str(question, "id")!; + if (ids.has(id)) issues.push(...issue("decisions-id", `question id '${id}' is duplicated`, "assign stable unique ids")); + ids.add(id); + for (const [optionIndex, optionValue] of question["options"].entries()) { + const option = asRecord(optionValue); + if (!option || !str(option, "id") || !str(option, "label")) { + issues.push(...issue("decisions-option", `question ${index + 1} option ${optionIndex + 1} needs id and label`, "complete every option object")); + } + } + } + return issues; + } + if (kind === "table") { + if (!Array.isArray(item["columns"]) || !Array.isArray(item["rows"])) return issue("table-shape", "columns and rows must be arrays", "add both documented arrays"); + const keys = new Set<string>(); + const issues: ComponentIssue[] = []; + for (const [index, columnValue] of item["columns"].entries()) { + const column = asRecord(columnValue); + const key = column && str(column, "key"); + if (!column || !key || !str(column, "label")) { + issues.push(...issue("table-column", `column ${index + 1} needs key and label`, "complete every column object")); + continue; + } + if (keys.has(key)) issues.push(...issue("table-key", `column key '${key}' is duplicated`, "assign unique column keys")); + keys.add(key); + const type = str(column, "type"); + if (type !== undefined && type !== "num") issues.push(...issue("table-type", `column ${index + 1} type is unsupported`, "use 'num' or omit type")); + } + for (const [index, row] of item["rows"].entries()) { + if (asRecord(row) === undefined) issues.push(...issue("table-row", `row ${index + 1} must be an object`, "replace non-object rows")); + } + return issues; + } + return []; +} + function renderStats(spec: unknown): string { if (!Array.isArray(spec)) return errorBox("stats", "expected a JSON array"); const cards = spec.map((entry) => { @@ -315,6 +441,8 @@ function renderTable(spec: unknown): string { } export function renderComponent(kind: ComponentKind, json: string, id?: string): string { + const issues = validateComponent(kind, json); + if (issues.length > 0) return errorBox(kind, issues[0].reason); if (kind === "diff") return renderDiff(json); if (kind === "mermaid") return renderMermaid(json); let spec: unknown; diff --git a/src/plugin.ts b/src/plugin.ts index bda1550..8105a9a 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -5,12 +5,13 @@ import { lstat, mkdir, readFile, writeFile } from "node:fs/promises"; import { tool, type Hooks, type Plugin } from "@opencode-ai/plugin"; import { ArtifactTooLargeError, - renderPortableArtifact, + renderArtifact, renderRawHtml, type RenderedArtifact, } from "./render.ts"; -import { AssetPreflightError } from "./assets.ts"; +import { AssetPreflightError, type PortableAssets } from "./assets.ts"; import { parseDocument } from "./markdown.ts"; +import { formatPreflight, preflightDocument, trustedHtmlDiagnostic, type AuthoringDiagnostic } from "./preflight.ts"; import { FilePublisher, slugify, StaleArtifactError } from "./publisher.ts"; import { GitHubPagesPublisher } from "./github-pages.ts"; import { CloudflarePublisher } from "./cloudflare-publisher.ts"; @@ -196,10 +197,21 @@ export const ArtifactsPlugin: Plugin = async (_input, options) => { if (findings.length > 0 && args.force !== true) { return `Publish blocked: the content contains credential-looking strings: ${formatFindings(findings)}. If these are intentional (e.g. redacted examples), call again with force: true.`; } + let preflightWarnings: AuthoringDiagnostic[] = []; + let portableAssets: PortableAssets | undefined; + if (args.format === "html") { + preflightWarnings = [trustedHtmlDiagnostic()]; + } else { + const preflight = await preflightDocument(args.markdown, { worktreeRoot: workRoot(ctx) }); + const errors = preflight.diagnostics.filter((item) => item.severity === "error"); + if (errors.length > 0 || preflight.omitted > 0) return formatPreflight(preflight); + preflightWarnings = preflight.diagnostics; + portableAssets = preflight.assets; + } const rendered: RenderedArtifact = args.format === "html" ? renderRawHtml(args.markdown, args.title ? { title: args.title } : {}) - : await renderPortableArtifact(args.markdown, workRoot(ctx)); + : renderArtifact(args.markdown, { assets: portableAssets }); const finalFindings = scanSensitive(rendered.html); if (finalFindings.length > 0 && args.force !== true) { return `Publish blocked: the final portable bytes contain credential-looking strings: ${formatFindings(finalFindings)}. If these are intentional, call again with force: true.`; @@ -209,7 +221,7 @@ export const ArtifactsPlugin: Plugin = async (_input, options) => { permission: "artifact_publish", patterns: [slug], always: ["*"], - metadata: { title, slug }, + metadata: { title, slug, format: args.format ?? "markdown", trustedHtml: args.format === "html" }, }); const localDir = join(workRoot(ctx), ".opencode", "artifacts"); @@ -282,7 +294,8 @@ export const ArtifactsPlugin: Plugin = async (_input, options) => { hash: result.hash, }, }); - return `Artifact published to ${result.path}${result.url ? ` — live at ${result.url}` : ""} (gallery: ${result.gallery}, hash: ${result.hash})`; + const warning = preflightWarnings.length === 0 ? "" : `\nPreflight warnings: ${preflightWarnings.map((item) => `${item.code} at ${item.line}:${item.column}`).join(", ")}`; + return `Artifact published to ${result.path}${result.url ? ` — live at ${result.url}` : ""} (gallery: ${result.gallery}, hash: ${result.hash})${warning}`; } catch (err) { if (err instanceof ArtifactTooLargeError) { return `Artifact too large: ${err.message}`; diff --git a/src/preflight.ts b/src/preflight.ts new file mode 100644 index 0000000..e0c0da1 --- /dev/null +++ b/src/preflight.ts @@ -0,0 +1,198 @@ +import MarkdownIt from "markdown-it"; +import { validateComponent, COMPONENT_KINDS, type ComponentKind } from "./components.ts"; +import { resolvePortableAssets, AssetPreflightError, type PortableAssets } from "./assets.ts"; +import { validateChartSpec } from "./render.ts"; +import { slugify } from "./text.ts"; + +export type DiagnosticSeverity = "error" | "warning"; +export type DiagnosticSource = "frontmatter" | "component" | "chart" | "markdown" | "asset" | "mode"; + +export interface AuthoringDiagnostic { + code: string; + severity: DiagnosticSeverity; + source: DiagnosticSource; + line: number; + column: number; + message: string; + nextAction: string; +} + +export interface PreflightOptions { + worktreeRoot?: string; + maxDiagnostics?: number; + maxDiagnosticBytes?: number; +} + +export interface PreflightResult { + diagnostics: AuthoringDiagnostic[]; + omitted: number; + assets?: PortableAssets; +} + +const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/; +const KNOWN_FRONTMATTER = new Set(["title", "icon", "description", "theme", "source", "font"]); +const THEMES = new Set(["default", "report", "ops", "editorial"]); +const CHART_KINDS = new Set(["vega-lite", "vega", "echarts"]); +const ALERT_KINDS = new Set(["NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"]); + +function clean(value: string): string { + const redacted = value + .replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{12,}\b/g, "[REDACTED]") + .replace(/\bsk-(?:ant-)?[A-Za-z0-9_-]{16,}\b/g, "[REDACTED]") + .replace(/-----BEGIN [^-]*PRIVATE KEY-----/g, "[REDACTED]"); + return redacted.length <= 240 ? redacted : `${redacted.slice(0, 237)}...`; +} + +function diagnostic(code: string, severity: DiagnosticSeverity, source: DiagnosticSource, line: number, message: string, nextAction: string): AuthoringDiagnostic { + return { code, severity, source, line, column: 1, message: clean(message), nextAction: clean(nextAction) }; +} + +function frontmatterDiagnostics(markdown: string): AuthoringDiagnostic[] { + const match = markdown.match(FRONTMATTER_RE); + if (!match) return []; + const seen = new Set<string>(); + const output: AuthoringDiagnostic[] = []; + for (const [index, line] of match[1].split(/\r?\n/).entries()) { + if (line.trim() === "") continue; + const parsed = line.match(/^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/); + if (!parsed) { + output.push(diagnostic("frontmatter-syntax", "error", "frontmatter", index + 2, "frontmatter line is not key: value", "use a documented key and scalar value")); + continue; + } + const key = parsed[1]; + if (seen.has(key)) output.push(diagnostic("frontmatter-duplicate", "error", "frontmatter", index + 2, `frontmatter key '${key}' is duplicated`, "keep one value for each key")); + seen.add(key); + if (!KNOWN_FRONTMATTER.has(key)) output.push(diagnostic("frontmatter-unknown", "warning", "frontmatter", index + 2, `frontmatter key '${key}' is ignored`, "remove it or use a documented key")); + if (key === "theme" && parsed[2] !== "" && !THEMES.has(parsed[2].trim())) output.push(diagnostic("theme-unknown", "warning", "frontmatter", index + 2, "unknown theme falls back to default", "use default, report, ops, or editorial")); + } + return output; +} + +function markdownDiagnostics(markdown: string): AuthoringDiagnostic[] { + const md = new MarkdownIt({ html: false, linkify: true }); + const tokens = md.parse(markdown, {}); + const output: AuthoringDiagnostic[] = []; + const anchors = new Map<string, number>(); + for (let index = 0; index < tokens.length; index++) { + const token = tokens[index]; + const line = (token.map?.[0] ?? 0) + 1; + if (token.type === "fence") { + const kind = token.info.trim().split(/\s+/)[0] ?? ""; + if (COMPONENT_KINDS.has(kind)) { + for (const componentIssue of validateComponent(kind as ComponentKind, token.content)) { + output.push(diagnostic(componentIssue.code, "error", "component", line, `component '${kind}' is invalid: ${componentIssue.reason}`, componentIssue.nextAction)); + } + } else if (CHART_KINDS.has(kind)) { + const result = validateChartSpec({ kind: kind as "vega-lite" | "vega" | "echarts", json: token.content }); + if (result.error) output.push(diagnostic(result.code ?? `${kind}-invalid`, "error", "chart", line, `chart '${kind}' is invalid`, "provide valid JSON and a valid chart schema")); + } + } + if (token.type === "heading_open") { + const inline = tokens[index + 1]; + const anchor = slugify(inline?.content ?? ""); + const first = anchors.get(anchor); + if (anchor !== "" && first !== undefined) output.push(diagnostic("anchor-duplicate", "error", "markdown", line, `heading anchor '${anchor}' duplicates line ${first}`, "rename one heading so every anchor is unique")); + else if (anchor !== "") anchors.set(anchor, line); + } + } + for (const [index, line] of markdown.split(/\r?\n/).entries()) { + const alert = line.match(/^\s*>\s*\[!([A-Z]+)\]/); + if (alert && !ALERT_KINDS.has(alert[1])) output.push(diagnostic("alert-unknown", "warning", "markdown", index + 1, `alert kind '${alert[1]}' is not supported`, "use NOTE, TIP, IMPORTANT, WARNING, or CAUTION")); + if (/^\s*[-*+]\s+\[[^ xX\]]\]/.test(line)) output.push(diagnostic("task-marker-invalid", "warning", "markdown", index + 1, "task marker is not [ ] or [x]", "use [ ] for open or [x] for complete")); + } + return output; +} + +interface AssetProbe { + markdown: string; + line: number; +} + +function assetProbes(markdown: string): AssetProbe[] { + const md = new MarkdownIt({ html: false, linkify: true }); + const probes: AssetProbe[] = []; + const visit = (tokens: readonly { type: string; map: [number, number] | null; children?: unknown; content: string; attrGet(name: string): string | null }[], inheritedLine: number): void => { + for (const token of tokens) { + const line = (token.map?.[0] ?? inheritedLine - 1) + 1; + if (token.type === "image") { + const source = token.attrGet("src") ?? ""; + const decorative = token.attrGet("title")?.trim().toLowerCase() === "decorative"; + probes.push({ markdown: `![${token.content}](<${source}>${decorative ? ' "decorative"' : ""})`, line }); + } + if (Array.isArray(token.children)) visit(token.children as readonly { type: string; map: [number, number] | null; children?: unknown; content: string; attrGet(name: string): string | null }[], line); + } + }; + visit(md.parse(markdown, {}), 1); + const frontmatter = markdown.match(FRONTMATTER_RE); + if (frontmatter) { + for (const [index, line] of frontmatter[1].split(/\r?\n/).entries()) { + const font = line.match(/^font\s*:\s*(.*?)\s*$/); + if (font?.[1]) probes.push({ markdown: `---\nfont: ${font[1]}\n---\nx`, line: index + 2 }); + } + } + return probes; +} + +async function preflightAssets(markdown: string, root: string): Promise<{ diagnostics: AuthoringDiagnostic[]; assets?: PortableAssets }> { + const diagnostics: AuthoringDiagnostic[] = []; + for (const probe of assetProbes(markdown)) { + try { + await resolvePortableAssets(probe.markdown, root); + } catch (error) { + if (!(error instanceof AssetPreflightError)) throw error; + diagnostics.push(diagnostic(`asset-${error.code}`, "error", "asset", probe.line, error.message, error.nextAction)); + } + } + if (diagnostics.length > 0) return { diagnostics }; + try { + return { diagnostics, assets: await resolvePortableAssets(markdown, root) }; + } catch (error) { + if (!(error instanceof AssetPreflightError)) throw error; + return { diagnostics: [diagnostic(`asset-${error.code}`, "error", "asset", 1, error.message, error.nextAction)] }; + } +} + +function bounded(diagnostics: AuthoringDiagnostic[], options: PreflightOptions): { diagnostics: AuthoringDiagnostic[]; omitted: number } { + const maxCount = Math.max(1, options.maxDiagnostics ?? 50); + const maxBytes = Math.max(512, options.maxDiagnosticBytes ?? 16 * 1024); + const kept: AuthoringDiagnostic[] = []; + let bytes = 2; + for (const item of diagnostics.sort((a, b) => a.line - b.line || a.column - b.column)) { + const itemBytes = Buffer.byteLength(JSON.stringify(item), "utf8") + (kept.length === 0 ? 0 : 1); + if (kept.length >= maxCount || bytes + itemBytes > maxBytes) break; + kept.push(item); + bytes += itemBytes; + } + let omitted = diagnostics.length - kept.length; + if (omitted > 0) { + let marker = diagnostic("diagnostics-omitted", "error", "markdown", kept.at(-1)?.line ?? 1, `${omitted} additional diagnostics were omitted by the report limit`, "fix reported errors, then run preflight again"); + while (kept.length >= maxCount || Buffer.byteLength(JSON.stringify([...kept, marker]), "utf8") > maxBytes) { + if (kept.length === 0) break; + kept.pop(); + omitted++; + marker = diagnostic("diagnostics-omitted", "error", "markdown", kept.at(-1)?.line ?? 1, `${omitted} additional diagnostics were omitted by the report limit`, "fix reported errors, then run preflight again"); + } + kept.push(marker); + } + return { diagnostics: kept, omitted }; +} + +export async function preflightDocument(markdown: string, options: PreflightOptions = {}): Promise<PreflightResult> { + const all = [...frontmatterDiagnostics(markdown), ...markdownDiagnostics(markdown)]; + let assets: PortableAssets | undefined; + if (options.worktreeRoot !== undefined) { + const assetResult = await preflightAssets(markdown, options.worktreeRoot); + all.push(...assetResult.diagnostics); + assets = assetResult.assets; + } + const result = bounded(all, options); + return { ...result, ...(assets === undefined ? {} : { assets }) }; +} + +export function trustedHtmlDiagnostic(): AuthoringDiagnostic { + return diagnostic("trusted-html-mode", "warning", "mode", 1, "trusted HTML executes page-authored markup outside Markdown guarantees", "review the complete HTML and permission prompt before publishing"); +} + +export function formatPreflight(result: PreflightResult): string { + return JSON.stringify({ error: "authoring-preflight", diagnostics: result.diagnostics, omitted: result.omitted }, null, 2); +} diff --git a/src/render.ts b/src/render.ts index 20a7376..342d1bc 100644 --- a/src/render.ts +++ b/src/render.ts @@ -667,20 +667,24 @@ export function emojiFaviconDataUri(icon: string): string { return `data:image/svg+xml,${encodeURIComponent(svg)}`; } +export function validateChartSpec(chart: ChartSpec): ResolvedChart & { code?: string } { + const kind: ResolvedKind = chart.kind === "echarts" ? "echarts" : "vega"; + try { + const parsed: unknown = JSON.parse(chart.json); + if (kind === "echarts") return { kind, spec: parsed }; + if (chart.kind === "vega-lite") { + const compiled = compileVegaLite(parsed as Parameters<typeof compileVegaLite>[0]); + return { kind, spec: compiled.spec }; + } + return { kind, spec: parsed }; + } catch (err) { + return { kind, code: `${chart.kind}-invalid`, error: err instanceof Error ? err.message : String(err) }; + } +} + function resolveCharts(charts: ChartSpec[]): ResolvedChart[] { return charts.map((chart) => { - const kind: ResolvedKind = chart.kind === "echarts" ? "echarts" : "vega"; - try { - const parsed: unknown = JSON.parse(chart.json); - if (kind === "echarts") return { kind, spec: parsed }; - if (chart.kind === "vega-lite") { - const compiled = compileVegaLite(parsed as Parameters<typeof compileVegaLite>[0]); - return { kind, spec: compiled.spec }; - } - return { kind, spec: parsed }; - } catch (err) { - return { kind, error: err instanceof Error ? err.message : String(err) }; - } + return validateChartSpec(chart); }); } diff --git a/test/preflight.test.ts b/test/preflight.test.ts new file mode 100644 index 0000000..cd829ac --- /dev/null +++ b/test/preflight.test.ts @@ -0,0 +1,178 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import type { PluginInput, ToolContext } from "@opencode-ai/plugin"; +import { preflightDocument, formatPreflight, trustedHtmlDiagnostic } from "../src/preflight.ts"; +import { COMPONENT_KINDS, renderComponent, validateComponent } from "../src/components.ts"; +import { renderArtifact } from "../src/render.ts"; +import { ArtifactsPlugin } from "../src/plugin.ts"; + +const execFileAsync = promisify(execFile); +const ROOT = resolve(import.meta.dirname, ".."); + +async function withTemp(run: (root: string) => Promise<void>): Promise<void> { + const root = await mkdtemp(join(tmpdir(), "authoring-preflight-")); + try { await run(root); } finally { await rm(root, { recursive: true, force: true }); } +} + +const MULTI_ERROR = [ + "---", + "title: Example", + "not valid frontmatter", + "---", + "```stats", + "{secret: ghp_0123456789abcdefABCDEF0123456789}", + "```", + "```progress", + '{"done": 5, "total": 2}', + "```", + "```mermaid", + "```", + "## Same", + "## Same", + "> [!UNKNOWN] nope", +].join("\n"); +const MULTI_ERROR_SAFE = MULTI_ERROR.replace("ghp_0123456789abcdefABCDEF0123456789", "redacted-token"); + +test("preflight aggregates stable located diagnostics in source order without payloads", async () => { + const first = await preflightDocument(MULTI_ERROR, { worktreeRoot: ROOT }); + const second = await preflightDocument(MULTI_ERROR, { worktreeRoot: ROOT }); + assert.deepEqual(first, second); + assert.deepEqual(first.diagnostics.map((item) => item.code), [ + "frontmatter-syntax", + "stats-json", + "progress-range", + "mermaid-empty", + "anchor-duplicate", + "alert-unknown", + ]); + assert.deepEqual(first.diagnostics.map((item) => item.line), [3, 5, 8, 11, 14, 15]); + const formatted = formatPreflight(first); + assert.ok(!formatted.includes("ghp_0123456789abcdefABCDEF0123456789")); + assert.ok(Buffer.byteLength(formatted) < 16 * 1024); +}); + +test("diagnostic count truncation ends with an explicit omitted marker", async () => { + const result = await preflightDocument(MULTI_ERROR, { maxDiagnostics: 3 }); + assert.equal(result.diagnostics.length, 3); + assert.equal(result.diagnostics.at(-1)?.code, "diagnostics-omitted"); + assert.ok(result.omitted >= 4); +}); + +test("diagnostic byte truncation stays bounded and ends with an omitted marker", async () => { + const result = await preflightDocument(MULTI_ERROR, { maxDiagnosticBytes: 1024 }); + assert.equal(result.diagnostics.at(-1)?.code, "diagnostics-omitted"); + assert.ok(result.omitted > 0); + assert.ok(Buffer.byteLength(JSON.stringify(result.diagnostics)) <= 1024); +}); + +test("preflight reports each independent asset reference at its source line", async () => { + await withTemp(async (root) => { + const result = await preflightDocument("![One](missing.png)\n\n![Two](https://example.com/two.png)\n\n![Again](missing.png)", { worktreeRoot: root }); + assert.deepEqual(result.diagnostics.map((item) => item.code), [ + "asset-missing-asset", + "asset-external-asset", + "asset-missing-asset", + ]); + assert.deepEqual(result.diagnostics.map((item) => item.line), [1, 3, 5]); + }); +}); + +test("component preflight and inline fallback share validation", () => { + const cases: Array<[Parameters<typeof validateComponent>[0], string, string]> = [ + ["stats", '[{"label":"x"}]', "stats-value"], + ["timeline", '[{"title":"x"}]', "timeline-time"], + ["findings", '[{"title":"x","severity":"urgent"}]', "findings-severity"], + ["compare", '[{"title":"x","annotations":[1]}]', "compare-annotations"], + ["callout", '{"tone":"loud"}', "callout-tone"], + ["progress", '{"done":2,"total":1}', "progress-range"], + ["copy", '{}', "copy-text"], + ["diff", "", "diff-empty"], + ["mermaid", "", "mermaid-empty"], + ["decisions", '{"questions":[{"id":"q","question":"Q","options":[{}]}]}', "decisions-option"], + ["table", '{"columns":[{"key":"x","label":"X"},{"key":"x","label":"Y"}],"rows":[]}', "table-key"], + ]; + for (const [kind, source, code] of cases) { + assert.equal(validateComponent(kind, source)[0]?.code, code); + assert.match(renderComponent(kind, source), /chart-error/); + } + assert.deepEqual(new Set(cases.map(([kind]) => kind)), COMPONENT_KINDS); +}); + +test("chart preflight and inline fallback share validation", async () => { + const markdown = "```vega-lite\n{\"mark\":\"not-a-mark\"}\n```"; + const result = await preflightDocument(markdown); + assert.equal(result.diagnostics[0]?.code, "vega-lite-invalid"); + assert.match(renderArtifact(markdown).html, /chart-error/); +}); + +test("every checked-in example has zero preflight errors", async () => { + for (const name of await readdir(join(ROOT, "examples", "patterns"))) { + if (!name.endsWith(".md")) continue; + const markdown = await readFile(join(ROOT, "examples", "patterns", name), "utf8"); + const result = await preflightDocument(markdown, { worktreeRoot: ROOT }); + assert.deepEqual(result.diagnostics.filter((item) => item.severity === "error"), [], name); + } +}); + +test("plugin returns all preflight errors before permission or writes", async () => { + await withTemp(async (root) => { + const publish = (await ArtifactsPlugin({} as unknown as PluginInput)).tool?.artifact_publish; + assert.ok(publish); + let asked = false; + const context: ToolContext = { + sessionID: "preflight", messageID: "preflight", agent: "test", directory: root, worktree: root, + abort: new AbortController().signal, metadata: () => {}, ask: async () => { asked = true; }, + }; + const output = String(await publish.execute({ markdown: MULTI_ERROR_SAFE }, context)); + assert.match(output, /authoring-preflight/); + for (const code of ["frontmatter-syntax", "stats-json", "progress-range", "mermaid-empty", "anchor-duplicate", "alert-unknown"]) { + assert.match(output, new RegExp(code)); + } + assert.equal(asked, false); + await assert.rejects(readFile(join(root, ".opencode", "artifacts", "manifest.json"))); + }); +}); + +test("CLI refuses invalid authoring before creating an output file", async () => { + await withTemp(async (root) => { + const input = join(root, "bad.md"); + const output = join(root, "bad.html"); + await writeFile(input, MULTI_ERROR_SAFE); + await assert.rejects(execFileAsync(process.execPath, [join(ROOT, "src", "cli.ts"), "render", input, "-o", output], { cwd: root }), (error: unknown) => { + if (typeof error !== "object" || error === null || !("stderr" in error)) return false; + const stderr = String(error.stderr); + return ["frontmatter-syntax", "stats-json", "progress-range", "mermaid-empty", "anchor-duplicate", "alert-unknown"].every((code) => stderr.includes(code)); + }); + await assert.rejects(readFile(output)); + }); +}); + +test("trusted HTML disclosure is stable and actionable", () => { + const warning = trustedHtmlDiagnostic(); + assert.equal(warning.code, "trusted-html-mode"); + assert.equal(warning.severity, "warning"); + assert.match(warning.nextAction, /review/i); +}); + +test("plugin publishes warnings visibly and marks trusted HTML permission metadata", async () => { + await withTemp(async (root) => { + const publish = (await ArtifactsPlugin({} as unknown as PluginInput)).tool?.artifact_publish; + assert.ok(publish); + const asked: Array<Parameters<ToolContext["ask"]>[0]> = []; + const context: ToolContext = { + sessionID: "warnings", messageID: "warnings", agent: "test", directory: root, worktree: root, + abort: new AbortController().signal, metadata: () => {}, ask: async (input) => { asked.push(input); }, + }; + const markdownOutput = String(await publish.execute({ markdown: "# Warning\n\n> [!UNKNOWN] visible" }, context)); + assert.match(markdownOutput, /Preflight warnings: alert-unknown at 3:1/); + const htmlOutput = String(await publish.execute({ markdown: "<main>trusted</main>", title: "Trusted", format: "html" }, context)); + assert.match(htmlOutput, /Preflight warnings: trusted-html-mode at 1:1/); + assert.equal(asked.at(-1)?.metadata?.trustedHtml, true); + assert.equal(asked.at(-1)?.metadata?.format, "html"); + }); +}); From d3cda52da583b0c644a2bcc35a287e735944fcef Mon Sep 17 00:00:00 2001 From: bitgorust <bitgorust@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:59:10 +0200 Subject: [PATCH 06/10] feat: add bounded renderer design tokens --- README.md | 6 + docs/component-spec.md | 15 +- docs/engineering-principles.md | 3 + .../goal-3-design-tokens-2026-08-17.md | 42 +++ ...al-3-design-tokens-desktop-2026-08-17.json | 44 +++ ...oal-3-design-tokens-desktop-2026-08-17.png | Bin 0 -> 108251 bytes ...oal-3-design-tokens-mobile-2026-08-17.json | 44 +++ ...goal-3-design-tokens-mobile-2026-08-17.png | Bin 0 -> 42371 bytes docs/redistribution-inventory.json | 18 + docs/security.md | 7 + examples/patterns/design-tokens.md | 49 +++ scripts/portable-browser-evidence.ts | 19 +- skills/artifact-pages/SKILL.md | 5 + skills/artifact-pages/reference/components.md | 16 + .../change.json | 15 +- .../delta.md | 0 .../evidence.md | 22 ++ .../proposal.md | 0 .../tasks.md | 8 + .../renderer-design-tokens/evidence.md | 13 - specs/changes/renderer-design-tokens/tasks.md | 8 - specs/current/portable-renderer.spec.md | 21 +- src/cli.ts | 5 +- src/design-tokens.ts | 352 ++++++++++++++++++ src/markdown.ts | 21 +- src/plugin.ts | 5 +- src/preflight.ts | 20 +- src/render.ts | 53 ++- test/design-tokens.test.ts | 219 +++++++++++ 29 files changed, 976 insertions(+), 54 deletions(-) create mode 100644 docs/evidence/renderer/goal-3-design-tokens-2026-08-17.md create mode 100644 docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.json create mode 100644 docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.png create mode 100644 docs/evidence/renderer/goal-3-design-tokens-mobile-2026-08-17.json create mode 100644 docs/evidence/renderer/goal-3-design-tokens-mobile-2026-08-17.png create mode 100644 examples/patterns/design-tokens.md rename specs/{changes/renderer-design-tokens => archive/2026-08-17-renderer-design-tokens}/change.json (58%) rename specs/{changes/renderer-design-tokens => archive/2026-08-17-renderer-design-tokens}/delta.md (100%) create mode 100644 specs/archive/2026-08-17-renderer-design-tokens/evidence.md rename specs/{changes/renderer-design-tokens => archive/2026-08-17-renderer-design-tokens}/proposal.md (100%) create mode 100644 specs/archive/2026-08-17-renderer-design-tokens/tasks.md delete mode 100644 specs/changes/renderer-design-tokens/evidence.md delete mode 100644 specs/changes/renderer-design-tokens/tasks.md create mode 100644 src/design-tokens.ts create mode 100644 test/design-tokens.test.ts diff --git a/README.md b/README.md index f04289b..616c845 100644 --- a/README.md +++ b/README.md @@ -173,12 +173,18 @@ Full reference: [`docs/component-spec.md`](docs/component-spec.md). Short versio - **Markdown extras**: GitHub alerts (`> [!WARNING]` etc.), task lists, heading anchors, `##` sections become cards - **Local images**: ordinary `![meaningful alt](path/to/image.png)` resolves from the worktree root and embeds PNG/JPEG/GIF/WebP or constrained static SVG. Use the exact title `"decorative"` with empty alt only for an intentionally decorative image. URLs, absolute paths, traversal, and symlinks are refused. - **Local fonts**: `font: path/to/project.woff2` uses the same contained, MIME-checked pipeline and a generated `@font-face`; it never permits a viewer network request or arbitrary CSS. +- **Bounded design tokens**: `.opencode/artifact-tokens.json` supplies project defaults and one + version-1 `design-tokens` JSON fence supplies prompt overrides. Only documented + color, font, spacing, radius, and density slots are accepted; prompt > project > theme > + built-in precedence and provenance are recorded in the portable page. - CLI/plugin publication preflights the whole document and refuses all detected errors before permission or writes. Standalone rendering still degrades broken specs to escaped inline error boxes for resilient inspection; warnings remain visible on successful publication. Worked examples for every canonical pattern: [`examples/patterns/`](examples/patterns/) with browser-verified screenshots in [`docs/evidence/patterns/`](docs/evidence/patterns/). +The bounded-token fixture and desktop/mobile offline observations are in +[`docs/evidence/renderer/goal-3-design-tokens-2026-08-17.md`](docs/evidence/renderer/goal-3-design-tokens-2026-08-17.md). ## Sharing and hosting diff --git a/docs/component-spec.md b/docs/component-spec.md index 6bdab93..ca66ee5 100644 --- a/docs/component-spec.md +++ b/docs/component-spec.md @@ -8,7 +8,7 @@ Reference: the official [Claude Code Artifact guide](https://code.claude.com/doc `docs/page-quality-benchmark.md` (comparative quality gate). Official media remains link-only unless explicit redistribution authority is recorded. -## Design tokens (adapted from the official visual reference) +## Built-in design tokens (adapted from the official visual reference) ``` --page-bg: #e9edf2 (light gray-blue) @@ -36,6 +36,19 @@ unless explicit redistribution authority is recorded. Dark mode: same hues, backgrounds shifted (page `#151a21`, card `#1f2630`, ink `#e5e7eb`), via `color-scheme: light dark` + `@media (prefers-color-scheme: dark)` overrides. +### Bounded project and prompt overrides + +The renderer discovers only `.opencode/artifact-tokens.json` for project tokens. A document +may contain one `design-tokens` JSON fence for explicit prompt-level values. Both use +`{"schemaVersion":1,"tokens":{...}}`; the shipped authoring reference owns the exact key and +enum list. Precedence is prompt > project > curated theme > built-in defaults. + +Each source is capped at 8 KiB, parsed atomically, contrast checked against its effective lower +layers, and emitted in deterministic order through fixed CSS-variable slots. Unknown keys, +non-hex colors, arbitrary font strings, selectors, declarations, URLs, markup, imports, and +expressions refuse publication before permission or writes. The portable page retains named +per-token provenance. Trusted HTML does not participate in this bounded token path. + ## Page layout - Page background `--page-bg`; content column max-width 1080px. diff --git a/docs/engineering-principles.md b/docs/engineering-principles.md index a114e90..b2acbfd 100644 --- a/docs/engineering-principles.md +++ b/docs/engineering-principles.md @@ -121,6 +121,9 @@ collectively exhausted by [`docs/product-spec.md`](product-spec.md), not repeate - Declarative publication validates the complete document before permission or writes. Its component/chart schemas are shared with the renderer, diagnostics are bounded and redacted, and standalone rendering retains escaped inline fallbacks for inspection. +- Visual configuration follows prompt > bounded project file > curated theme > built-in + precedence. Sources are versioned, size/type/contrast checked atomically, retain per-token + provenance, and can populate only fixed CSS-variable slots—never CSS syntax or code. - The portable page is the long-term compatibility layer. Services are progressive enhancement; no page-view dependency, account, package runtime, or network is required. - Schemas, CLI/tool contracts, component syntax, exports, routes, and host adapters evolve by diff --git a/docs/evidence/renderer/goal-3-design-tokens-2026-08-17.md b/docs/evidence/renderer/goal-3-design-tokens-2026-08-17.md new file mode 100644 index 0000000..3864722 --- /dev/null +++ b/docs/evidence/renderer/goal-3-design-tokens-2026-08-17.md @@ -0,0 +1,42 @@ +# Goal 3 bounded design-token evidence — 2026-08-17 + +## Scope + +This observation covers the checked-in `examples/patterns/design-tokens.md` fixture rendered +by the CLI and opened directly from `file://` in Chromium 151 with offline network emulation. +It verifies the real desktop and narrow-viewport surface for the approved +`renderer-design-tokens` packet; it is not a browser/OS support claim. + +## Reproduction + +1. Render the fixture with Node 24 using `opencode-artifacts render`. +2. Open the resulting portable HTML in `selenium/standalone-chromium` with CDP network + emulation set offline. +3. Capture computed token variables, provenance metadata, CSP, page overflow, browser console, + attempted requests, and screenshots at requested 1440×1600 and 390×1000 windows. + +The reusable capture script is `scripts/portable-browser-evidence.ts`. Chrome window chrome +left inner viewports of 1440×1457 and 390×857 respectively. + +## Result + +- Both surfaces computed the authored values: `#f5f1ff` page background, `#ffffff` surface, + `#211735` text, `#6d28d9` accent, `8px` radius, and the allowlisted serif stack. +- Both exposed `data-design-tokens`, the complete prompt provenance record, the named `report` + lower-precedence theme, and the unchanged strict on-disk CSP. +- Both reached `readyState=complete`, rendered one table, had no horizontal page overflow, + produced zero browser-console entries, and attempted zero HTTP(S) requests offline. +- The screenshots retain readable desktop composition and narrow single-column recomposition. + +Evidence files: + +- [desktop observation](goal-3-design-tokens-desktop-2026-08-17.json) +- [desktop screenshot](goal-3-design-tokens-desktop-2026-08-17.png) +- [mobile observation](goal-3-design-tokens-mobile-2026-08-17.json) +- [mobile screenshot](goal-3-design-tokens-mobile-2026-08-17.png) + +## Boundary + +This is one Linux-container Chromium observation. Native macOS/Windows, additional browsers, +assistive technology, print/PDF, localization, and performance claims remain governed by the +later Goal 3 accessibility and performance packets and the declared support matrix. diff --git a/docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.json b/docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.json new file mode 100644 index 0000000..9c27124 --- /dev/null +++ b/docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.json @@ -0,0 +1,44 @@ +{ + "capturedAt": "2026-08-17T15:51:26.616Z", + "browser": "Chromium 151 via selenium/standalone-chromium", + "offlineEmulation": true, + "usefulContentMs": 396, + "observations": { + "activeElement": "", + "bodyFontFamily": "Georgia, Charter, \"Times New Roman\", serif", + "chartVisuals": 0, + "computedDesign": { + "accent": "#6d28d9", + "pageBackground": "#f5f1ff", + "radius": "8px", + "surface": "#ffffff", + "text": "#211735" + }, + "csp": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'", + "decisionButtons": 0, + "designProvenance": "{\"pageBackground\":\"prompt\",\"surface\":\"prompt\",\"text\":\"prompt\",\"mutedText\":\"prompt\",\"border\":\"prompt\",\"accent\":\"prompt\",\"font\":\"prompt\",\"spacing\":\"prompt\",\"radius\":\"prompt\",\"density\":\"prompt\"}", + "designTokens": true, + "fontSetStatus": "loaded", + "horizontalOverflow": false, + "imageComplete": false, + "imageHash": null, + "imageNaturalWidth": 0, + "imageSource": null, + "keyboardSelected": false, + "pageBytes": 35349, + "pageTheme": "report", + "projectFontFaces": [], + "readyState": "complete", + "tableCaptions": [], + "tables": 1, + "viewport": { + "height": 1457, + "width": 1440 + } + }, + "browserLogs": [], + "requestUrls": [ + "file:///evidence/goal-3-design-tokens.html" + ], + "networkRequestUrls": [] +} diff --git a/docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.png b/docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.png new file mode 100644 index 0000000000000000000000000000000000000000..60166e4d3ccdae2e8ccebd18ac9f70ca71b9bad0 GIT binary patch literal 108251 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 literal 0 HcmV?d00001 diff --git a/docs/evidence/renderer/goal-3-design-tokens-mobile-2026-08-17.json b/docs/evidence/renderer/goal-3-design-tokens-mobile-2026-08-17.json new file mode 100644 index 0000000..3966a48 --- /dev/null +++ b/docs/evidence/renderer/goal-3-design-tokens-mobile-2026-08-17.json @@ -0,0 +1,44 @@ +{ + "capturedAt": "2026-08-17T15:51:29.178Z", + "browser": "Chromium 151 via selenium/standalone-chromium", + "offlineEmulation": true, + "usefulContentMs": 335, + "observations": { + "activeElement": "", + "bodyFontFamily": "Georgia, Charter, \"Times New Roman\", serif", + "chartVisuals": 0, + "computedDesign": { + "accent": "#6d28d9", + "pageBackground": "#f5f1ff", + "radius": "8px", + "surface": "#ffffff", + "text": "#211735" + }, + "csp": "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'", + "decisionButtons": 0, + "designProvenance": "{\"pageBackground\":\"prompt\",\"surface\":\"prompt\",\"text\":\"prompt\",\"mutedText\":\"prompt\",\"border\":\"prompt\",\"accent\":\"prompt\",\"font\":\"prompt\",\"spacing\":\"prompt\",\"radius\":\"prompt\",\"density\":\"prompt\"}", + "designTokens": true, + "fontSetStatus": "loaded", + "horizontalOverflow": false, + "imageComplete": false, + "imageHash": null, + "imageNaturalWidth": 0, + "imageSource": null, + "keyboardSelected": false, + "pageBytes": 35349, + "pageTheme": "report", + "projectFontFaces": [], + "readyState": "complete", + "tableCaptions": [], + "tables": 1, + "viewport": { + "height": 857, + "width": 390 + } + }, + "browserLogs": [], + "requestUrls": [ + "file:///evidence/goal-3-design-tokens.html" + ], + "networkRequestUrls": [] +} diff --git a/docs/evidence/renderer/goal-3-design-tokens-mobile-2026-08-17.png b/docs/evidence/renderer/goal-3-design-tokens-mobile-2026-08-17.png new file mode 100644 index 0000000000000000000000000000000000000000..00fa367b33c4f199666d27831fe733ec938b1868 GIT binary patch literal 42371 zcmce;WmFu|*XD~f9^8UMu;A`4!QEX#aBEx}m*50<3GM`U8VDBL-QC@7`v1<n_uH&> z*P1)GKXsq#t~zz9`kcLg&$BB+MM)YJnGhKY3JO(LMp6w53OWo53RV{p2GSCOi)IQ1 zg#jfiDW>6>b(#%lfTN5v_`uuNE&$h$*AMv9{y$&+4&VCE0RT806;{!<_MP*ObG-Q* z#2%6G;4YSjEe-*JmG*AXjfHy-Z|X=920%<wN=!`b2_;GuF^K$cKtc#PqF5dk1)?~{ zS6Rd$41gR~5IG<a(mX_F@^XJhy<Y&?s5Uq0t?`>a|37bw5b0H&G{t4bF$#~rR*Q-A z{pWWYm_<)<fE(RMF{B{!FK{AX#V`ung8uhAM-=<14mrN&H$T7kkyMu8;NY3*X&f9J z1x3Y7E?XMrcZr|1#*m);4n0c6!NqmESnn(>r0QucFE1w%aG#x>Eu{Uk9Y!vLi;0Ex zrBGV*`}ggI8sl`?Br1Sd`A;wyjCIrP|1n-Tl^O#1(yxpRz3+bF;xLKTDOw2=0wUo9 zLi(Ji`)~KK)ZWKBycsqtQ{7PA<j2au``tNsZhIdO((IHJF$$Tx!iKmtXBU_B2CJ$< z2_NM<f=NwH#q@IC98zIzmi6m9G{hHDQ*(O@Q*&;wXGD;MVcOl)hs)suqRe9_dgTH* z1&eJxFm7MT*zIik4C|n++gqUYL9&M!3CT$aL_&%EmJ)mahTxH!oSgs8V?-(}8$l-< zLH336ca=?=D&lqO7jap=cHhC|sAh*XPtWIRBe-u*$V@&j1ATNmczjLPO)ekDhoy^N z99rEs&lseHPRG+mX)E>0C8a;pV3fo*pbhD!C~0UcEF27$S82uwJ3g*|jK=Fau6CMU z_e4cS${|-TkX0JBoh_L)^Ej-(#Mu|V^VH)jBqgMjZ`5)hV_thumPZmDPVI!V7@qU< z*cf>1<{jadI{<mJaHE>VzaLjl;b|2!5oai5@e|XepCeDRtJUKvq_tbIQ_1NMpW92H z*$JSX38Z5QU0*v76vK}eh7~Ul{eiVQYw>uI{VvXa*FD8CsoU-5v=OLD(Svx6fEket z@ZNoYJ}V-U9?$OLwtgN03B;kF@H`zJk?d9WP}9&14qmMq{$aavja7e1u|6U&Y-<j^ zeZ+JjTLnoSu;>ff{#}H|G49WbGiW(^{(S68K#)%Uqt2P#(7Pd2PcsgUa{UD$N&pAf zx}rR;NYFg_klp<!LhQi7zIcoPC7ID%^Wj3U(|Jp4vZTi850VrOJQGQ#LK?e*tnRO8 z!&xp3Sdps??t(97hp^$1x9@AT4#xh7jY?_y-T7zHUOT8Ux1v18otHoG>{jyU#16(b z!N1)M^rbl{g@UJC7s5;IZFJdRd`%vP9gVx?Wo7krGlPLEJdTf#JtJ%qmTa~h^{nuH zs93Rz-aTSf1|sO=@LAa2%$3MmG8Bkx8ePc^4*lc!6RX)1XUm;$hx2zYgJ_vTpz%fi zI~%kkfpokK<KMr31Kh}DDs{VHZGiEUGYdI_pu%5&gbTNv44ch=O@0|f&o!>M^U<-| z9Sbj^Q6PEeyCh2OGwk^Od^TvAHF0LQFa`Y&tS;pDmsnk0LfxexezUeyATC$HEyjo# zFN@Wg^sC`c@!dstOIk*PH*jLu9EC^qBRIs<=mW(Km1|O(AE7*Vg_~GLNy)I?oWd$_ zB)PqlKL)V02C`ZUpb$D_=QxN7hYbn2IG}AjS)MI7+&Q0d*&IDyYEGPdX31(rdpUi0 z-)UGfGr3Bg5QKk72YirZWt{WZrZCU6+22$&$Epfm=ubYxUk;)hJGC0<oS82a#2?L8 z_qW<2&;ckS<WdUnD%?9gkNvh^&msTa9A0N==RDO)<K{nRw5V0qwp_Ife@llJjjKb( zEYQi;(6P4ie{HHBZ-7=4!)pL2;7ntyUzTx3eg3sD;8z092HL_NJfP1J9FjRM^-9~k z>mRMEv*vSMuiaU0@9C2m^z6xLH3V%=DtPpZs)~}ihHsRSmcfD}6E$Ef&mb_G|MMdp z7Ev?HucV}azxopo4Z*(Sy%B3ZZaR%l!iD)t2;Y%?5UaoN+6>7m%a<%b(cm8LKMwba zW=9FsPtS00*X*>;c|TmR+pLOWu%BcI&R7rbOQ?JvmC!>dDk`#KS?^p?-tw2!NvdfU zp{lfJ1Pc;#m$T@^;snJ@n#)b!D<X6X`c4=LK{3sTKjfNT=fAVpE`-o=NY7Q8bCoUt z+Wu79Z4&6Wd*w%#WA!_F6AAsW*r+xO>JClO5~ik(+14EMjLT*Brz7X!h*GNEj8ncz ziP;hv!fWv%$Hc~!K9HA_4Pv`vdXBLt$?+T>z9F5>I?XEli`*W#+;nJJZjT`FY2r2Z zt)fRl`M{^cPn!0aryTWTZccM<R+Ied45cRVb75w4UBM&23_h$7(+~@kMDgD$(YGXb zRW{UE8jG+?5wJ+<p^TTU&(&zK)?9ltX&)Q=$;0g>e51{DB}FKCh^lo$gZ}~&E2@&g zqMPT3HINBq49>^Ws++EYXWYYXg}9RRa^clE6gK?6<L!}AL-tV0wU#~XE;AuH-SGPs z_nCgLKYFvrC7{dmSnSGhTtxztUTOgWnvYL6@&V2y9}|Fd(071vOKiN+#)ODmShit) za*pfD#K^*89DTQQD-|L=6)}I`dBm*bI>yZ1G-*8FN;IJkKgW>k;TIpES16HLT=aB7 z*;{b*H%Qm^uyKlO^|X?B`D8YlMb^6P$OI1#^o&P>D}906va%_X^*g2Nj6j4iC|;sy zEeVxq!AaPev|OHqUm!>1EFmlDi$<PpTvKbSSI+tRW>-;65_>>`Nxqe}ZNLhm-h9gV z2JmH{(i6>C_#vP_DmA=`FXc0eAXmzB$1}cXHusqUe5`5ycSgL5zhh@hey$7il<|wT z_Ob`(_Dv4Fmn-D}{=?1L)A>6Da#VpP;^W0eE<;Lee9IX)wA@uWlrN|)TStH-19pE2 z2c>h8g~3hlc9)y0I<9*8l}O%ppnW~QlHFu&(<UPUfpz2t7xd^CofUx^^w!o^lJ*Ap zIbpBcFB<S-`9?zi9PZN?$YMhU)0I?KryDMwZ%KcmzEFNw%G!W?$Lwj{bSC9;nEV+y zQ)#PLX&};kstH0ub}(NsyNKPnrPA2owlfwNnI4uca+QfwZUgO1Jy%8LUBh+Lo@<-> zmpa3GBYfEEWIhnhqTc={7s-7l1Ea+eiu~Nx{>Pl#Eo*s9bU2MfV#qX!vOk#ST)XB@ z;$?#p_UW_S5dlHf3Kxq#Hhfs9ZDL|FrUB9GAvRpG`OJQY_mXrsINT$*@nUyzu}c`F zdcNUx&G+@QYoa*Db*X|eRw+~io|q=gvAMLeqnTaclYFu03ulYA&0N!Z3&`5K7`g8R zKZk9~sm9ow9k%3WeSiN`W3)G_jcNy)TrX@nv4HEnI@mJT<6XrvgzPao|0$PlpvaF6 zN*=l!p0?*f0=8$P7x^aYx%<NzRXhL{X8nuI>5@}cwOx?*PhlV{Mf&&2$d5Z0M2@9a zeWJ*XSC9R_nqOHK&-ASj?Ch$*l<*O@sTTp?WG%&u;^wso+s^mjpGk<(cu(^)_9rte z#f^A<`o_C*Aag+|`L60PlS7_dcvdrGS}P4D<;i1fHgF@3SiodxNZ2fFyj;I~zl{mS zzf4*bww+()lR|Xkh(`c-poj13PrglR0^#EB)*>Vv&mFBpdcU1>#S=8%aZ^kyd<1-f zS1pz`Zb(RUSr%Lj#y)u&&t?ATc_`QngTz4UPkv6zjh~qIs+rMEsF!p@Nh=ue9mU>I z1Qm16L+xG8Yc}vUOpQ)-eno!!HlX55Y5qP<htvJb_AHV_1w6a@^L{_ey-U!2(QY!< z9EDqvpq<rtvw|#qUeVfdA*RA(#SLh39d!Y9IMTppc0_qR<;UBw*P|Mo7<;cg#p&38 zvnMUx-{de8?QrL^`Lb-9s&|!%j~OB=$sR(~fTx&Q>$wovNG|IQ--azc&e6=S`^R1B zY2gyGif8MiaUf6UsoIw!2TFuIw^mT)IhMY>#*FmW^Td+zOM58d{P_42W2=1%IN(o& zwfEZegdyYb)I7|JqhJwjny*=g<Blp$fp5^fBb_WN=o9rmPu7=~{**?u_|l=>J+AN0 zWJwd{zXR{amfAiZ-{N$(9{EilTm^quR<C{U!kblqFcG*K5*E^jJjXR_F;2u2zhNE- zZKuGCM`ul^+}(bC9m;g8&@7uz;(5PlzcIdJ0_Y#44v;}5p0_<JPe1*_gAH|OT-%x_ zzE)O&-G7lj2W~?}T<x?xNMBb&{9==~EM-0A5SY$kIgO+mLyt69O@N(hzu4pnS(7H~ zx-4dbt{0`d2V2z_@Fom5I<>>c+EZq7a6Hit6b!eHw(5V(jjHazt04xx^3hYt{(c^) zvc8d8xvxy)waJM{r)YSKQi?16%`7sxJJ?_o_@EuRfjZcsQz8ctlaUG$QU|9x?f>%w z%0xv(xNFe7?4#yIy1xB0e6c{y7dmm7Y^Fsw?(}^xHk3wK?vEmuaT9CE-XmO|%a>IC z=n;%V(%4w$yg*^bH59UkrR+R=(Q-Rl*P8qjM2`6MO8w?>z?cOPK^BX*K?{Qw#Rwr+ zNx%S5ApR$}K!l-262k}*g=9bf-#3d34)KJEV*oG-EE;D=jCBk)B%}WGkpgayB@&nC z|MJoQZ=Tuz>AsyJ?OF`Zb4U<!Mf`DzNc6i+*O1f)^mNnJ=dfSkW38<T#MAqv(K6bB z@OKZ_JqsU~VwM-p!p~vPRoQwm*{nh0@zWHB36Lq;75Kr%|F*fF_}CJneh{ZJm#Eih z*U`~&bA4UrAF(ID09k)OMHCj|#Fr4xr!eVp6=4+Ol<Os|ciw^CZ;Liw0%|W2S$&f< zlQicv+*})$8{F(>)>Vy+luCUK3_3P{XlQV1+VlB-ck~tC?p+#LHR>*#44P8?W3T5% zR}heuRhbi*Q(fJZ(^cKrsM#K?R9DB2Lm(;r?mGPaw;3IPF1tLD%2T#lzwv0i3Lz-n z>Ce?A%B$4#SK|L>0iSPgucES79!Q+lZ7m$W=zzO)&A2lSRa8Wk`FWcP%Q=p1%0bT$ zm8Dd&GIEP6JaQ95h?nR;l;Bv5#XC&<yyJgH#y;Ke5!pvW5zRZCO_njuB8ZijiYPJ+ z?(@`a<j<&0t3i_RVvIu9a+w6tI4Sg8NfD0c=eES+ILE0vYj3@_5zU3*m)L1@(ad7y zj1%vVy3L?G6{6<qF6Sw@4mlG<g<P(P@_iY3c~x9VMMXG9Mw!*_7ti?yIEAj?30T?a zR5Ub>2K9F-CqgnMC6)$Vhgc*e^#@>XCnxSZ>GXP&u$B-{S4qyOF;e!*?&>bIQ1T-6 zZwU=veQ0`dI*jv8hD3HYa{&#Jk<+sTJN<ukULJ7T!o|hKyFsXITpV}?O%f&CmjxJ+ zb+w!f_9^A%*O6RNYeqf++*5okx$W8fPJX+GJ+owRx2|_>ML0o8api{q3<6Bh%pP|( zd~5#QPt(iV_t8uPJt;X&#n5t%FTG#lhy~ePj$iu_vU+q1$~h9Su*^r3e<i!y?U1Ko zZ{mwm$Ta?@uPkKA?H1P;&W3OPIcZ>SUX;;j=L-^^t!qpd_F-3;ewv{f1HE3RtC)Uu zd5?6tQVf87{70Rd2WBzu_Kk{(n3<nq=VXUUZ4F8KTldY@xz6RQU$CK$j%PP?C;Tlb zMZ;rW{u>@sHtc=>A4=NJPT%hEkf4H2CBv0i!)3k(2eD*vXXk%+w$0$rvQRZ{-_@Me z>Jk#LKEt&=w}GDTzM6?m<yVw2@jgw7)HWoXJLI6B3!|YTy^#{8q@*}FIM~?P{8%Vd zU2q@)M9tMTFxaEo9NSD7-DF8d?bmcTouy2!I$=wbF8g;ps<q$SJFFFJUir32R2y`> zpJzKRKGeo<USK68SQHP?SPW#hy9J>1;J_O1uBJzosd>J<hia&HdfwlDdpH*isi-{w zYw;s|x^y6U!8JZEKj9mh9#bxiEJKzJvP_osM@IC%IMHrbV(Njh<zd#=Pzxy!Hz|%N z`xCUi(lC2#T<zbtY=1Ce@NeQ{>mf7~-EWh<<k6jgQ+!G(rSR_$K0ZE`n2->v9<C5T zxYW0S=+FG53gx{ZB_$;ExR9c(Wq!MjPB8_A#Vp_J!>8WXkse#hwDkCR#O}Pjnp*32 z8dNmmC2Bf4F-;2O>+7II-KC}ekpR())h6^~*HIssOTPzFzXj$EfRk>Mb6{XTX*T*r zKwch8D_^TP19bZ2tXjzo4ULq_=tUoT)ac@06=~^}f6G{e%%#pyV?g=+8dsMaoY4?% zlwAW}54X+uF7@KL5`8mGjbzkMx4pezLgw@GzB-zl_t3W~K-k4Z&UN+si8g6`@!IS3 z^mKn}m8lsWgM{1(mAIy2zoMapBG7p}`rA91F!DZ_gt1T>Jj$1t+u0_ttzC_w5;H?P zKI2_zndh^&zm)1o8coWRMK{?Z+ZVH<<#DtOZJ;d?j?T%(<$ZlTzJF#5rw!9ISJ!VV z8pmj>1C|3=3VC1N4m7UxH@90ZP)Nn)?b*&WaXC2V24sH9GI!l4dT4HMx+o}5SzP4p zk;RDGHXn$~ai4x7u97CqIF{EQM-cc88WeZTUSWtc4IU^iE}Tj<N$l0EeQouz*?1S4 zFc545JxF;wpN3$n*v&4Q-cnIHtar|Oh)yspC>ysrNeuxR&^T>x&)7ny3Z!CA`sM<S z$Moh|k(i=^AWJ1C*JGYPj;;q&jiK@uDAhfyy|<w+um>Cn>G<HAlA5}D&tkTpM<bRs zM+%GBI_?MSinmk^yS-mh4V6wleEWZi$ZN`|tv$N|+J^&dt$`n^wpw;Ul1nIZa<Z|+ z9F@^9mj?DL4Vmf0BBn{n$$T1b7Na=;bfRpp^HFnFeQrC$yWSmW9p-pg8s))p`<UoY zX>eOiXGdF&51UPptaH_8m)-8KXipWfh<Ev8F$dgffs&zs>+!Ubm{_$TBo@u!{Bgad z`)_Qlpd90(xhp7<gufA&e$)G=I<<r+8~#oZ=&Jn@*qQHMZ#qI>d<{eR$7=`80+UsA zAexM#1*<H95H&v?=AD6_ULk`$Wy-vQ5HD7VT4M$fIDk4pjm}cC_R`!t7r1e6NR-A- zp2VzekUqQ)@&PYd(73rah7#hDt^4;D<_ddJ@S=<w)h3fer&gL4c;kqF)})ZllZFj{ z)d)u@*neFbIn(#ZWjE!X4FK3;h$pT6AjKx3UA!CbDxE+P#A<4^Wq*Q9DD+*Gb^nyz zm%u$BeLn7znZ%Mf{Eq%^Kcr&g)}YZGk+G&GRKri@ObT5QqAnD~Gz}DLnsBoT>)ubS ziTC$0-gjp-I^--Y{tNeSqdzj;(MA{eOb#0zPQar<gjpuZt&Qpo7&I2wM+FD{&F5R^ za3Afi7l`&aK~I-P`Emy@54H06)oh7x2zRD!Bge-llQkF_Q3o~b){By6FJwy0AL!6Q zYmH!U+@yT`2_uyIwD1Hvy0Q+#w<X&!3~^kLyMXDmRnJNkd=jEBvpVhg(VW|6auhQ$ zT6j4*1;qv&ZDRvhT0=tJmqawiV+kF22>E2Q1`5sEsz?JJBqa+~p19A5Ck);1y+5w+ z`zq`O8Z54Had6?H0_!0x7)U6Vnk(eRXL=gvxCj1L=XJnOf&Y0?<=2+#C>3u#+<~6X z>+bg5!-r}sJdzdDWD0^2H`xVxP^f7mneQ6~v?8*`x0W4m<IB+5#rG0JcKk(Z|L(Y; zf8RGw!>Iy)!MUW%PExjs1;eifnZR%)Itqc*)MrE=3Ar3(D0Wr9B>>K55YR4gT(1zX z`3_gEsZVHPGKX7O|H;ZZ+gR5F9B*G=js3MOO!8QG%c_d#c<F8pQDAjs!@`~+QH6q% zYH|_AFRj(QB6PjKc2$G|ji>BhuZ*QYRo|z8$b+(a>9i*Ku*+Pbc7A@nphP)(k>roV z2U%d9OR9TNB08BMW>$w!<^r9uygs&|n8r5@XKtc*Ut9n>bY*57w1|6|u8U7k!e*=4 zbR3yrkzdnzj<DBo;o6hoL1trZ&2|bxVV1@k0yQuyV6e@sq)EGGwZ-er{i>MLN|B7s zbM=XV_8<2h+|>M&EN?=hIsuQDuK`RV%j`p)B!Gb?prcngk$Rr7$L-jAhq+|{J262$ zLH+u1ObruLb(ZX#x`f%l=`eMUL_R23&xPOw>G@R?h;@pu$b@PIS3pa);m>cm&S`PJ zBi(UMc(&YZfh2of^YeA-)lR3RG6y>swwv9)aWk>0mNPV!KnQ7WfNjT%MbNM6jM}qZ zG!>l;SbQK1di9-}=5r-n-rV%R!Mn#1r_vNM!qWJ<x$pUWJzm}@e@KHX21Ymh&C<$Q zX>S8|=~eLVe%q40^7$xGTwZ-$=v4ZvgDZfWI&N%xGKso;q(m&@1BqHVuS7F3U&ST* z%F4wR4aZmxQD4*U*`eIQPmk$XBhq2gDtj~{7ql;E%O(7;9k^}9Qde`i)q{=J8yV%~ z`5O$tHfR5J>X%{>;nW-No0Ri1s77uXPTTcDv()=h11lM?ixcT9Z)>V25F*r?-5srW z48`8o#3v;f7PiZd-U~>iy3%}(=?WB$dU=#B7-=P~?JOBfYx%K7@9jJ>4hsn$m-&}z zW3!>rVY03DODrI9uSO9JOoQ{1w*?mA1s-F%y<r9X?B@Ncpr^@&YPn51zgceqf^u4p zZumXf$x;Qw%>GdPuXJi&B*jf!8|UN2)-~A!m(krm1m&$&@$L2OaGdUWtq)5C1Fxac z$0;CE%G$~U%Rfg;E=TK$Q}mYfj*U)7yNyfC?kd#RtH}VX?>0EERg)`^r&sc|wMIe* zZioSIjO#}Lijea91n>+dDa|QWSkt;oY8I2N4lCc~Gt+v9CcKchhi<zmf#DepN0-L~ z#n|aGF+VU%_ag^(g&tIhCME>qezr0FTT@*uP7N2<{s8py@PlRx;IP(G;mIb{37KPK zqAM3=Sja_#b~pv;ooS~~fhyEBR#m8zJg-}VFefzp<uDSdoK`x@wE{4UV!G)|EIHlq z!Q)dAJl{i1K7vzORyVr^x^-*4<cVt|49dk5^7!Z1%fiiWlmwd3n1r<z70suU*hZ;Z ziqzHU@2T}ls!6<WU)i!S8dB$~ELYl%AKt<}?AE)qs_H~Cq9UU)Prb-*^N^dQw5fMW zqA@j$7BW5H1OStVM<27atTgmt<>c9btW}Dhe6ly#9c-*4qu8La*uC)cFMlxzbfJ}r z4}XDX$j%&~-^p@CKuFH3mEW-2I+O8e2(1!=k!fZx$k){J+;Ayk%Hs>&7<i`0Y?9`A zrpv>q6I0?bSd4bQ!dev)@!0$;!#>y&_Bne(BxWF8S5`LWZF3&`ju~V0`1Yb~7~|^Z zcbrX(jV=9#1ePKaJ}${Qx7D`Co0gUcT+at*=89~ZK-m^G<g>t$aDqGA(btTw2y63} zR4?Tlt_wa~uro^rSFobYfBZ;E+i38@ra>LQ@VM|4`j#Epm0zLWoU(~>bo~v#1y&4D z1*mim;gn>SHMwJ53+<^#teM|72Z#h)P0v<8BAA=k+P?TH$9cpbu2u#APJp5I=H&EK z8$Hb;n^Qmw-+5TieU^wO=Fm_X3l;`-0nf`#0;Swh0pScVC;KZ4@!Ka$jW<x9e@hdf zjxb$6i`fr7tog|BHrD8PP{E6(@&WIz-Fh5xt({$NXyP)To{N00`r!~lP>_&7<Bq2u zU(0KHVs^<@JlcJKEzs7RtA^`@C_wtUX>4-LbveD>2+CDSl}QtHo=m3`v_9tTX%!cW zV`mQupYgx?eTh<Bky_o}?$zbwFaIH00wV&7jZ^+wW!s{1$I8Q!N}qjNNfv7g+m~x} zszPZJ{j0u&8ChsI2y~CEednTN#P_W5ro&BpaFG0QtI4FXI-p2QomTHN=-0zv99?;v zbc78+Va#Y2zeM@L8jE%=aO}H#>$8EbLFan-T5x_(M?N$dPAhx1oVpx9KD)s9X6Z9n ztQ8~t=FOUD($8pIr*(-I*ncpl2jke-Y}D1Sji$s23S73*(Fy4UQHqTWspgr-xN9{2 z{nb5OOs}`<eS6;Iwxh%k`Z(XYqAXF$5ME#HW#r*`aG6Yn^@*Ldz|mL&-<j#JNCB5t zURfRq@}T<{4uVZc#56TSt8u+syn1RL=ktqvM<2J|5N^M>9R~yn`aKFV-j?7wSxw`) z3L#z{ZmC;27mFkZSO{A&ba)@Z3!w<&xL7iCv$5UZygVL<W~%FlOTSioa~c_4fPI4{ z%$yAzS1}DZIu=Wma>-{I$~k@g&pEPlaxgzXeq;TC!rOY$vhJgOTcodN^BmMVa!<NC z-jd$tcHy~GUg4vfjCwlX1CpM1)KHd}rxaxDy%`f|6G=5Z63Wbfa)zj0(M+)A?#W7c z|IGpbniTmJ15g4`?<76Mi$aSCJy~=d10r6pUnsh2$(HUasyDiP>zm3bO2{A1<ZD+> zcwA5Xc4(+Cus9EL`Md(t?bgm|eNb)OH}!uUgBGnbeRT-v4CMZT<sFx{xfW{H>38(G z&CL4l^tF)Imjs&5JAvnN5y6Rv$JWZZLaiJdHBJxreS92vs@*{ZJP4;BUiB&LhJep` z>BUCD%OHf?-`ef&d&Mr6euU2WImfLi7Pll|+eJAAh4Td$nXCPLF^QKkect+SiA_Q6 zsLu6%GjfZ8bl4Ws(t}_QMql1%eku6po77S6W>zMrgPwR@R$C;hb+2xG_I6Pq6O)4Z z?hlW%6@>%IFlp3N0rAOuh>~nSGZ2V01}L7)e;Wj|GmV)7gm*f_n^LD*y{;d1Cjie9 z!P^sQLb|1&It2G8j0iVB${x4LC*4jKg#C^KHleF^|7`Vi(iYh}_3lnV(lQpO`I$kM zG~rwma}h5ueXHGwr5U_+hUcL_AcT^t_IY)gxi8e(k1#7;ARlXUi}5U3+VWnxAo{nA zyOH9tw8!lwTdQkCG&HyaRRcT0DixD!a8$E)?s?FLyZS3zoE#A&(&$UtQ1ATjrZW;+ zMG~Le%i(QJDzVLh&;`c_BSS2##HbU@exB@(b<buyT^oG`3kxj;g?#w3a(ZKz&RUNJ zG4y6072N<WQY(*>*`l}2{I^Y$%tan7-?q)KSWhrl^EZZ9ybd_VlAGEcO8??hGY_;P z83?0MnjZnUfsSILo6N={F7Kzw55i<HB5;A;LAV4|m&s&bdv~pOh>7)AJDd<03u-4X zFE3@_;JiE&6cu9xTy0jS^SzcR-J36FwNw(8n;7z|n1b;;Hq|4df^qyFmv-PatIpba z)}1>K_O8&}={OmQ9ncraxVYx@8Y|oH52-l8DdaJkoI#yMRZU^;cOkbww+AUrsE{&0 zhD+!xsivIveved>AP{7B+kq|rd>^YhqXUT1<8N0|VM3=2K|<F<*Oyp(_)o{`GxHfv z83C$0Z}SJ!aQGI7S(7Ti{FCCyhdv4k<){1WL%T>XjjJ~r#1AFZCyOnZPOEJ7I1&oW z^2H#w7w$Qo1Td^P?d;r~9FZWoQr=)<OchrsIpUJWoeWg+Pp>I(Wu78+TTCE|Wz8g# z)zry)0mpxBHj{O>GhB>8Bpk6&NSEiSmS8aQH4qQjD6vg-R3a7#CMD4{GIBUMM-AFh z_<|@-GuN^`U*Xv7xDk{ccRc`q4ZJy?F_VetUv{$+`hJU1k!xm0{-3YuBn0r?lz5!Y zUtJX(I0|)QV_{)otE;K2H8gPEq26*fa-pK$33!}=L#QM$LVU}csi3vyQy{zx@WfC$ z&;_B?@528-5$pd$uKQDjQ5N<ky42iSdto5wO9W(U<*7b^5^#SE;K)u)%$}(->s}K; zOX)P>i^4rP?7_aX&+NmySoIdAz-cr+YvP~32Z}=yM-{{}<;brXtyEQdP0&i_8#xk# zXt@S87BgD+B;-@A_Krgx!1I5tBja!Ix4#k1HaCTN&JA$>jSdZs4v)X@)$K9T{5`zt zdmvjDqxi<CBWQb8yoaL(NdM1HOC%MP9L@{N%(f7Yv=jyA%k*s*y2hTK;9M*i|BZ|V zD`;}zMhec(&H-qP3X4dEJl^)y`lHsIJ0GYIPi}P+{aP!-iuv!fYrUTa=I!;kZB?&d zI;0Pv@vE}}u<u6hO3TVr=OV9-Pd0)_X^W;YTa%MgGgp3Va^x#GIk`ur+&56Y^-D@g zk}Z2gKc@mcZd^N^N*Y>KFyF7Ai0*i<(T!{|DV$}O8|-83?K8KZI$!QS^SOV`PDx7o zgo>KWZSCOF-lR@5iuU@MO6U4nb0NnNKETEDFo4aWMo4}YIlONPBE^T#3!M@*<?uTx z{qV&3(dD;ix&g+t`B{5fZU0hZ)CPSLE~}Fn_ka*?K|;}KK2u#1XB_>Ry~|pHR)_Tq zW8LGwEjk<X4N_#5jP8Wt5PjfU`B?Bv%=w)C<u+V}NtAg1t1zI!<@Ekeo>eeBzSZt3 zW81dN`O3S78CIX$8o`BQngd|z)Vc~GRp^I8zfH683<Yl2n!J?Vwa_>gMrM2<*f97x z_IM36G;aT5GT*fp4wuccnOIk`f=pvd%}RhxB2UJmaK7eNYIt(aMA&TDX65GKu--PO zpQYy>Xt-8?LyNBifupy>Hzpe<^UKMZb?jb^Q$)<orv4^E>lf=2xs5nl(?y24JK@Sx zQM_aV92Sp1O50+|gh>8@L$M7g42%~wWjIU6^kG5eGcz1u``IE8+2YE3XILLnz8nlQ zGvjyLxzF5k$$r|mCw!kG$1<8W`|B6t!zv)_ab^6biiHqu_CyRZKlC{L>DI`B1GU4+ zT?J1;m|}?eO2aWsW>jos=;&h$;2F>x<jU4^%Zg-~_t$o8^-~<FAl$~6!YxU!7B8_a zZI;!6*fe(EN#2%9eVz;W4>LXwBMe@^*H4xSU0wdzg$*ryWPF__Q<ZPh?tU*=9l&*D znvbU$?MpB^iSXfi;)&bwlG10Ps=rYUpp$ihX0pnqme=L6n<d>PGbx4fkrBt`%Bw1b zg=3!5f|3#rxBU<!P&-$XBd5>#tZntd$$UNNbvq@r=m>$Vn>H@jajEtF@dy@@|Bhxi z7C!zPV^Sa!?4;94mRFP|;<JNb`ROU_7c{bso9m^DH34))+zNBs9sFDP)1E)rQlj7I z^tQNsI<Jhne%L1#yH`I{yPm9j%{-q4xomB%{;|K9`~{oa+12d%)bZj$>af(3n6r0s zV>)^%+i0Vk+v@2${(5xzd9}?8p?n}{JT*3ZC5PWXBz<;zJ_HopwzU_<4EF?Q$wExT z+3?Xg!ZR3?^@WAjV{3VM+)#jc(FZ4o%;MMZjztkwNml!AhxuTerLu9gVJ8xM``w}I z^Qx*$rG6*D{p)kKJWy_{)qCe1k6k*N(0BWkLWWqd4I8c1r-nI58i4EGrgn4xhLToc znTK-J$iUs@d+*(!(I02LPRdz$u{@XQ*uKu<hxB}VQI<#U@!b0h0$)>|v<*7k>mU%& z&yTFeu=S7iOGdU&^CRdHG1ElpF}BJll`+>Jq7-yKH}W`s?eRjWgE=UK;QkeK!+VnT z^t5<h>(x)5TV=MTQ#_y4v6TEEE23Qc{OuT&{91<}EiI4bTgn^-MLslipvq?hJTCjH zEy=U>?z`(y+i-OL83sC^gV4j3?vkP+0^;}hE!fg-lrL0t92~Zfhdpi2{3gmht!T)v zSh?{s6a4OvKW6q7MHL|oltyWbudv#%m9D{4&iS~%UK{??`An^<kUhy*J|%q6*xvgy z>ZqEgrctM<u9XgN2dM}gKvwQ2M{#;uT3TfJzrg@7Bor*IDASv({VZU*>toyv2D9`( zsHn-Q$;t+%`##@NckEh@r+;ChEGjOZ?$lnGw<nIoXUIHZt_G@b94KjOs99NQPn2aB zY_8T@GB?^Uw7I0-J3H4Yo)V!F@f}|G+nTgqj)V6y`T3WD<cxSDUDlM+B`J+3^Tm;T z#+;m709tCQzqmNNTcssq8HjPn#Ef<%f}jprE=}I%S_-mUbl0r~mNl`*;?XpyUe$J` zDzxln!)|YeY{x;mim!bi8C+F9_eaPj9s^l)tVd^O@|wh~LI#fOM0^&A2E1NgeDSP< z^Gu;eFTiM%JSO7~i@Rs@JRDaG0*_FLB%@P#gFnX3&D~%-o#!(#Ze+A>;@n#~d0~pR zy?y-ZAqA0OEGIIxd{gs=ziBJ`ERROe)5~3sPU!N&o=d5SmLG=Y`|<agw|;C|sAy<- z?woVEEk72Iow}c|ar*9x4qeQZw1Z{_;#s-bDm7;a85u2pvAgH+5nYnqKFmhAtUNdb zp-8{E#eb(J_<T5pWC&Ux;h<oKeh<bcCjFF|p>@mXX~D@k_obIIBn9>LmVh3Go0|(+ z+q@M0qpue^ihzJ%G)oAs{F6ZQg?`8PuE{zJyy8pNN$bpv4lao6K(%|@WfC7ad3>{R z1FmdUUvBa!q?ggL@<g?jS3&m1T5lL-<OQ-WMMtBi6Pu}14!z!Ug>>K5KxCuQN(in- z?CKmD`1dbM3Cv(9FD)-E9hH-mlr%XzEu$dwn|9;nx$k4S(RMl!!X+<FBqb%;PRdNp zWNE>kfpFN{*kzG7dm}tLRe|}?iLi0og8|Ax=oFbuO(y5;E-3Zf-xA(N+F=0rmE!!U z3y7zCA(HD(T(uRcsks{!D|ry)%Ro=p<Z1;Oe6S1;J7-?5FSvI)dOH_*>;X*~d%q{p z=mw=4{C72!gK&kX@IX#Z-rU>*DTT%Uz`C%oaC-;4SRsweq8G8{PgytSujZCy1W&=k zif_1`2rb0xis@NNpHg>+day6ekZB2MQJ8YTJY_V|>C9>AR6vTJ0RGwsnZ*a71;eN7 zx;h2Ib?aNHGUH$AIY<+%cONo}N^=JlmRs}5IO&|Hu!1Sm?9&Qpf}WSTe%BoA?3)mS z5DH%A8|B!u7hY7qm6*8TMY769Q>zJdk2*aa#nVg>F`A}R0+v^ao|aPBaA02uLrzu} zvJ`T5z2%@-?T`FpPU~mh<*$!>h7^lggTEgUhP$&j355Ok{^o<Ed8*gkffrMdsGi%# zz}PA^ub9SDbu$cFf|qT}1CScVO%DgXQPjOLI5cE+Nei}>-8+COIuooHnV}gE<$-vZ znGM_A^WLR?#urWpj36}&c*@c!5vWVC;cBjbgKI{+xyIZc%Ewl>R9fvmKwOkVNgc(@ zO3q0iE>u^a+3ZfJ!@6VmvHN3MZKj9k)jvd5RhNxJ!sLXmOd&%U*wb3eS6)h>qou_i zY8;F$F7y3!_mLwM7Y^%&AjlZJ5`;x?8!C@~c~Vkb)Z?C<>|e^UNQaU#dOicXjAinf zuQZlQeyRS7SsE2+Yj^sH3AwX#1^xEN+EYI#hDd2DYF;)3-;2!z+Ni$$yt|=;uc5wU z2*C^FPDvh_zYu7!ha`)3+(Zp@LQL88Jiw^fOkON1B*8-!|L``By9KdZ1nNscofRFB z=Y_F58<4sebgC`KW)+Bh`7-M$$^zui;q!vlD}y@-${%m|0r%)%=$T`JdaS*;d~L&- zLu#a;6b1&p&QQqw#K%dp6SJ}^MR@cNx)Hdc_-_`FnUjNaO9u|@D?pr+prJjSu^fVE zdi(aBN{Tud2B+fsB|;?DWuZ@Efw^TH{nP)5-4%eT|B#qWi%8fr9gFaA(lsnBXfFRE zYekXUTbolneh!Wr8(sY|8Se*?jPldt=pPJKiGhy6UqpNVEjynL;BNLr$HrpPo7q@= zI;hIa3&<&SM4I_+!>+?QdQ=ptdHAno{!@<QdJqPWt?9&mW`*jnDUELdW*$~+FiD6V z)ho@XcpV7ugyl*Jn`ko1$}A4B!Ex9fu;Ov9Mn<NoMFcK1-QHV%uS3?Cr~CW6;y%aq zv@D_XP-r6%5jxvd*yrQij1?8kC2Zb%+Q7Nq&cn$0eQVVmqBn)RMrkSbn77y*dH2}& z)SWCi1bzckz~Mr-JlWakN%+|*5L2sY1}c^m7P1MT`#}TwYK-e~SGOV}hBq^H`Wy)` z>EW$xZQ&k4KU}5bo6fQ~i<&_<4z&TB)?O1HOtiR~MB$%^F1Mdf;@&&B30rOqs$1R9 z=T(y(wW5iIG^DiLuY!^{`w^56irrTJ18|=Pq~JG!Lf(gf7MU%eLG?O78h!Tq5Vuzr zenqadLhO9Pun#%%9-Ej4qJVK%uO08aLDMS+k_GSMHhvrE@9+C)xXoQZYyE~|V|58w zTX=!h#5(r%uZ??n>Fv!pHuQ0A^BxZ6P7;OQ3Z3#daqK~z5)d@K{`|GWwWnOt&#)%C z9VX?U!h{Luw@X$LU&MgS5WnbLL0|Y}W1uS183m^#4Rw)0>)(?osJX?&!SNuns$?@1 zZ-;E}#;r&Rv#*uAr)5zP4Ln_UX_ODAZNAn_We|rwNMTH2Gpu{dnMe!m#5AhD9-vSK zS!J+1)kNzKSaMKHVC<;J9W~Qs!#X{;kmYVggYiH7<RW8ZL)$nxZ9ZRTyO@P3cllqi zQ5ki*1`~r|g{(q5Jr1^J1}C^0xtN&7dbCk5hNT~JD-Bzxo(`?83L&vdYD)+ZRR7Bm z1cWzk+k_cAez*`mUhY)ylXA;1Zn9cOu65pmSkvl`{z%Y5giS26T*L_|JCxaj=SKa_ z4Eu$3>z$0M!ML1q&G!8;MTTU}B<7rMWOPBnPhG-(mwGdkDw+IFXdRQ{-QmFsoc8mQ zvbs}KWttj4W{(o_n;e$=>ApfK0t%!`(}I2r&a#hZb$Q+_GkKyxyo6Y*mYz?x*4FDz zogP*nSEH4TEyK*&-OsN_@{3z|RT+UFpEDBC&PCi0=HDP9C71oO=l&{PLV_TAY)24r zhXtr}<6^z*V|Msv^W`(2_s`18=$4k2#>V9E_JbxSACu<%EqK7*97^-hPb;8?n3=nz zr^aP7h9gU^gOEU<_a>_tve2ra&;9<=$HBOP_s$g_TC43tEbG!Ac^|7d7hz|cz3IDL zVR%$BAwUMX$**Mjd2@5~z}^1A0Uq0_5D%xg+`C&(6#QzCG%fEBPsBJ9KDU)SQbD&# zJzCEpDXB1{oQu59(Ypb|m1VB{>Dj_E!yl5fft}Kh8*LA-!y~F@;rB-iu%xYL`uFEc zt+@xo@QS<6XM{Xf(}kFoH`AdNz^x1}N43}LfZjg%m>b~3zwvP=pF8+vfS4;-6u!|L z_<@;a-}0ljYDZeB)u1&!E33nMO)X!KsJ{Z2p36>yObHJ0xpgMh#Ca@iFWPM6-#-|m z3Re7T6dBxxpFM#Ayczl#)+bg-Mz--es(myN;|IY11&x+?98%qZS=rOuJ32B$wEIc# zS?pn}xYPTHpR=FS(ZTqByhg7{>{2$6TMm{oSbf~`VhwaUJ0PZ|l=9`X)3CTlT6;T$ z?VX;Xw7=A|tF_E_Bd>B^#*^{ZLCn6AKV*h+mq9Ddjx1H9qv;$+fT{B{`hb@UWcHx= zp>76Q6{NVRnDBXqQe#A}D00N>l1J%q;LG)sey&4sehzw7Ey(+mB6FD{iCBF30a;_h zD3d}>TMh*0P<$dj#QT5aHq0M_{x4hNBW%L$#ox2Sr|YLo6=0N256jLNa@ai+DOnnT z*%)ZO&?Z3geK+;d|M76KV+KMsOGPAqNZ!8c$|Ky9@ReSaMwnS<3Az0$7laTkbZrE` zt5=Fb*?n}G>2B=i#DUdbjjt`=8uJ9NEYc^ts0R?<cXs#pkz-r#<rA<HTl^0NPFJ&5 z|4`B}lzq(>^m#ljGIKjws@Ct^Qy0b1ls#Rnce-wH(s0X*q$-0Dkf5FFcM>N7ZI%rP zN!Pb_fkSWggmhYL>B{10vdaYzC&N$tD)3wQWTysvaM-=ejdx{Lfo@JOB#QyTtP*GI zsrd)JJdpW+ka9cy)8QACTsY6xt~C0g+m}@?I9qWH^QH{Z$KL;aF8Q$2Sr8f7@ctg$ z9WpdL%x>0ybZq@_v{B%B8|e@Ii_9g0O{t8Xay8|1o7PVwLxi4X?y8k{h)HnV-Y2+z z;AxS-TkDFqbT7i52#=7>b{VE5#n5CvZ7Xr3lj)N}lf#^ae#O9>4=VcmJ<0J?t-Q+C zM@(%sj!42X#Iw@}A8T*EVfvKdCM!#4PUdVLT8az6D*+)x07-GKJugLV?(JrGe6gW8 z1bLVbXhZ5A0yG=z9MPk|OT`@W(NK(%5T;1+Ce2fJvDm5ZzkiJAytB;2@6OE2yIHuz z0|bar3MJV`AgFCepdz7V^VlL85CZL((mEOG*`+Rf#n@_vJrgUM!Bneutb8_$l8X#F zA#Bd+Liq>>7O`mKuRU*r{QP$z@=f1|`Fn=oCUUz7**pbLIaZY*H9|p|7!EtzgPssO zkAV{q{qBS>+Q`dk04>+|WPk1Slvrl6dn<T=?E$xx^=vj+9U=!vTwYCS1IHp2&2ueF zKlP%b^WdI}tcut*AyNuxeEE`Eh-H>ForJg5^mnpRyrR?l#xB!67Bct7or^3rSyE=5 zH|MNt&^f926RhCPi+KMntgJi@miAcd@MsNdq_)FZjbXK&s%v%ex`GoS6L5BZ`WNbU zye#bhZu2-KhKqWF9OC<Oe)49z@Xi#KGT!Pm=Js(e@xR9X2gL6O(PTsZc1f)C48uhr zJmiWB9qvIWjl0#__nrAU;BSax0tOiK9r-rNKg8nKWUFbYrHNS5y^Z3>K`{bKM-R8K z?QP~;?lzAXBPg34*I|8_HH!5o#%U*r!~QO7X13iOa(XTXpkKg#q5L8*5RavN+BDW) zQ1E@+NYH)1_s`FtL;WaPlMLY~gBtW+iSh@d#+=wm4t*I_J2bIvyUu!9V>tpYNF>j2 z_q%_O$Wmcr&$n*$f%`QE&a(2d0&ZJF4e8jM^sJ^VTZj9jI#d+QUElA%^YOLX>)$J6 zat%+bwdCwQP(ZC`)bat*V?GQ0fG9cCQt=`XOX|vh?55N4dHyHd)h7F~*{s{W$=K!P z|Csc+PbSO8Izl<XVa~!J;BcgudG5i^pi&<tx7Pq*(TRlQ43ZbA%>{~B5TrmZT{Ps< zm6m>edG<XNX^BftemcGznRndptd@@o)^oR88AQ*SR+ZW^(ok1V=W;Cn{kwOGBlKNQ z2LsSMG*lZFvzK(V!(uG(<|EOycW|JbCP?UB2`b?p(_8exZTyk!G?a|{TMJ{6|MlZF z9p5k*KWw+t-|Qt!nM|bQ08(P(el{;#`)_i&3>T5@aOW?b*=GMsmz9fo_<blm<t5HU zh5+igWok9aCOyOj-s#E4Pm1{&@iHiI1&VY{uZeq45QDIUyK31T7{u^9?<;yL>AKn| z;#0W@La^qxw8F_^5eWN53O>vm$PP(7U_@{SoG=r#pGW(i(uMp9Jx#qIPM}%1i?60$ z)_ht5Eypt9X|dOJHgQ<KehrBF(%|?E?|mlF^k+}{&~Bjz$tcSY7rj#a3f5~}U+`r6 zsRB6I`!Tf>#?w$CJwd-CXxzQM{O^w!B;&`y+1-r5__QU<?q64@_dR5#PGSL>f{9YI zgxL}N1j+7q_xAh<5E>g6n~b~R`_8^wX?#}4AeAx0?qd-N|7$(@(*qj}OKEkedFx5Q zG&8fHxGBB)b%H`$>!)7E^W6#9%f+WZQLwa?v&nIGI{#@$dg3T!`Npy)v+=deKP;!q zK{d#u6YC$o<yCM^Zc#Fy-A}&z$&0NbT!+bc4+pWk!%OJ@j2)|LI2xH~Nf8xw&-Ta* z-)SVq{Jd7s;5URRM${JBH2LD1nn7ZLa572B2r^WSbh1Ylq0SJX9joohxSQN-tt;@1 zimIQ3gQIK%W*6SzB5J=XU>OSgZz%nnU|&5HOH)Q;WqLxR&58=-e&Y_&x5jkvCuq~c z@I*!Kn(+Uh=U?l`nhB>3H}7d@m$lh+$oumHf$fUSk_au`EB)g-o`FG<2~~&ZUXdsm zm)mXN(Z_?#?PO|cNQF#L*CdnEVfrg46^?>T&$WW!=V$h04mosU4V6i4pckS=5S{98 zX^F4fwtVOz`vv~wr(RY%E#>8MP$B~wOEPk}S~4i;C8JvYP7!4J5?=jLv1<_jHHM1S zHq=Sq$(VpnvSA`tZ$=QxX-}uLSZ=pPv1RWFWq0AC?w;jz(jUlD4_y;vVqtxd-t`BL zGWF}%uPSrU+fvD|=7)M>A@nNU(ZPCpcKW3mMMdV|zJXFTJ)4a3avOq`a!GpEA@k~z z4hoyC2)%A^6kaWGYm>#BT0BziyOpt{{NTQZIg}%ckB=rWDygJ)vX$v0&=>8Q+Y@|v zg)Pmj!>KQ&^}Lv$Xzn^vDhaM?`tj7EIl@z^*VPr)I2Tq`5gmgXg|FY~T+p04aSp3* z-MscoVtn=jh9w7LHm_P~AvP$oUlFm{_`?dN{a@YpM^4Mq653YL`qr=bb-;QliKMN` z?tk5%#gI%=<kBo(AKHk0(}s;9(n)W*snj{#jVbfPWhf{~MAE(<PjM3up+*w6{kdUj zd$D-00{=XR)KS05MotsHmLo7QH@)6)%3NLmBOm)^{XnRt8RFUvBJKHMr)t=Oyx0l) zNWYPW1Ons6PUovdpa}Dh^^VZ;uT|2M36DOO!TX04If*!{mjwf8`pq`#zN(=5hMP|w zIG%$ruOe)GeisbH?T>LPxCvA^J4y5$z4iq8wxI!$no=1+tI4{8ofN#4`Le3uU%vux zre<9Kpd3^emzW3abO0UAo~{i2&k+prYpYHCdnhaszx`x#_DcGnlNKVc&SSO!s7z5G zfq$?kpRa{9&%WZ9A#8D)Nrx_ck5zS0J}7h>wl9%#z@o;W?S5qeBfeXpnN(E3^-3sW zXCYSI-!CxLiCmm^KIDVB)pd(dFmV#^{nrP;!W`cnl=r|y_osGW$zT3gPR9Gq7EX>r zThpVu@6NVmAU;CDf3pDLi^24`$VlNWUpzfQ^m764n}IbfM^(=`FHg@W|3}1ytD(T1 zNi{7!TIH5Oc8>aV+AkEpnhzqrtx4vQJiRW-2Z!B;-u@jn1A~pbZBT?NqHk9JxU4`_ zpVbyW1U-0&<2$F{*bC+ZN@%rXx#4)*xj*?!T=`FT2Amz&tJh?6=@uq7*Q!Z==})BS zXnIy<UT@Qs!W8+Y1-<Y;*?<3Ma8o?+7Qa^!>x19H?L&71*|qVtxVCsR)jjr^55&v< zNqt*gch`O=M}411_s~y}=Z4|xQ~jQ>h)GCL7y_j*tgVkAUMR3K(nt_G!~<RQS0W6d zPEC9^#Z$ElVv5JN7>;~uNCu2~F>5<QDD&xR6|AA?*L6nDHb}}tCgOY?(~eKYn+4M| z*(EDGT#@Q%vJLxiI0x798B&lN3R>*k0jI>Y=QTR*rsX$?q-{ZJ6<P>9|G0?w`Mvd4 z<r~8CR@_!jqe&hb1!>i$rL{oB;Q;=CI&>m7jPZ#m%bHC9vtjKqL~R0Y<p_B}t2qze zy<bt%nqIHzlj-UbTn>SS`<8;En8)!R{7%{(?ksqjCwMM|5kOPZ^DqlWR2PT6>t5b7 zmZT2!ymROmB7yj6Nevnh13`nk*f_*-&3Xd;Q7-nb+#L7^(?IdS%yN^NM8sU-hv)7X zkq1sPjy;^<7`cR*w1VIBF7@iS2L}<8s3-&kf8$;OrX>hZg2*+!zk>b+RD#DF>^Ho( zThccqdN+Tyf^J9~<gHGulHGG4i5rymM<_=sOICMzMafKw{{&>51iddcRK<+c^>;>x zqyDh{fz%b`Nuo=&GZ94FM`_@h3<UW~yg>Ey=!sF&#-{&qxQU#$0Rmv|aXRF|eBUB+ zkiWIRHG5Z@+3D|ozzRc`Vl*<;rBYK<XK>qoYN{;^q0&?gkYm<?;47uVp&jF2RQyd$ z4bjf3De)iSK|vjmnvWHpi?pnj>W3opLPDM=W7J~Jh+JcETMR=(!^oYOGwzMD#%h=E zJGbNhHo4z|G=uF(0qy<t+6u<*1JLKJ)xBQ|jv9`OOA}I+ii^7!DDj-0MyM(yt5Ldr zkNyOy^m?MqV?h#&KSut49)<D0c|yj2H%rP){}0;UDk`pUOBW?bf&_O7!3uYGx59%v zgamhY2=2k%U4lb!cXxLuxLdHZ*k|w4ef#0wK6~`dV`Z#j)sp|8^P8V6PA7_g#9be1 zd;YT-tEe=!Q4i2w`1t~_Px<|k`ULy)v%G#*O7Ra4Ql&5720ow78WI62A-8i;&*eQE z3}-98ENUon{c!#Jbr6CYFeA)%*`($S^6oeMYkq$fv$MJP+r9rq$G!e|r5<J~8uiPS z5d$D(aNzemAG(=#N3y`A9v4*yeFh{aTFB+BRo0>^#^RBm+g>M-N-p&w`(TZX42_J9 z<83s4Z4kVCN<6689G8v(wkuUtEWiSyWdpu^U)`cAaeP%g98WlMd3~s=al@og7*+Kb z3?AMr>5vJo$@vGlU!tKwud7Y=tH3BdKc3;0t7X?MvZ|Oy6jQI@vj4aY#ZW@O9P7yy z3|S+o>%(D#ojO`q%lb%2Aa#-Bc{pLOL5sygz~>t$3pAbCp8b?ts9#3bs!18uuuHZ~ z_w5>u<#cUYZSRH?D}brZPq{L3SzH=A(i<uwlcT=$cQ2V_ATnUEFN0ms?MSOpsi-L2 z)mgl|@%1_RrbC5*&%!`hSg}4Mecn=gOom>amxh#OdiQd4WP}zN{OF4?MMrZIjLk(f z6Y{yF#H1`$et8ZEGd?;zf<4i#DEIS)Z2ySQRH^IN#8Y?dqw-cb_Y`$v2-uWgRZZLE zYH23Rr?9WJQH|G~&D-M~+czTt^Pkf?_kN$siORo-N25B7-3xg(g7*ta4H4$`t_|)& zV;$MgJs=P?huRiBQ-8JnSpmdr$1rv|H6<mjPGeS;XLo1<_KK>eW?!4m2hW!E{k`2^ z)HJe5+;u`CYxij!+Ex{sZTvhm`!f%u`|^5jfWQ$9E_Pl&CGAHtOlY;c_?sNNzr77~ z1J@_>H9a)IGLleJ=rVblby{P;ZluoT0<l@`%hc>~lVi2Y(}lnM@F#g?Wu>QwOnp4A zc-y~E2crcV5U<`5HpcA}Bs6HYBfo=ki~P~3*ACsyvJP-nRLotwx8L7;YSz24q%qN1 zj%Ii^O{(1{rlLSo$5W)cl8Wr@>`?!rjKJmaMasp(!mIKew$l6DUOGaXn!-jdi?7x$ z^yzPH8jmL!489o2|3;Ho?)Qn2i6w*CQo9DL#Rt7mHs$etlO^<X4fabnaVDqN-b(Ut zWgw!KDqTuD3E92-aKb3Nm6p!ZE>sq|?deGVP96qQ;&hcgPlJ1UWcp&Y4+k&tNw!w= z*JOpB=|f=^b+C>7*+QN3p8=n7-F2-NkF{qW<a&#p^kzKvgdq82Ha50mrSXK2E3P!} z{?g?J$SGepQOvN)zr#^^aj=m4!Y{q%6he_tPyD*gcHgFN*?8`(E*{TqPeLO`;J;Ea zj5|LfpDlRmr=B(6oq0a}<K|8V6_jT?UHwf@TS)_`azXz7@1dbz#2+%po<NCV*yW!% ztB<y3(aHD7mHNj03!nzh=5nVG>?K_HU2pctWlzf28)p{UCg(<**BjdNv9H~>TRjFf zlXl(Cr0P%8Im#Fwpiaf4_rq~Un{^)}kMlAUhUp6dp%A;3#%zSZ`Dq$%nAwtMCa1f} z-Ah>_gEo)HL}SlQ2grsR52l58zp`g`uKM={f*aKA)7!}1cP^LNw=<y?PH1mj1D;_< zc6lzX)qz%k!F$qUO)}h2Op>0Z+Ug>y6(p`4i0x{ch-~IjQ$S~OGIKf7n_IIdvs!EA zF5^HeX^D%P^a~YGi5QP-n=O|+?$e*_?tJeI;k4Mv8pf{Zgvq7;so+0bEfNz-X+z%| zMpYy#(a-jur=HgvHmF~tttxl}BMB_D3JZYc9hj@ZKt;t@N!yr&hQb3zH3f3`>$S<B zk^}&SXQ_#6_O@?+lfzL~UMVd1(M;dFwI;nbj5+MMhwN#yx!Tjk!e4;kFPuwBe`<L- zF~fbe7xSmv-b(StHdI(gaXP6AeCy-odut@57diPZa{YcB&x3!?N+G24RArBta|(;# z8xn^2gD^EMO*{3y8{q<r25=b7#3Mf;iDNZ%xZR%2wNjj!GVlo7Ek-+rDH(|FPSNYh zSP&?~vi^2~s_dNAs5Zy|#M?LZM3<pjUS6I19Sm=R0lRS72%;;X;#(SeeuWJnL;S0a zlYd(ewLjI2NW#`KFu*{_iA{@Pq`>b9rPGiJ5dUUomD__ZyY`nBu*7j;CQH96k+xAv z?J_vSjW9Kti3rSM(lJ2eAf10hirShL@(P8ALWW3JP>Awd<|b}qTmdGooGrJY&=!{w zsnBl*X3+Kk{WN!!q>^>H)_hbO$>o6s1j^5!nw~1QQ@G_SoiGdg;O9AteDydi%lP9$ z_`+J#Ee2PonHM#N216qeEx1TVKEdhX`r2Wmc07&!GxZE093x;XLcYNUC`dW|3GoTh zMs_>*sGP~oudhm)l8Vvq$1tx$Dk)#k2O{SXoDiRk(=;%~^Az<@Z}d)Msufn|;S;b) zxT$Y1a*g!_ykn!|$0QkuuWL4JCR;ih#R}=!xK!2^GL!S~?Z2ZEZ+Jre9=`V%RoDUK z1`Ik(naMZT7YKj=COUfZ9K&a%xG0Xsz-Dp3Qur4g9nLWWM`u(<XILPBkv!$aQ3%@w zkQ^!Yl29i)JqW@_W^%dg^|<cm7Rsk{3Rq9doP)l2BakGLaYLTSf8lswGXscSQa!<} zHddw+?>44kw!HK6^5VS{Lwh~T)Ln%-CZMnz8uJ&cRS>d`-ifd0Bg_|FW*3!Gx(otT zy<y6gY^7xYl38iLf~|$q-Bys#z5<9;Rd$PiJ%j3G0mLJN1SQ%|6Kt5-4PRDwtzT(q zDU8D9HE|n<@l`;?m4r+Uqw|->?Jm^KhoBiiOC?Xh1Jy<rwTCvka~M3}3Er@(tz>;9 zxDFTuI3>8uvN91=sH9UJjrrN-okv|)lNF3ZlniFSI;5zZq+j<@y1Gf)yphe9TB`8$ zp1<X654D#D26ER@Gt0g+l$Z#>5<GWn@jCFjyM;1&{(8x`-U0&-Fe2wx)>i(`s=^Pv z4Tcq}+QQn5(6+!kF}&ta^!sqeKebay|J<7!V#iuk(uh)?afg}yGiQyn<uWo{k-5mH zxuyclRmTDsXd`tfm-HetF_nh;|CpQI5}7dDH0~`mYDuWPJbtDpQ?5)7F2-Q!qONF5 zPf}JFlaiA1d8DeM)Gz+$8bWVlrLFB@)MVqz4O<ovU^=QfH`YJy<?<N<!Ai3p@rjM9 z*z|mg#ywFVT|o=OZBwnbg=lF>6F&5k2z_EHJu2$@dJth?g!+(JR-#%K9lk>=Psnna z$A`!1ac@<BO898mJrnuKJX(9HZpqQc?dak3Fh2E!=eHsAW_G~)3V<u-8X<vQQPaCP zGI#KU$(K#RWGu)?iL*#AqnlY(`m^JYbqR49X@vJ$x8z<;&}JnY%l!72U9HQ=n-(jr z&nTL6MS9}@SX<#PGQ-4_>9;R?`}#6%`0?+VV5b!55FZKGeymdoQ9|15ZzKZPGlNcL zXo;$_SglsW!i0yt&1)4;{i)x@Vy)?RvVEbXl$7xFLL6L8mLVTEH&zQ&!5;ZC`w6%6 z9`g#f{AWISNtrm5#3WWPa_z#%mrULj-fXdNdovF*{wjusyBgEjai}rMs&aKxQ2SkU zJQfq2$j%Iu$kIY#nD>oPPy9Zbd=0MyBP%@<16lDRR~r?bAMQT6zg)kG)(;O4kx%&o z%lv+!@os@mrxM54#Sv3sBX~O3+uYqBew_6!<V|F1PF{Z0XDBKu>VOk)y2n_#K)@Pr z^P0VxmA=#bZ4Q*^Zo4s#EsQmp{N1HSbSZ)lvHjz2ZY|G$K@D#cC=@z6SsTsIOWwRJ zH0Cc`i-70}kl-5{2rGuEj)v+PeBwI+<k;DKzvSCbWeNfUk^#A7t*cJ6U9K#T1ZOB9 zcJQ+%ba1+CNUkJ~5<8E_!ND0xZS8sfZ7e5u3W!7;AKI3g$A}jsK;;QY<Vb9%EQJhf zElvo8E+|7&zNf;N#mB>BE)Ek#@MZV(YC7{}YnLbHjFcESFNUI$x~(4HaHAcM*osIo zYVJmg!+J@n6Kx-|K8Y(z9bSiA&Ffe-wqHYt4F3FF0j7kkX$)ADO<cagpR-(>%J`eB zU#s<I=Rg`q1goec^gr$<QD^#1{a;wXzw26oQdojTpGXrYh>~ZfU!^V#2U*MwhD01P z-rWy1v{wg+BpqJ?LqN%Rl0L(bFBVKlS^%$UKQxv~1}F$>h2p3Z;M_!sVK0Lqcz%fN zDjh#*Q%i)B(sf7Y57Z<9qLnNO7-b=Hp4J~Kf&dNsJ(t1agMn-H!=(j%Dy0fqqdPts zRZx6@n?F(ByE;hT)I3*5F&eN<Edf16V!AYN1mU?w7rIm&b%=J1U9UnUiof;-+_M^4 z$S_4@x(M+9)aU+Fk<?=Ea~!R&sI6!L69L#_8x80KRFxYY5Gph=JiS<gyOw{9XdGKG zk)-L9$f+gZ9SE~ig!bR<`~Hhz;=kI#|Mc;f(LU)*x04#TzJeZqGRZsK$!~wBE({aL z$3_SjjAT4yAo*PXEXf@$B?ud?H9fSrY!L3;aWS(})6pr3NXv?7Sxz*x-6BO$0%w+B zMhYfC>UX_c+-@FIHYuL|K+o)^oYvCvw5v4V>arm&Z+2vAW>O;V5$T^9*&ozK>L&9T zzXLcnHYy{7;V)n9Y-+Ajv8`zU7(u41cYA(_(&ptHQ#|r-<RP2kg}Nkf>y_PZI3hr8 zPE6Ce4tI1%K9K^BR*5>5w)5U&^FF?uTrKb=G(9r^ZLwbZG)%bWCaf*Iva(d#)2L6P z!{wST_p9XB&r}{?IUWa#)0uqCbbjx9<^aHeQMidrhtKiqW}71lf=|=iq_HVbiVOSc zn)uIH{0E)Cb+KwubX9^THZH>2XzA?r?0`j@zR|3?VvwAuL=Sl{1>hB_jF~4r)#IA` zQRW7Zy{!3lO4Xhw_L9NHX4)1jZ1$VveE9VU8p6(ZBbEc-r%d0A>O}!Lgv2gb8dya~ z;1@7gv2s6dz^Vv!h(KG~w+1=SvrUW*kJ40yXGGJ-B3-|JoQn!=V&=oWWDA%3fhU~) z6Y#ezxkq>eVPatXzy|9mIgW&9Uw;qjjZ?<pxi%9ioK|9)8(D;WB~C|4bh#UX;`YAu zWx@bFEeG@{!Su#fFy1fxW`9v!#kfMT-Lhw5vN-iWOq$wcMjkW6C8RTbC%#X@P{H6J z(3~78hI_k6m-S#pH_g34zI|ERaPAw+yWy@OGbyc~Wj<ZkfHt_Eei{7|RIBP~zOVI| z!UY&~Ko@Jiu!Ff9&$JDMr}6^6Em%0Y4*AuXK9s|tp$R<9Jgs^1wjDXx3Bv<lKYOVL zR#aBMjz{OfWBWCqr_y~H2ejP#Kech!&Bb+f+v?g9vNRm7o>o!QDc;|E<N#kU{6Y<e ztYBx|m)B4%uMiZBOpw!PwtkLID<u$NdG^Gv_q{UdKe&;RCUA)}Lej#(B9MD~V4nD5 zpnsQr;K+13SYMl&D&pO(NNc$`KBeL2GvED=NXI70T3O+Kza)1KntVvc)u6b|C<Oi* zb;+V6grUvRrRTl(^ReZ9ysDA*_<_UL*7UYRp^c!TW{KAqXG=jLu-8Qb7nD|H0R>-% zE8t?PjOiU2AD5Y^mwh=I&Ex=}fg!yHaAQ*(Q?o2H+0(eDxWFU*c4NBMzk%r1c=5e@ zuRd@`z#)fZP5W+x05)g9mzJlb0Zx=pwilV2e-vYs4*VM<iYI6Rvs4MTnizOafi})< zo1vQ<4X+pEBk;LaUEt{9B;2`ud&8ZS4XXE9q=URwM@a$<aGh1YjFM4t^Cp1%MSGsA zJuDnN9PT3xgkJQL9mYF#lFsIDt@&RLL{QRS&~RFu)>OI6N(!dD9T)(+p%UNt=&pQG zD0L<n7?^{p;+a=cTC%OlQR67s>&K_d?B<P9)lVtO^Na!hO7p7k``mnM<j&fYQd1wF z+Lq_#*OO#ybes>C<&-3H$AbwO4~`D%&Gz~hid@{vs~U;5w9}}pO$ypQ9zBLr_Aq>( zW(SOEsuwMon7-grD%u77jy+kfPgFJ98%cJ^!Ml$k5y|~|%BiwNYtmbYe_3{<4iBD6 z_QRf<_74I9lSl#QoszaHLOQS`-YmEM>-lsu!)m5$fCo~<&)|0)^*+TJDS;(Q`>HyB zodp@te3hAIJEQ=zL>`JE=<CG4yC?l`-zCY{V|G5a?T|37g+v=Bb}dDz9I^dAtU7Y# z-bT;c$>HUUg1NwNSjfJEMH5N6N8`?f$I2D1bV)K`sdoE4TQ?~;N&WjGm}kSCe3<<N zA#tg|tAsGrPNq+Pd1{aU9&yI0A+IAn6EJ<7Rxe==h7aTWePd_mak$XyCsT?@1zr3S zWd%xKJ-EEknFwxtrl9?7X08U6(Gk2sS+eDTw?kG)G%Q0+O~dL`QkY*MC-260;d(|m zVK389n9K7A#}saq4vGuZcuLBnxZBrK2Nnt9Rt9B%%Yws`B_7a&o>Wz|5EYixSFb^e z_>TEqzuHTB8K2@*py{vK!ZE)-9)YeqI~Bd_*3j3s3Mk$6t7=mdrtjj`l(#3}{w(Uu z=NabjVj8A8n<)RXjp&-iM666tO!ao#pFQ*0G%zq|>RZZ?_uTyOfPJf5Uv5oBPwZu} z>}o{6)6Kua_Zp|9Ktv2WSja7QmHjpl4*RpsZ3iiFc$R&F5|H1uHG5w)#?^j5?^g&q zl{H_~cxbt7KlNbXpr~-A;Gv9)kM`_-@RAX2i76pT>;{L*XEd6wL_6-c!FX6tRM)Mi z$}3hYe)_;YQ&j=3IDg8(|GfUb_@L+z&)ku4N?cs^Fi|THuniWfjmN-6Lww!EKq$kn z<J8iW3mj8cRjak9W#wr4Xi3VM(eCfxAVwFvsMXim?YiCLxAiMN3ge8anvZ31AX_e- zyoD0{OyT^Y7d1sIO3%isKCC=?E>DG2|MKs}qr=NdO{5Z<5=ALaNlCHx)>yNE3pO6c znrpDqHv7$`-4#)xRZYqs<nJ-$(@c+5Nb#|=!u&$KTROXeaQ>EWxO4AcDEdI-OU!bL zQcs<`i88Lh^g(x<`-Wh4bM*?jt>S68Mhv>x355>qV1m+VJJSPNkZFm^><Wb?0}yNF z-*F6|Y-Gcw2ggR}4OeMPXuU=vBIRVH$ATlJJZv7g-gbqZ0}lmpzHgRbFdR$Uq%wge z)M!Id4_yti9ee{@*hj3+GO0nSuGh5Em6c;&PAo(n6|Z-?sIhqO;fF3uQc5aKn-~+j zXJ?dN|J*D-Z}Nm%e~q^fgH>!L(u`4_iQn)}_)+@3z?kM2)ugG_<Q3v?3KxT9W`#S~ zvlqA)VLWbX;Bhq1S*?>FCZ2YW*mduJ(8^bTBChD^ky7O-;)u{=hbA~IFC<J`c5HR= zS&}XNdYW!8Z;*=?G;(k1Z>?zwBQfK4wXnbSl4|rjJf)6-t?r(oKA31nq?Qc#DjcSf z!HXEMU{TrK-I=pc$GMRd3Nfzy!2_(CayG?Jz8;2bFqL|W=+j&T8M>Hb_t9Iju=HK| z{(*B(OvUA8B}KHabVUAJsnlMCnSVU^l+yi^$RvBt>vehOmQPqBMfd}5@TMoLBx+Uw zS@sc~CP(e5SU{gI4ZKCpQ;RQ&3Y}}zxG&_|Up9wXMYQQ6gGr5sd}~yp671t%1T}>m zpJ58Mr@Kr<MnQ*<zjfCx2PB7w28Y@lZ<%V}zSw89Vu;F|(nqkx`7sG66`EXHThzBD zhL+Rw`>r9;)zzcE4Zfe|?(VLi`N4ziUE~>mx7$o@oHCn=Ri@WRuiesX3x$ND37oqR z)-2Q2%+ob-%9#4LosC0gp@CR#dIEuNd^e8GuX7#RYRd}fbx5yY(RIhyL_FbZ4}N(E zH`S#SWZ>0YX(Vgh(eoi(zrg>rId`Hert-Rc*}6E_k#n*Vde>iCfLizi3b4%ZTJOX^ zd7eTRMZ~*gIBrhHNQ4}7XTk5Tc(?wodwWwqqU_^Ltul#lPNyoQym!xh(L%6G>iYzR zGIy6<LrpvGr_#eDfS`e387&-TN0iI}6Tv0?PL&4LcCZV(?4O*tlLJvU9CscXmi@us zY7A#BrqhLDzvyKbyoXVqm-jzaTUx9ZIESsQv?ysYNJ~j0r7xxmM^S(1Un|F%04$u8 zvBz-klV0)oa$GE330%Ea(GQbc8z1^q{{maom2xDqR7W<8i}-lf-8xb!FO016jGoXi zeT1jp<L70Y7hn-lI5Au6{3#seoneO3A5kFA7Y2GPRPsi3Dm&_o1a=&Vr{n(?iSEA` z{FN}SpaJ%T%1E<&erR_3@lilMXeUVX|Kk34kFU9s5%3q6+BAvMG`_||M*MQ}4p}$A zGi|a+ODnc1%6|W7Svpe}Tmvd&yOx-@qM#whjX`riU)8~d18d3VW+b%u4DIvt^XGn1 z;|tJ}*cC_FsV}}&MN8buxbSwO-*Ck>{CU3?IWCnu{bHMamBBe&2}*9XO{RKWTeT`N zZY_MiubjDetY8q{V_FYlo4KK2`{U8GFXUl#*!-s=rCO>;0<dl@X(elgy^55ScKLKm zMMBqB%e=0%bqRlYP#Yby7m5XCZi>TX?}tXprClB!6NnRLSXOZM>57hpfrFd{EGH}_ z=Ub?adH|Q@24$%UlwZL~hjd_llJr}5am+=jUIanQUst^<`rF}+t@vn}u_U{o{D2ad zL*^D2<F+c|hcw?258au@o>Ep3v51E7nTBvyI3{)(vtcTU0*d2nJHFjJU;}--ajeSL zU^i4O2y(P^FG4&BIg)J1RISLCS!)eX%No-30^u%UvHE+d(wXZ5);~}Dd&?84%iW4D z1qrncJ>lZ3poOh@{^kmM9JjMnXPRzJb3Gk`FT+f{Y@4O8_V7X|lL`^5%O2<l#Tz?R z?S=MucUjbO5;gD7{h$|wvTP5ld7DjTR_o5!8BUw4<2;;%$^q?COl?|Vnt`APP5@D` zNl~9_Ojd~K74J+%LpF4&6_>h%t{9`lAHT)nOx@1c(kFt*VEIq?OFa-J97RSnbQ+jN zbC@<<xUP)~AppA*6%kMzqcKOD2r`d-;LThs(pnt$h2TTJ3F`4f$&5=I;b%)B-~TS- zZo}D5)E@n_7a|o)W)QiG7kyA6WPW09x_a<y=A%X<7wHk_+iiHSDjr%CxUgZ<QBEnD zD3>n^!CGr+)2ujZKp6WkEPz)}JKVGOiq#Pp2B<eLi8IRvjA}aBghJj^H9~S%c|P7w z4UMF6#mXtG(Hwm9XozSM3aQ~G_2YigegK~TCIuaksdnovP)vJ&x{tvBXHvj_G`_Fs zAK`uw#e;vxlF@&m?&ytzfK@D3ZpPxMCWC;11Jv844M1o9Uy}y@<M{ta0KvaLCW#}% z3Y)J_4&X~e_1FJH5$B}|7OYi8BcS*Nfye|+jO`*kI7wOpPi+N22Y@f9vZTAPm=!1l zTM~GGQQ*`ijy*!Wd!-C6zS{SaT`I(*_>1<~ll4-uKNl%}C$ggf%gD$u>q&)&5rUwJ z5nQN2T#D!ZFCuMZnx30UhSj@dmt;WyJs`*{51ApQ85tZy7LB|qE{#S;cA2OCa143j zQI7NBI=Q=x*Z?(83U#}h5-F_rtIw-s8V!A47&$m0L{ue$tkYXFzC&SWC&a!HBQXqs zHM}8~$hN4Wal!=ALrPM7tTe=apml0w-N35A36&&utqnse?0j+jX3bI}DxU-3ESTLU zD9Hw*O0$~~CB0xFlpT2=%PU}kLZzmr2JIdF`NL@XkMsjr8k%2dR}m2r$*gZchKhfB zdU`=YfkHuGpg7>3mIPK6hi^(o1#CqX?O<fM1q5Daf2pmvc}h!5D=8>2vawmtl_odb zBNwv29iAhm5aR`>y7K>JfSOa%ZreR>g$kTi=IRTy^>;LO5HYxFk#_A5_<oTNsQ<M? zyR+dh8y{S?<=$m?DPzWn;)|k9#pPiPp#UbZ>KI8d1{Mka81bpyqr-cANv|s?R!?7- zDNVVs5LQ)Tg+J<l4whEn!qr9!dbAEm*a62tW8c6ENkUn`xUhq-4ntL`H%v4Pw5d!n z$e@W)UDzK)TG8^P06-t^`ZF<Xr=(=f3nqB$(&v^9c=;85fX7IgiW#NMJ(U8y1wb~! z^ad8yrFkW%)FDK$U!vx?!2f?iL%?>KM#e#LHy05Eq(~5VeqHzL1_b`BY_qkFtS6}) z1bkjmzBwKjUC!E{gFGXHBO=B}M&{<`CZ;A$fH;ATudP5G2|SUof$`BKB@92f2mmKJ zpO_Tr8v%$ATz36Kd=6_jMI~t+5v9>r5h6Y~H;0{>I>0w?Dwb-nSmaR_aFYtj@Q(oo z|9S#JjKkcS%NpH%zQBt6BhH_fJ(Z4}yo5V8P8$dbiPg2`0X$K1CHNj49PU~@uR4ok zv7M&50H<=Le!9>w*(?e*y4AivlUZL|S3IovTx@M$VyGPCEM%S*zSaPvX~42UGQa`c z<avb}o6+t-oo=#LVmrMT@rM<56foKUa}9W4I|$3kIdxVS*B(YDb1qCsMvg_oMtXU? zg5D6jD0;AgK|r^SZ~%gtu1s5W2!AZnIZbK*thI?W#o=596$67rVJ8XT=HH89H1c*; z{2jnRs}>?xWL^ZnCoTD8GZI#{=wL8dqM<K%f$%H7PFRPJ?(GpEZtb-^&Lf_hQbay$ zFPhIv(DzbQgpaK&)A5u0^T&+Yid;yzU4rodaQXk|&oHP`?-sxF%T#DP<MHNxH)<9R zQX$c`_DIHRvW*SWoUhFTUXyq;T-L$q?jR-)a{131n%22R1M2d~XO1*Z!kS;eU+13S zy2WpRAQs{zGb(UDQbsPilk6cP;iQ&{m7LSh-?j*y1f<`dXH&6QtxY9>)slVo%rA!H zbPb^J`1q?J*6>Np)r5I0f23uqGNQxl93SaQy7l!&hWk8cEp;}Q3-}JbNe6JI+^rF! z<C3OYyU))jB<-bRSrYGjNQ;jzfoI^#sMO2m4wvsuiM-jji8lwp0YqZ@XD91q930k5 z_hMNROWY~_<EKyggzuz=LO-N))Z3T{%NOJuj$~G)ysfb%5oCZ|ABIH@kZ8fV?RZ!v zX0$myB&OHcki@=hJYE3e0^mDE6)nk3FIH~o)lPlg|4c=6C$P!T)Y7s(AvC3RK{rtW zKM!M8Bu15P)#;@c;r<7p$<8v)BIP`~-@GbVa;h&E@V}OL&AOp+X3l&;fYhZUrvrh& zz)@tgS?C?1y;^dFttLvtmpcJ`C1yjuHPy^D$fsOzaCMwKfQT7z{SSc~L_9wH!30u2 zK1xLC1nacIEB^(sCMn6pyt1P^KSC;MDw%W`*?T=0V=;+-9nNhue02E>#LV*v$_Y}* zOfK8smO-ktSYcGIPZM0^I?d&XwR6C3ivl;94sm&fKnplHfR{q9nh)7(Ej8<<-Q3>G zaC5Syv%7}}#1~i8zs6sc12&=AVS|90)tO8E*Pm2QXd%ij?sv+=Ys<Z;*w~4QiOUV& z4zA(Cga$JB-4F$`nX@F^kt!tkgi&H1q{+a-F~(tz$5Typ%d<6w<!g?fXis!;88C_} zzvLnSrmT4Q0mBZS1d2aviN+a#U{ilOZjAt@3e5irhJK6l`PXjx-~AZqU&lYY{={#< z@w%`DGvZz|xxDPi4jWk_zo5yfq}9e<@7g<kJmb2Xo27{R<9d5O8n;P9(hXP`3k@Mo z#>`9=e~CyjdT@RCP?x!D*O)LzUbe*Ubht3nqn%m82`_a?N`9SP0uM#{ssC0sP6Y#a zb%ziSwn0_urB<HH+C1LtH;1w1ty)*&6~I%@&P2SslWiVyF=$bz&Jy(AlADc&j%CNZ zXR;MrXhx76TJ7cUVlzXg@Rk8OusTY}p8~|8-jUEq*_Fy;eQg<oJ1<MZH7`Sp0s-q@ z&H-<?_3h1cH3Qm&lE9$}CcYWgI5mcaE6{m!Q^C-TYd7EO+PbYPw~?7Q)VFlLRC)3q z(0s`^Q<Ye3c^H`%1PUtv6qb|%cX6A~)p0s5z|7HB5b=DQl#E)vqOdAkVfNgAzmL4; zvt58#X|5Us^d^yJ8`5T{3t6f<!@rEr0s=+h_cFQM_}IC>&d%ZfKAQj0&qGC=Uprzy z#vzoKV3}5uoj*jYtI)yKa^(BF>>)gW^y=ygrvLE$44cZbv*o*x@as}JK7uJF*X{j4 z&q(gJSG&75%LMC;7a)u#F$|f=esAjw0QZQKyY(5lMWoIb8>`ir{`#BcSveDFxr=?g z=rJ^)Y~8zeP??SXp<H+`YH~F`4pU|Xk=6{yz2zG50S}31F;`FP5$i@FGRiyQaC422 z!xJKj(Vacr*EsddPr+AfeQj+nckGPsMbr70D!Dx}E=!#w0CHcX(I2Bhk@V%g_)-=| z|E=D8;;0y`pLwET%Flgl&DU0~X3H{eyXUX1&RN3&kE-nC4(A|7dpy>1IQaPVc&MSN z1~RsgpW)JAsj#2HexG7v6Thgc?3C$+8)~h?mr|hg7Y%R@T)6BW?V~HNmy-t&y;AQW zI}GpIH0aBQr_6lV**=Zf_Y86}*z+N{CY%v6@j+v5#Nac?NLr9Dn8<OA&ERvn+G%^N z?&s<7qM8%~(WAu16hwQTEZvkr+o#l9?Ht_tFi`rmzOT)ddiVW{N&g{xt?R?W(P@M+ zBHldY@0**y16aoO+gir3m)44w-0xV;G@HR;O{N~df(pcCl^DIV3}6m8Dm;b<SgRO= zL$-QV;gKT>?tSDp1d`|w3M}3mKP;WpbhO9DCqEB+7JV81`0}CURpx@mL4R`MAxj}t z>hjKtcb;Y9y)3G>KcSeqmYRr$7}ExnwGe&K(ZgRpzWKTg5RQ3$1z!N3o*((|w)X); zgM*TiPavoq(gEuZ-4+`d5MF~vp5fxm+2vC-g*ub%Bpmy1I<kB+3ky13{+sG5HV*b# zzecP%@(p&kjlWwDqzpFr^QQVbngu>p^!MRkEt~#oX34&s2^{?~#Y_3q=6Kia{>-J$ zVU!T)TmBgW?`)i~IXWW&&K&CK{Rry-^y)i~x<`c1_k4}}b0rb!zBvTF6<hh_h3)R! z>)z_dbe|jtLSVxhZ4ZcDs;87#ERQsV%FqeK-+gZD*yL@vfoK2lVjcQf9W)`Jt}0ic zlekA+66#zoa5OXh&M6!~49F<Lpk`r-oy|Zb^cp_zUYH0Ve4mc=IU%5hqGJB-5o++^ z-6R(WL;BG87S@hp8gHnUkF_y1M3`&{xN;#TuHmD!aK_sts7NlR2L?10x=j^5P_HbA ziD83KUA&@Pu1+E??gyd=296|dC1Jpxr~$XKf>Cm~xns*hLPZ5isO1KF#o*%iG0={z zRz?mgI1+}(B69E?3}K*Oy}w3*<CrC#t*^v=hUK1W+>~J{uy3KAf`DS|JIeZ2pS_W> zf<J^i<f9)^@BjecHE1J^i;}>x#=Rlwbl}n_S`(w(VBgen>H%P2gNi8HT;KSRoIeWR zQce00CLdCBo#Hrcy(aCVlIDv%HXPG_KU8#3-P2p<Hmbl_8SdpS@U)0o={mw4@uh5% zl7Y1M+w_3ZFn#rcVLFzT?t5wN1mH}dqjYSC8Q7wtQ)FX%vg&)i!FnDu=Ik`O{B!#n zJ-_f1_zKxmJSi!-UWUv3c%!-7MjEyy`5x)LDAa_LH{8eiEZ--VNf@4C`VzgOSFbMT zJCCW>TjiK(w7ay)Z6i%lYxZ6(PPMfC#AV9P5>4~f2D`?$gzr3+euTqyt*g}*A8}@k zHfZ^L#C9V09N8+2vg0jmMZl4P%~eRdURRqUw{w*0%SFrN)C#9Xwi_RYoiNKtIWTtb zcBF9s%e+K2!s0z0Mq&(tr`wm_94{+7Z6`$sUGWm5vOTcgS^!^rOk#ANiHXkNgvPkI z&_BMqK(8gDOu%7RMz#iL5Dzl$MFcTM@6<*l5AIa7Ig2lMja#m6tkm|T)k3rV5}z8< zj3%tl;XPF_Fp$MclY~8&j6^Et`6^vHj!xf;=x+OauF^15=eGB;o!)bN;SGWz88~f4 zj8QhC8_er2>dA5rFzj8}(;<f)Q(-NdH294`wh%L=XGIu(+p_LdkAGjc%JK2h#x7ru zG{b9_pa~k@qpfXxz*g|^PM%18LpnxvPUi2ET&vNv;@umrA?QQ_VS7-GU@~V_tgkOt z7$8F?D5EH)JO9y(@A(@4UswS1p&Kn0zJ-LG7d>TSZAo229PLPc*sgJYe~V|<dQM_^ zla&xsMFax_4I;H$d#f4{)xrK<I>vvcLaK74`Dd;abF@F>5wGTI$>fovU+dkd6#Qj5 zixD3S&CUxrcMhtPli6ZcO$v;DOa)TXO7HvR?Og$ot98_Isd5g(MmBBW;o;+E$cokj z_6g;YAA3JoX`c?qdtzRX9)&Los=PBmqjiP41`&IsQ^C0c)(X5UaVAkQzXVCyCfmqd zE;X1B)543Y5t5xhRb&|pslZhVyj@!E;LmBI+6kay<y%Sc65^7g3gj`|?XoyZwBo0} zwFK4N1;v@qA8y~>LJOgMQA8vQQS*$Q&VM;psL?MSJTQny6cUxa!TFQWiv}gOfq0S> zD{0H6tkPvrlc%h%C<T<XIfdK-P!Kx$U@ANN=?w|Y;Fo2!I#y}LcV3l-0H#8ny#ym` zP(_Bh3DoYF9KP9aRD=J^=nzyXwC8AQ@n7O7G1-|4cKlS>W%1Dc$(d@u^#(~`6;U_f zKxV-R(P}I3@Dnn^D0ajraF~E?SZahSns1b}oN8yg0jTQUhD%vhoFC|+T)!9We$oC5 zem3?1dMN`g4T91(Vp!7-rR3CZOw&In>w-HmqQ4Y16?L7+m!x2Y;m>)yl)y)*Ut-8E ziC0X(zM`t)LUK+%aUZp`wLf}sDw6stvuRTcWI_p?f3}6E<aWY0B^^==_y@$%plzk< z_AU`YptPTO;PVgmkPX-GH5$+<lFG9m9fqj^<A*WGGKnH7SjFCn!)~-6fNBKaJ@pI7 z4GmB*m*9x|4PcO_lpuXjpp%0KP$u|)54`<9=`6t|FMWst&0<jkFkViN=q=IUg*bn* z-p@q=&Z<DgS%OC2yQu*4u=2!9U-(1eYY99!2ayEMR7w%v5LW1gfEIoPl8IefICN-q zbI<UKaKzgRmgA+;GOm^&EWnBen)rPsau28|?IV3q_jA3l^d%YC_YK?mPm~o*n_60? z@;Fv_;NHOUraoos!YP8HrDLG~0+bbfvOJB~P%_&G!AHEeZnpznN}vP-H+ZgfG?vU3 zfOCkknb<W{6;+($MN#}IC6Cg`gux_3c%mqItS`iO?7l>^kao&&|8to%MeN(4R<8rs zzxTTk?O6*8E_|NpOOB;Mkq3Is<q-;1fWBLcgV4?G9*9-&L`IU9k(r!P24X8}t=DX> z?}Y%Wo$#&zj0eB%bxwt|pp1jX>fhg*^_KiDE@xX>dfeXI$>?f*vV=|g2g<-w72P=` zi8`AFO@LIGl_dz4_5JJp-FSB-^Y`Dq-;ce<5y^}$lc5JAWPc_r-zlv(xK78WXddFF zn7w?4JOEGRk`~snnQ13dz5aO#J3vqPZe+B`R&4yu-0^0sY$mRjo^Q!&wN+7gD@eV+ zAH-_C@Ci+RwZgXvh}CqCj*03ws_E?kbP_Q!G?d<(W(u~cOr})15?Xhq*Du#L4=IZ_ z@4sFS_^>Q<eEKd$DBzJXnVaFs-SoKD_OLNCh|qkH2INo_Z)5fLl262D>4oi;Lh(WF za(g-LSI^cIj1odU0I5~wdW|#<P!y3mfnBTazu$VgFCtB3tL;}9UJgxX9pyx<KzprV zs8v~72?^xY9sDU$=k^3L+l)FN#Mnyr%J4WW*MjBx`+5Xi??dI9b-e|Lm440LG6O1J z8<W+z49DXME+al5A;MT**QclG!Vh?;ML!<IILzNL#hwB%2-x{(F+)!9_vucRw1cKh zsSVC19y^x)BV#~>%yqKE552E^r`?b84qaVCadJ4RcoT3ojQNGe69>}9)@0;lJipDq zs&t0~ML-XL#PFXzC9{&^LU4uvbo0rBefgC?(776icqP6xs16@PF3ddyMb$Ch2*@;P zAo7BuJi)^5Hi?ecrzhWBk1*4nhIelZXwWqlN0Av5Aie>0YV2W=*B;@eY#QWj*VeN2 z$>EXE(cPTCgaJWla0xO}QeUTlT!D#@k#JgWV&aSLcML%PPcb#+qKc+`D%g0)R%uw` z1%TfeQy{RS6dLb@12-ut0s$aE@sb?e@31BU(_)z~{P8+R*9@_aE{3VY{025XFqsGe zr0WIW&CIydCuV1TUjY~<;5C;5!z~(cUWOF#Vq#~aTuX)!ijwlV4oxjiCXPiy4#P-; zd5;=f5Rrm*aeWTAJKyzVa9ToY7=u2)uuzs_Ya8uA)J#efNiU<PcX6pnlOdz&vGw)4 zfzd*C%{LE^Eg-(mT#M7mTA<~pq4_;>0WLrmF$5k4T-?`l8NFtQAh4g4OGE27@|v1z zX|3)mzkpHWe;`8_x%l6M=*tfL@0O7rd=ENjYOE;~@^hbHBz}I1N=o9*8z(zg_X&FJ zxR9MVyz9K%u6$8?3MJS9uwg8KGqr*0sV=t3$&hBNQDebVyW%IyYCd$C1<*wd6d|!< zi2#QE*{T;LoI>nBk)<E9bi>6p;v!o>BrK4l1p*ZithmFIkJ5(k)2BdRLgVF?XU`RS ztD&&DX-ykNn$AytJvh|RHxpu62~=r*Gik+~0u_6SEYITavnbjfPc-)Z=9#Oqc_P60 zh<GK#)Ekq=lf`UbQ0DL7m_Ile<~}p45&%Dv#^Kdn2im~L17atkQtQ@q0fEJ#&CMK> zLQd|7F+m?<k6-Y4QCstSDfP8}{?(z1C)_Q_8oKS**EdL60HniPB6M~l)a|Cz<l3MS zCjK|DDN9zLRcQ9WcbK@7d1Dz%6JC3Ax{m>9#b)LvdA*z{IP0XfUE76L37d6X@y|PM zHV>RL39Z)a;kD;C+?%{^2wmoa!vAcaVZ%<n2aS$I;A({*{5(H=zz6pEp<Fi)C#`y~ zGNr^@cthsQz#no#meMv@iNiXK_BX$<GqNPc_MHw6%oAI^FjYYJOtFBOLJ-?WoE04v z*~RJHa|BEcE{y_+Sh-+%05t^8{$hh{#F-56W`y-*YH&GqOQdlYDx_cKw9x2|D}mi0 zYjU`KzhFSxkC^A8*N0iRJ2)W8Vwlv|76<GtZ|Bn_g*H=`q#H-~=>*2OOtb0a$uto_ z_V&v^O|VR$rorq*7T2wo9U%{^Ujd6SAo}EXw9mnRoS9Mi^K^XtIl@gwjhv0`t%e!O z#gm+hhBEe8dw?@sbD$!?hZspF^yFrKpuw`X<@X@eoR-sDqu3cA8yewn*e4@f*gBr7 z>wPsiB_ZpRBM{y<!M-UP8F?ZG<BI8Z9Gtr=167L#K*IB#y8~cQr^CbA{c|^R^kdSA zPu#1`-o_%NeB7;$&o8kO4Aj(Af=6CJ?5^)o$j_gCfh<dv25op$W4j0(=v%QV?AARW zk&LZO;md8$wE_bD_xG`buXbr${ya7Si{%?lkT6dg?ZmOo^J;SH0Y9-K9V#>`CauZl zRuctjAc(yIM!IkkB*bug$}4xl^HrXJtv%c^G|>QL%bB(v<RaDYAtZbuuy`WS(4TQ4 zk<EU0-x=og=@YOzt+`&fsnwZTT3eY410*+BU^&#!{)oSdZ1LIVVx_@pd}ifOt1|>^ zP-p2L-AO`eO1`G8_0I;EK?%?K8{6~x31Hbpl9PqG3W_}a5b?ZbEl{dn+dIA5-+cm1 z?SK9(-}4~2#uB1VHJgCb2NL+^*!O@28wSP;;prYmxj4Zx!l&D9iLF@T84E226xr&Y zw9aQY)8HfHZHsqqliA$#+K~}!z0$&v-fn7O1Zzkxhu`Vah6-IwThh6({I*0AxZ?w< z+~5CgYhd5&ajWTRW@}xq>jx!Cp?4Brz-?N36k&LM%Dt`tpf+m0UKGq}sdK|M+((~M zw^&fudw$UhJxs9Xxw-p|VcAX9J|Q9-XaUdx{?y63Q{V8a_3acBO}$JIc%CM*IJA4$ zMxL;(Ag`3svoo0Rd8vBQfNptVw$zeX+2VNSB_==e^eysR5a0)W!rk9TpJ_8k;Bv)H zB493PPV@L`62;)NKAveaDl;Avh4CRCfV|L|IcRzAFS--4%J5y=yckF+`1D=&$?066 zl1g=2Du~)Q{NxopET(oEMaH<+Til-;9qDQUZ|gADQFv;vJVezUB2o%`bDMYfvD0|G zSSDWawX{cOx@;B}GH97pO;aJp2wG21aEWLrx{Ycm#&Ju_i&K)K>@FvM_uPL+=`=z@ zTyiiI2Obx4Q7v#5&-v$4d^bgD2_0P@3UC%fGkaQ8VxmsHx!mYdf_r_MoRm!deTvwH zFBQmYi9=6HYHMiG{o40R?Pt(yog*D~MQUDY4yTQ%8jKj15e>3c>$T^j?PVJA=qG$5 zqwh=)@@LHgEzf41hj365PVOG_qbC8*xT!yMTF?GgySKCJ2YbF^Wkh%47vYOi{~zQL zfG<H*!~p=tArqWHh7`n+fbJ6Qf0;|Ntr#;MKI(!h8a{e`d3osv>GH1nAbY;lLIz;K zvi_cpjg^WtA!C%(8kn~Mh)R`yW;3i%_JM;y;!8Z;Sex@z;~l=Yv^hFs%Fu$Sf*|7) zfM+RAEATtH=0=Cm%lHPwXC`{@9_uj1=_3}4T_mDFQz7QiSOxaAL`iIBPYOTy=JHpE zM(~J5ih{`da)v%6BG7!jQ0qm*lSdn=I(=Tk`uwZ8Ru)4|p7F>H`4AwHEAj`|#dIK< zt+br-APdy*w@9ILdeW!0z`$ARAY!uR4^-kvO8ATWegEOpt@T6e5;j>obU2fWQBA*V zri+Mz2fM^;GWtRv{+$CqXX=IkXU)dzekAcAxY*F5tAw242M9;|T;N{X#YB>ftb+@N z3-_R9Xd_jv4Jc~beg9!`#%l8*Ib2=*`tH7L+W@URqxls;rLiM<KP_{-<Ac2F8S0Cp zP+@54)L^msu7+;!S*dVqEr9<y6_(!uU+d}zvK~p7K@X^BlooNEpb=>bqRpn#Eeq1| zuJ5Xz&?^fgWy#DfaOr(tlL8vA6cIN7(4Fx<aS<Sub`FR#(9k^gev_{^UVHbMpQqLw z@=<AvJd3cfq=1Z$)bZ-_+qsROT3fM*r>(6mf;sv^tE3^u*uSuV59JIQ0+*2azot8Y z>CWZA;MR{#24%4#(H;;DV+#9EV50EK&3Oy-8<K-rWdHXg{Qo_4IQV9r2$2|@7@Lt8 z+c!AAYjgQE=C43y5Ad%S(8=aukmc=sjRD}D@h|BBkDqE_P;y{Ys-S4-Q8{UN6Jw`~ zL4blU%m6$@DLq(R)$=m{2j&fwm=%xdU>=f+_}k7t$*{3!1llz!9H2Dwn*`Jcio42@ zZc=jM9;#CQ!8b*)^b|B8v=OGaM!gVE5-o{Ng#m@gw^4)BO#o<W^Q0BMti+wnSZ^o> z<iY~W-u4q>U_V(;evY*IB=jN#i<}aNjQDjX6RK{EYTzHF<zOpY8j;8xAP(xx8wWde z%K3+<bbpF(O}Vzs)eTZ|O;K}BPU$)P6~gUV6E+5pMn&62(S>Ev<*|rWQ(pKRJ3by* z(Bcx)HJ4~V>BRrkafz-73sR{p%3f|L&QD8oxX1_UNf8MyL0_#?{g2*rG9pplh&g3> zzE5d-2@iikeY>HB;o+i>V>g<$)n1Q#N4-}SEG%}19IP;lKrZ|IGL3rp=+vwV+!MxP zPP}X`dCMP*MUBQ`thC{Q0ZS8Ob{mr`=DEzwL^0rUsYIQu?Y%T5I>|Y$U9x%R8<J_$ zw{O~TA|8(%&af01=jR)(HXLfyw_Ws5oviA@_((1;rcT!w<R}@99cdP3Odcg>`nLBz zje7#Jk?Nw`Oo!RZNhRf&*%{@b(a~X<eGQdT5=7K*VO71o)%KTm_OA41MMF$$AQ4hW zOchs85jIH*P@D!Ex<(5hv{0ysLvIAAZ_mzaazl*fy>uz`W>jqK;q>^+g-H8-QXCc% zJCdA0%Z7t(mEAx@k~-THGp<kx9kA9~z?qh8*_ftpsx_4Bczv_V&Mz+e=mje3zo>Do z8P>x^M^AjKx~PNM-}QjLOPilV8c#g)@JG2^4tzeea@a$}J|{@_fDit0y}Hcb`hk^= z{mTaiG`_o%hJ_eRPlhyin1jfd%{G2whnH^mvoee8&OB<@&t$uAm;(G85oe`-!-5_# zcTtSZ^1_xAvT_q<ylGAoxMJQhKVA`AUEDf`Xg_~$K9kE>%MOh23Q*0-vKm5rd=?Qz zxdej;zSS%m803_&fCn+-+8HM?Ihg8pUYQg*VF+Bsa3@GZ&+K9nVkS5gDkkuH@^`*O zqU2E%FG=Ox>%=mnwZE<<S)!tb82=PdQizF<`|bx-Ec@6L*avu>B($W2;NXMc9)^jB zq)2pPDA(#Ms1{AV^%)^dVJyM8Ab6n{r^+o=CPD1HotdH%n7Uv9&QOsDys$|@h4kd~ z@$q;%m{8KyJ>M}LU9#T+*U+v+RzMJr3Z|~a!AUb*GDro=x~bL|3xGFv<iDghk^3nQ zP#<CM81C*Hb$QDP5eCKHdAAFFt8x*FU5Ekh(*$Xq|HlBLfy~^$o^<a@e7byyylHU2 zsQ(?~yMH2U!T?o6LcJT}GpsQ4HTn<<`Eh_r85!r;6Yr!qWf;-G8KgWVmz$`HP;Xfl z#ONEUGL@katp#peGyxIAHtu}Ke4<fB3qwv6F*Mu(Uiv5rBTd5I{LC5ezC<9aG$FRP z^AO)R>OF6KBbT;|@GG$%T3MUTh$+~0G1-KM7?!Hz3RWIeCLs!r8R(bJ0l}eZ;KFb% z!YDFZ*xEAPe}PVihJN7KP<(hGxc7MtRuY^fh6tV_Eiog5dzmij*|n>gLTJ#w|H_9X z^a4=}h8`zP9VM9%%_%Wb2CJfGiP~V9@CmB`Gmt>l&j}D1uF9)|_2JZ$vB0vb*vp6~ zT7WaDe6z4TyOsB5FW3RlaQ?3`M*wU39}PDyI^?v%(L@t^!NAeHq!9E|H_!%CCE%o@ zeM854!WMcl==ivSVZ#NZtV<s>{160XWB(v2r4%!zE-xA-@Lm!{E3Fr+kStG~YcZ|G zf<8<vjS<j!nQTjnt9}e81x{5+N>fJIcW4aSKBUwwZD15DicG<9h7_6@(S>?*k!<uA zjV3rpmW&Fww1OBm<UB(bE9gyC24v#q;NuGoh$%`P1KuJ_zYtG>?g^j+?OFf*l0P6= zduquk$EpAJ(bp?b2+4cEzxnGO@U^&DUo&(8VD`%#kuOpH1#)9OP+DU}<x2f`=js3c zw)sCHNAu?l2>vm5+bZJQoix}g!R~N81#I*?YELRL4j0D}Fg0_qhSln{h_Y<5bT=t= zs-7R5eyvX=pz)crGc>rKbOx}{162;D&ws9RN+9zJU1^%nh*5!fV}~&S66@rRj!AIy zc-{T59+{=V%sd5fFBC}nIuypMWT*Yy<$<Yr$M=0JOIli4%4pOlIv~k-_`npmz(^d! zS2-$;_GeNOPD=~bBoX~!nc&w$21ps=`+y)Z*Y;<@eO5DV?CT=g#{|H^?$J5@M;hWU zXXigtO8{R8jmd&tOG=T1gCY@)&%N$btJ4Euy}_5h^g9bBkNXO{slK;YdScOt?j)&p z{ZU?dmZ*9jNyJ0DE5&eGD3TJ0O}STFSuQc7mf@x+*R{c#ag!r#-nklMIN!$pq*DfH zRY!Cgzf6St_P=zT#-4dXb_tUzDJo_w8fkRbs;!Uxv`m33-=BQOZm$3Ie7oD+mTeVl zgxDuf2pHeRC8X$j7?X76%#|-(a8Yt{Gpi~}L9!oi%sBi_vqeK|8Ajc}p2;6YDPUaS zH|JpBbv|6$YCK9FNxtfnPiOmk;g|R=aLj-?`{NT)mFs@>+H<o4tRmur8uvRgiULMc zKB!ORo&dyhOZS+`X-7=K<Gc}-0cff?Dpsx6%^+Y=bRg1%o|_|s3;>>z7ALE2y`e3j z76Whq3FKtgyE$|tzATCDz;U1a4o0(;TK=L106hYcfkMv)j}kM|HIaP{m6WFbA|B4= zsKf#);=2=v(|J$Y9l>{`=+8G=*n`8vaP+e!j?-KS6mVrjJu@h^#eoqpd>VybvPu4# zj<VA5LM0Rc#trzo6ri(}j&5EQOzYoRXf|^Qv_q5<0_pYAYU4}Z7Nk4--}g&(n<D6A zlampRFvx(2ZNW$&<!i!NP|a=b)4YU(Z}+B!Mm@5}LsSNn_0uI=jq@k<<*Y9!6`3Ob zPNY0E#+#+Tfhh1OC;oc$x`uDBhmBz_n%j?KpPk;L0nq@@AMPM{q-}(B_{zZ35;NsM z;_q9hTo==r&%|e{G>t~SX{>|*f-5e6dX9<9$aohxzUf&=)mD1E&>mR+`JDuEM*iyf z#%sck7Auez6_XI{+oLsp*Qw_!l?M>1jwTNl5h!j?=QXs`=G}_Q%7hqR_&C$#T-t9C zp04BNoDbF#<Qz^W%kRgun?3LyP|%F9`oig^vx|nO7Gu?t>xA7n{G(!G@=$gv_89I! z8u+g!K9G%z_AY!$_%dS@uj3BFA|erk%BrfNV_OGZ^%d)#A2-YL3i9<;B*S$U%7{rx zW95EG2fvZm%uKM#;XM?4@Alm#vqRzP=I~Q-1g{8Ibs*SF%5uTP1MNe4^k52xW3T<2 zul;)>Q7_jQEJ3deER2v<c7v+-aCE~sv>R1x^}~y2&;P5n>yBpo{r6hyYZpaPdsC}g zwTW1vV($t{?OCz67FBzc*i_7#wQ7$lT6+{VqiTyt(b(>%_nv$1fA`$q@7#ZLa`H)X zp6C6%pZ9AG9!>z`oT{G|v9Ynmw3?c4pOymN$6SxdQ>Z6Lp7jR%P<%%?u4wjksq2n1 z1Jm`|-<ewa#REP7m$4)CL}aF~C#k^;oxe;xC+?lqWkXv=LHwj);^ROm(c#Tb@tkeU zDA5Z&BLh}>8{MnuCM!7~Vvi>$7H{e`YXllpRxR-WRc=@qAD);|p<dXx`KaXRjE_ZJ z=L<jPiX9l4_9jb{<Kr8XWwUIC&qEqZB%GJnJ;lJBcgeE~CZ`0CMM_E>#Ikg&X&<KW z!)o1^`%)GCC^euWu6G{s?*{k0F@(YLmFfXB;@bH-mc9jQnYa6x|FV%n&=Ed;52ZVE zyoo)}o8Wv(C|o||;bvE*!v}cXMv=NWf0&LPI&@TXK@bw!+>LSnQcOaXKtPrVWoDwM z*VQvBrPZSrhu>b<iR=y)?Sf)CS2GIzIW!(`h^BLc7GHlBFf2IJDxHW;x(2CJ=9Wz3 z$NWLI-*g17?d+_99X2;M3S@oUewLpdBS9%@DymrIE%R;Gl_2OAvW#v)1#mo_*ra;1 z9Ba$4(?9vXTDM~?BNqNXgpat*WG1uFx-_n5omh0|wLv@mxvD*|m?iTLgV(hdUu;2P zqvN7-k9skNj~Yn{C%5u|xL3(2_U~EUyA6x;h*PRAHq0jl((~hlv0KwbBlIlt!pY>0 zAvZ%F&+$(tpG+9d0YA<lB|Vj3^^V;NF!x$NS#-&C$1HnY?v^Z0{92vwQuxxKJ+%xA z)RP;Blh0r>MiwVZL$q*n9vpmJ^0bB(P<dwQ0JHgtt9BjG!@|QW*>PVtdFA@@$#vpR z|J_?WSHX%Ozx>$DC<Pd*%|2B&;0gh!u9a)Cos$8-;qxoN>(qMCZLyuk*yz(1@A;Na zd-!p9A4~xgc(GYrmIP~h>8eR)799LOwjv_2hQ5lv(!Xvo83;=WUW*@&<2zutDftej zMoOuK_+FSmrmC5#_!9HSx2f^yN8iP@0O*vUfWX>(X=`Z)6EQyu*sF^lqXQ_IkIpIF zm>*X#Q?rh4ct%D>+GIq%+iCn^8=bJLUR&henO$Fx1%RodE_zma!UiIWJ)g5kAxqFv z=ta@HVtIrluYEL2yVMJZEpEONC2;5!QFBm|tC*yu6hN^3QcI)|_H(5ftaXQ4J_HQ{ z2LYx^y`-YKzE~WY>+@CX%XFJz<8hk(dSM}<>8T=!qlVOJlG3TtZ%MSNy(5zBKny`< z;nn)m4^;;vC4CvK7FcI4)=xv5=Iwn1;40ZojSV8ZQwi+dulXU*0To&yJPIX$K}e|S zOaq+j1PLq=nE##yq@^YkS1T22{zcmXWX$IO=t2Fjg53Y#4d<P*YyFo;(u`&S0ff1i z!IUw8M?lt_dVZh*Cuv6H#=k~NW&aaa%pGx!wEtE5VpH!QWx2zyk<s-YQ7&04kg+lZ zntI-Hw#Y3YT9B5Uoo5y9ORawuj&t;NkEo1H?^kpcxpGs_&q_-Qd~Ilj$|J>zolqT_ zKxj<6O|8I{vYu40#Pn_i!`^Yfuq$Te2*ckoyE(|aG&xJX-tZ&){UpWe5VP_%ER*}{ zGXu?8&d~6U-&GOtZm-GOgiAWl>op6;cKd&Kvhr`Vh*$<9R1H&G^L|Qdx^mf8ZSG>L z2_G;QxuMY1CmG3;T3z4Bc}V)mc?vbq;L`pC-5GrTS4O@OLDxdVKQul!GizEGcs*9U z7(}rTa`45>vG6j~WYt!GcsDKp*hK(IE`~m+zOP!sZY)-Ep%7~Pi1ycaZkt1Rl5=t} z6JtdNNtDSzCYg%E?zYqSh7V-1C{Yf!jw|-w=X4nkaSZ~TUq?reULL(%@r&dny6s28 zL7I-Faa?Vx_S&J>{U9lPOb!>w=cZdgq^9b-Fx0efoh!lgib9J6x%iUA8pLeAsjI7C zR(;vUP~O48k@;Ov`S!rigA_)31_E819tB{Lg4Ul~M;w9=%`Md10Y~Cv)#;kt)<8JM zt2EwHqFtC+vbkoCSFt;MOgd;UJu<RyqrpVpDk$XY(n!_l%TT}PWqkdn@n-Qw;?*@; z*3uPwTyog$qdrKNlL6wCmADwnFV1&|0aks^m6gVig=4huQMTu0W&J4fatNfZBkmP@ z)@fZ`P}^Nu`PPi$$V|&#MP1Fqq5)8RkMs-`Mj0HX(8g#%wCEJ+JsXYSneNZp&Mqg1 z50mSV{dSi4%R~u#Cu}P^V6FB3h_vnyB7S6B@Bgs=K8!_bj8^4p>>5jEn9F3tnex>> zO>#_5bVazyTWB~syUdn55yp<QDRu{*Gts21r97h??3rgj^*+R*Fe@G%?YKQ&UfzKM zy2rk#ex;iprY7h3EV;hbC#a1yliK_aBHE`wu9s)Am$;Wfv^Eg*W!4oCa8nebct)(y ziZgr~FK;(uEtP;-4Jka;S^t<22f8hDvP<wGQgyR7+hnz7C9)#!VEI$GBRY~qVqjx) zbEPd%_LmF4>w7hAtqg>zF5dVAv#cArhRwQtg3(Mm87XOLV(#^RuXjMeS;Z0WD{}c( z+<x<Lo_+Ox6TDFd?t%f9D<T<@pMkwz4g%AH&+l=4=2Q$lg<tIEscAEpmX)f%Rt<0B z=i$*SvtcQs9a{4QjFCKfK^N9}EJ1M7kD=Jn;hF<cdDJ~w+`gm1;%3V@o{^hgdK$9w zZSDigrJ4r=`?apXJoJtLZ3v{Bs)e7sA~p5r>}Lx}^rE$!1wJnr$bT(<zGFu0cVAN* znv$MjULR0l8#Jhwrmk#1kE>(Ow2Yel^Ajtidu!q!S_v*Uc{B_^Xtu^`vBn7u5%{`B z8~wql9F+BX-d~kRRm0J-QcDve)$Bs3^T3e1bM;h1Q!CpHAy~mr!6Mr~G*fPwDXB}& zYVT(07I-yaucnKA)Fc*td_wj_=^49t$l8o#UhZZM0stnCUcOAi$dY8qoXhZT4jk(X zFEpBVxUc>BY{AX#Pvp~c5iW4VJ8B{Bu+dBhzjdefr^DtG->*{xQ&S7KX+nr_(Kyg6 z-V+WqW{-;VBy0>%AFTvA4?(nO7TfzCBYmow0Ktdr{XGAxC5mec(<~u=L8VCS8;N%e zL;-{Wr#HQW9ve$WY*z}PrWfO}d5yOKUgTseUu|0N-1w@}UG@}2@~`!s#d>vhjcJ#$ zuivudLl1K1@{<*OmRns4L`pDH8|WP{->xL8+6oGVs_PKba9DxgQdZX?^Fp{zYpW_< z9bGlH9{D+{VJRR2jIa=45y_&{^Dx^?rRRyy<c=>t(<a+)x*pE5v53Bv2){$7sn5e+ z%dXCcM3(Fj@D<?S7#QfUNf(`Z1UXtqTHPeR+P;mg$0`eXxncmTW};X=TtNbxl$U8z zN{i)zGR2#GfWqh?X2*^_9J7UGwd*v(oA$G@$G4e?+i_^3SinJlgi*0pXR!+v4AB|q zw~Ybp-?vpP+9{6{qhg|ueiz%m))eHY(|pxf^;GZu;k^yJ0Ch{<UlY{7p7;U*LdyX+ zu@G=6+<BJb&(;$5NSRO&N6y|KdEiE^+@6NFw=hdvnnauXMQ)c|S7HI(u|9NAjI(6x zJ0h{4i7sL84!M?malri+<K%uYzyIU&R?>l7wv<cXH1YVNkJDgP*k<ZZ8<ETtkyho$ zeA42CxxKYkpD?nmIib;U;6*<%zrEhkNY>Yya9%@r=B((C<DXbEDU!HZk&hnVPx~Vb zlq(He9q>5l8&d_NQak;0FLVt|q6nm3^*V8$3e}4b;m{WlZ(hp~f2V~@fk2|b<fAtp z)Dv*@@P}Byz|%_JZxmPU5##QbWoC7|TX~(~kzDoCM{+sc<Rk2rx~G#~Nuk4#i#P#a zOIJ!43ijR2Ftd_duu%);jxQgnl1J2(CnNLi+qb?hI#J`;zuzMrxf<i;kr@FFB0i<k z-t>T7ZBtTKFJry>L=Y6ilES%d_u{$A8BZ9en6no-3@i=u1SW>MIQ-o>yf(M9fI_;* zygPlS+s!=fk$d4yRp;09sw?1zXn{`q1BXGy$hQ%D<L^@ygn6E=s6M;%t{}|Db<Orf z`3+drPoTT7E>w`iY<}b}H4}(W>KWz94X{5tf&D=kKZv_%DS)*C6${{Ypo*;uljT5B zaZn}Nu8);8+?zVE#-R25m=?C`-0r}wbIUKx&wwzRwVT8y*|lZL?w}`_Uc02wWyI~e zf3A2~44*E*uP1Yn@4s0bj!ZV>*LL>o?7du**=K(AOGxmjc^F~K0DXo>H5>~rV@J=A zMfm0xD7^GtYvj>6P+V?s!bC#i9!^gSgl4`M1z7<ch*dGZq0Ao9$TSEE#OurDbGl_7 z!0^s7cD>+h(m0!fU+2&46uoVdz?T?-#^aj;iEu+vK$Kq*e06{6a}k68(;bNa-6V|S z<VQClqfpVoqUGrBAOlAO0zv|i{Cis*CCuI9D%f9U1N&P$G*qGED`304;O9nMeF3!Z zFjh>DKu%`b6TdI6Z4PPdkN4*LRR@({O>Rh-uq}rGsekg;DmU&S?W7sv4<8dykv&Ql zHkD7>nYFV<it>(Kqt?TYerr1}>p&qpGo9;0kSO_kMIaWYlVZ;IK-rg&c=#^;F8UPx zYT^%-32^6FA1)IGHb^iP$(~}B4(@unxUV>{X(uNa@wE2x>@&lCk(MsYP74!{=_${v zpW5?A^5D*+F@}bZ9|7P~B)?;(?G=}BMR%u`4|<oEhgUUPJ%r*?P>4^~cNa12;J->m zL`1N|xcyK&tJ{mHQsBn5o7-6J+fBoy7O{C;4_6_Q7i2`Y<`oveHOzO@<Au)~4jna< zj63H$^BW8AioS1TgyjAOlEs@g*4+WZWPpi(^Cu&|M2vSva_YWfmFP)rqyws!n5Tj% z`A)&<bu}g}XnZNZJt<#qs%Jj@9LUN(#DvqeI4fycsEbilxm7)IeZV`O#SPu<!GFKb zCWn9H3UHW7KpMXa-f`~}g#XK8p#L6Tc5im=fdfZ^&j;gqj)Q`)k5xV0j-`MsHg&)J zWB9&&<HeiW2rB^s4*Yba)pU(kj%_B@-tW((S78sF5sLoaEyj7-GX<Nk#$#Te_G4DN zh(pewa{R-HLUC{OEqz-W8sUG)`L>cAX@`VUz%PTZDg^DY$1g-#yFZ=m60w|+-;;Fb zzciWC^_V6p7UM*Qry9akaCwd85!suc7n8ODD6hP-V#HJ`0nkn<K>&+dTz;-hu%ry< zx8^Q4OmF9}UoYY^vDjnj=2htj+<-&y@X*LaeKokYx09QPY|!>r%jEK&AHG5`>GgeA z>*ql6U#Qk$2wb}Oi7YFzlJPmKj`34*^any|@4D|=uR3^oKm=CqjnKwC#co*`{ru^g zS!N=d`|0>{GOjVbQ4$c|j^r5*e5{}J3R(DL+dS3EDB9IC@?|;+?G1%!7D@b9+g)a6 z>mWg`i5C#iy69fxLSI8@l5JhT{bu`Wk&=KYI5A0QV*0Ou$K&)<k>DkEi2B#ft2I2$ z0ifkkozCRt<(BkzTqp{P`QTv5`MKDd#-8H7w32x=1NLO+mBSIAXr%gG)|o_k8HJ9M z?e7h9YehDc===Pku1T?A-m|mQ#TM6xVc(awsH{G9A0EC8Tl8~eLy$%Oe*A{v3qgM( z<;yTT+~l<S*nrBH5exAamu?Y0%KYCigK~0m=E|MGh{a0nIOJ=jXoQv2{hF3C9o?R- zH+64D;ztU@3qi$1EUQ5sjW2k(xtSih-(jLrquT7Y8E>oUY@eyL@IFr0W4F77QB=0~ z9<<MxrLQYEJ3G6+Je*BLo3nIzCVzR}<T|ad2Aw@>n%hMSE&96S^V#h8#7104Ja+zm zPa@!Tw@1$~UoA{NV7Wq_*xoY~sekJ5`Bq_2yB(B80sn>v$<0uezxv+iNY#bmA40Nk zU&nqlIEgyCqXV0#H+3Q$QYn>hP7kJat|OAAybkR`LvPJ98Ek1wK7718--&Ox%luA8 z*=3zR!RWgoMLc<~jLfW?ZVc=232?3+Kl8-<G1FW+ovN?z@JuJN1Rtk+;a*7i^Th+Q zXnpziw%?>ty*_Sx-y<OuKpe#^f{iu-q-W+dlb(XwdVg9kmc{(&GRVJ}m%_ZsiDc)M zEF~b90lK{E`Hq?T&h_%ITpSYnLGua2HNbeB$ka<H0vJ2sFe~7`HhT(2Gsd?VU-2ff zvp>=Zfg^dTySpmM<z_8)=#lxVLlUYprrQUHbK^_wZq7EEF$WdPJ-$=9v6dHb5w~JM z#pD+d7?p+0o6uWRyuTZ-HnTp6bX)G7tnb9%TpqZMheyx@Q2`lR?q?2X(3+W<l?SbK zdW{dSTRvB&x&Ju#ZDtZIF-!8T$l<v(rtm@4$c{Dw!N`!G;IRt$Ba!Q-YU1Uq$rN$l z$+?rS1A@GbjW4C$N-SV_3u9x;NzOH#_k+OdC**FW2#Rk@;?FaoI3g=`{Z0L$#!yC{ zrpCttp>WKq>7Ps`G&wg(RrjDDD~@sMA}_|z+1XjED>gklRkQYfhdFS!B+Ps(N^eO6 z3Z(+tfRZ8movC)MMJP1V8-CoF^+*a{{u0kUuXTM(T}=f<=W(<`(FBAavxqNu2X_)& zkAM8)<hZJ&4Ho7QS^L^q#tuQ<c3J8WN28xF+^=@A&_O6bJp~AP0!TrEKt-d`ozs04 zEqq7-gawOb#qX#TJLuj^W?hOz<x!i1VK9X}jW<G>XQ-=zoP*<;Qro?@%Rt5%MEP%Q zScWjAA(gDJ2i4ebDuqxR4ja*eu52-HIeD`EhmU=}zdlttd_<>cM^JHhqSNyBp{Zo! zR+Bej2X6e^$otd4{+R`-XRxD)`Yet{eb7&AXW3!;RD%*3lvRO(B!9S7T6E0kFf;kE z?oA(|bnelh=!Db3xb~fi8C5=jO>&k-n4+4`u4eUVDK$s<TSHaHbDK|bRU@wUy;H{X zr3Mx<UVl!{8XJ%86ScF<DA6n^MUC${CLV|p=1lj2K^_2aV6^57Pu0`cYr~x5nJ;lE ze_qp5>{zeMZZmg<KLC<`P2RmrGcYoloSF>uztD4sj^WS60MMsn(rSZ~)nO(IILej9 zEkl+UMf}*;w$NpTNZ?O$Sk1TsZUraXt1P(>C!ht{xtiFkRp7=tv9J+%W(<SDe2o)J zo1^#MS!88pVa4Uh$bwBL9t}K?wUpfgsuNB;yR9@jWOyLH<sx%f`6&1l_ojulLqXkI z2SjFWsFL3L?uYai87emBsdT^(dZ;jhuCiR99dPBu=^k!*jI!i3VWu*qh3OwZI9To= zW)6gJLCiIs&7Bu2xEw_CKh1lXTA78);^~`RHss*}Yg%nwX}rIWAb&;arVz1>06uZ> zDWlKX2wfdq&MGKkaIVNrC<NR&Fv|2rW$=Zaom}fhKf1NOrs$x9&fCy1;ED){HOy&r zB;VA3=6bt4HgnK|ix%wIN}T4bGze;By)_}mKutecWD(@D$iyCC9Tyc!<~H@7WP4_K zu(mM#0tlXgLZ{W9;{F_$GLRfo!B@iinnT@y@LmVMLc$lKWPOIk#uOr%_%GgbJy)t5 zc`$09RB-WCG#R!wSLo4FvqB&5OffK%W0GGu#KlYgMEr^UZyO7Ad$ZP4jghZkql~4c zb~|!AT7~R^x~|Ic?wg_z<S&M+K>*g`1ei7f|5xhZe<IKooXsAqu!X8D35O|?Mj%(A zg7e<802dq3ha^`Pe-b3w_}f+Jm;3`z@1v9f3shuf(Yx~g!25|ngTfujb&LIfeWYMQ zT$u<CBUN5fZENn#6m>~oMp?i(=`wQ@3yv}LyT-@EySa%)27SQ8!z21z%kc5^{%}%( TGfe`4SMVUJP?Z{G>&X8AdFp#6 literal 0 HcmV?d00001 diff --git a/docs/redistribution-inventory.json b/docs/redistribution-inventory.json index 8e16538..2ae396d 100644 --- a/docs/redistribution-inventory.json +++ b/docs/redistribution-inventory.json @@ -210,6 +210,24 @@ "attribution": "opencode-artifacts contributors", "status": "approved" }, + { + "path": "docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.png", + "sha256": "84ebacab7a6258b611aa73973ad9ca9f1da29eb88b8f4498b4d5e64111f78e84", + "provenance": "repository-generated browser capture", + "source": "examples/patterns/design-tokens.md rendered offline in Chromium 151 at desktop width", + "license": "MIT", + "attribution": "opencode-artifacts contributors", + "status": "approved" + }, + { + "path": "docs/evidence/renderer/goal-3-design-tokens-mobile-2026-08-17.png", + "sha256": "6f7d64d0183cbd7632dea94f73aedf3cd0a1263b9fc16bb848df3cfd90badaad", + "provenance": "repository-generated browser capture", + "source": "examples/patterns/design-tokens.md rendered offline in Chromium 151 at narrow width", + "license": "MIT", + "attribution": "opencode-artifacts contributors", + "status": "approved" + }, { "path": "docs/evidence/renderer/goal-3-portable-assets-chromium-2026-08-17.png", "sha256": "de64548e9c0e40b594f31b3bc84cb09d261ecc73c3dd5b756ecd5ac74a7a529d", diff --git a/docs/security.md b/docs/security.md index c411283..ba33e65 100644 --- a/docs/security.md +++ b/docs/security.md @@ -41,3 +41,10 @@ For a suspected credential or release compromise: Never paste tokens, private advisory content, raw participant data, or private artifacts into public issues, diagnostics, fixtures, or release evidence. Secret scanning reduces accidental exposure but is not exhaustive; final audience-bound bytes and staged metadata require review. + +Markdown design configuration is data-only. The renderer reads at most the documented 8 KiB +project token file without following its file or parent-directory symlinks, accepts one +versioned prompt fence, validates fixed names/types and contrast atomically, and emits only +fixed CSS-variable slots. It never accepts selectors, declarations, URLs, markup, imports, +expressions, arbitrary font stacks, or raw CSS; trusted HTML remains a separately disclosed +permission mode. diff --git a/examples/patterns/design-tokens.md b/examples/patterns/design-tokens.md new file mode 100644 index 0000000..356c6a3 --- /dev/null +++ b/examples/patterns/design-tokens.md @@ -0,0 +1,49 @@ +--- +title: Signal Review +icon: ◈ +description: A bounded project visual system applied without raw CSS. +theme: report +--- +```design-tokens +{"schemaVersion":1,"tokens":{"pageBackground":"#f5f1ff","surface":"#ffffff","text":"#211735","mutedText":"#5e5074","border":"#d9d0e8","accent":"#6d28d9","font":"serif","spacing":"spacious","radius":"soft","density":"airy"}} +``` + +# Signal Review + +The explicit document tokens outrank the report theme while retaining a fixed, offline +renderer and validated contrast. + +```stats +[ + {"label":"Qualified signals","value":"148","delta":"+19%","direction":"up","tone":"good","emphasis":true}, + {"label":"Needs review","value":"23","delta":"-8%","direction":"down","tone":"warn"}, + {"label":"Confidence","value":"92%","delta":"high","tone":"neutral"} +] +``` + +## Readout + +```callout +{"tone":"info","title":"The system stays declarative","body":"Colors, type, spacing, radius, and density flow through allowlisted slots. Selectors, URLs, markup, imports, and executable expressions never enter the page CSS."} +``` + +## Provenance + +```table +{ + "caption":"Effective design decisions", + "columns":[ + {"key":"layer","label":"Layer"}, + {"key":"decision","label":"Decision"}, + {"key":"source","label":"Winning source"} + ], + "rows":[ + {"layer":"Color","decision":"Violet accent on lavender canvas","source":"Prompt fence"}, + {"layer":"Spacing","decision":"Spacious rhythm","source":"Prompt fence"}, + {"layer":"Structure","decision":"Fixed responsive renderer","source":"Built-in"} + ] +} +``` + +> [!NOTE] +> The generated page records token provenance in metadata and makes no view-time request. diff --git a/scripts/portable-browser-evidence.ts b/scripts/portable-browser-evidence.ts index bdd7b88..4db7847 100644 --- a/scripts/portable-browser-evidence.ts +++ b/scripts/portable-browser-evidence.ts @@ -8,9 +8,12 @@ const endpoint = process.argv[2] ?? "http://127.0.0.1:4444"; const pageUrl = process.argv[3]; const screenshotPath = process.argv[4]; const reportPath = process.argv[5]; +const viewportWidth = Number(process.argv[6] ?? 1440); +const viewportHeight = Number(process.argv[7] ?? 1600); if (!pageUrl || !screenshotPath || !reportPath) { - throw new Error("usage: portable-browser-evidence <webdriver-url> <page-url> <screenshot-path> <report-path>"); + throw new Error("usage: portable-browser-evidence <webdriver-url> <page-url> <screenshot-path> <report-path> [width] [height]"); } +if (!Number.isInteger(viewportWidth) || !Number.isInteger(viewportHeight) || viewportWidth < 320 || viewportHeight < 480) throw new Error("viewport is invalid"); async function request<T>(method: string, path: string, body?: unknown): Promise<T> { const response = await fetch(`${endpoint}${path}`, { @@ -38,7 +41,7 @@ const sessionId = session.sessionId; const route = `/session/${sessionId}`; try { - await request("POST", `${route}/window/rect`, { width: 1440, height: 1600, x: 0, y: 0 }); + await request("POST", `${route}/window/rect`, { width: viewportWidth, height: viewportHeight, x: 0, y: 0 }); await request("POST", `${route}/goog/cdp/execute`, { cmd: "Network.enable", params: {}, @@ -99,6 +102,18 @@ try { fontSetStatus: document.fonts.status, projectFontFaces: Array.from(document.fonts).filter((face) => face.family.includes('Artifact Project')).map((face) => ({ family: face.family, status: face.status })), bodyFontFamily: getComputedStyle(document.body).fontFamily, + viewport: { width: innerWidth, height: innerHeight }, + horizontalOverflow: document.documentElement.scrollWidth > document.documentElement.clientWidth, + designTokens: document.documentElement.hasAttribute('data-design-tokens'), + pageTheme: document.documentElement.getAttribute('data-page-theme'), + designProvenance: document.querySelector('meta[name="artifact-design-provenance"]')?.getAttribute('content'), + computedDesign: { + pageBackground: getComputedStyle(document.documentElement).getPropertyValue('--page-bg').trim(), + surface: getComputedStyle(document.documentElement).getPropertyValue('--card-bg').trim(), + text: getComputedStyle(document.documentElement).getPropertyValue('--ink').trim(), + accent: getComputedStyle(document.documentElement).getPropertyValue('--accent').trim(), + radius: getComputedStyle(document.documentElement).getPropertyValue('--radius').trim(), + }, pageBytes: new TextEncoder().encode(document.documentElement.outerHTML).length, }; `, diff --git a/skills/artifact-pages/SKILL.md b/skills/artifact-pages/SKILL.md index 38b8e53..d2f33ac 100644 --- a/skills/artifact-pages/SKILL.md +++ b/skills/artifact-pages/SKILL.md @@ -47,6 +47,11 @@ Decide the treatment before writing — the theme is part of the read, not an af Set it in frontmatter (`theme: report`). When nothing fits, `default` is always respectable — an over-styled page is worse than a plain one. +If the project contains `.opencode/artifact-tokens.json`, its bounded version-1 visual tokens +automatically outrank the theme. Use one `design-tokens` JSON fence only when the request needs +an explicit per-page override; it outranks the project file. Never write CSS, selectors, URLs, +or font names into tokens—use only the documented values in `reference/components.md`. + ## Pre-publish checklist 1. The title names the page like a product (2–4 words, no appended explainer) diff --git a/skills/artifact-pages/reference/components.md b/skills/artifact-pages/reference/components.md index 2ca6cba..49842c1 100644 --- a/skills/artifact-pages/reference/components.md +++ b/skills/artifact-pages/reference/components.md @@ -60,3 +60,19 @@ for inspection. Frontmatter `theme:` selects a curated variant: `default` (gray-blue canvas, white cards), `report` (warm paper, serif headings), `ops` (dark-first terminal), `editorial` (magazine display type). Anything else falls back to `default`. + +## Bounded design tokens (optional) + +Project defaults live only at `.opencode/artifact-tokens.json`; a page-level prompt override +uses one `design-tokens` fence. Both use the same atomic versioned form: + +```json +{"schemaVersion":1,"tokens":{"accent":"#6d28d9","font":"serif","spacing":"spacious","radius":"soft","density":"airy"}} +``` + +Allowed token names are `pageBackground`, `surface`, `text`, `mutedText`, `border`, `accent`, +`font`, `spacing`, `radius`, and `density`. Colors are six-digit hex; font is +`system|serif|mono`; spacing is `compact|comfortable|spacious`; radius is +`square|sharp|soft|round`; density is `compact|comfortable|airy`. Prompt > project > curated theme > +built-in defaults. Unknown/unsafe/low-contrast values reject the whole source before publish; +raw CSS, selectors, URLs, markup, imports, expressions, and remote fonts are never accepted. diff --git a/specs/changes/renderer-design-tokens/change.json b/specs/archive/2026-08-17-renderer-design-tokens/change.json similarity index 58% rename from specs/changes/renderer-design-tokens/change.json rename to specs/archive/2026-08-17-renderer-design-tokens/change.json index 4f1c414..fea8730 100644 --- a/specs/changes/renderer-design-tokens/change.json +++ b/specs/archive/2026-08-17-renderer-design-tokens/change.json @@ -3,10 +3,15 @@ "id": "renderer-design-tokens", "title": "Add bounded renderer design tokens", "lane": "standard", - "status": "approved", - "affectedRequirements": ["RENDER-07", "SEC-04"], - "currentSpecs": ["specs/current/portable-renderer.spec.md"], - "currentSpecsUpdated": false, + "status": "archived", + "affectedRequirements": [ + "RENDER-07", + "SEC-04" + ], + "currentSpecs": [ + "specs/current/portable-renderer.spec.md" + ], + "currentSpecsUpdated": true, "approval": { "by": "aaron.zeng", "at": "2026-08-17T15:02:07Z" @@ -17,5 +22,5 @@ "reason": "" }, "createdAt": "2026-08-17", - "archivedAt": null + "archivedAt": "2026-08-17" } diff --git a/specs/changes/renderer-design-tokens/delta.md b/specs/archive/2026-08-17-renderer-design-tokens/delta.md similarity index 100% rename from specs/changes/renderer-design-tokens/delta.md rename to specs/archive/2026-08-17-renderer-design-tokens/delta.md diff --git a/specs/archive/2026-08-17-renderer-design-tokens/evidence.md b/specs/archive/2026-08-17-renderer-design-tokens/evidence.md new file mode 100644 index 0000000..88c3c0d --- /dev/null +++ b/specs/archive/2026-08-17-renderer-design-tokens/evidence.md @@ -0,0 +1,22 @@ +# Evidence: Add bounded renderer design tokens + +## Requirement: RENDER-07 +- Validation: project/prompt visual choices need safe deterministic precedence. +- Verification: shared resolver tests cover prompt > project > theme > default precedence, + per-token provenance, atomic fallback, contrast, property-order-independent CSS, project + discovery boundaries, and real plugin/CLI output. The checked-in fixture was rendered + offline in Chromium 151 at desktop and narrow widths. +- Result: passed. Both real surfaces computed the exact authored palette/type/radius, retained + prompt provenance and the unchanged CSP, had no horizontal overflow or console entries, and + attempted zero HTTP(S) requests. +- Evidence: [@test](test/design-tokens.test.ts) [@manual](docs/evidence/renderer/goal-3-design-tokens-2026-08-17.md) + +## Requirement: SEC-04 +- Validation: token configuration must not become raw CSS or an executable Markdown escape hatch. +- Verification: hostile color/font/spacing/radius/density values, unknown keys, low contrast, + exact 8 KiB/overflow, file and parent symlinks, duplicate declarations, and invalid-project + plugin no-permission/no-write behavior are asserted. Full Node 24 gates passed: 202/202 + tests, TypeScript build, all 35 structural checks, package dry run, and diff whitespace. +- Result: passed. Invalid sources never apply partially or enter generated CSS; only fixed + variables with parser-owned serializations are emitted, and trusted HTML remains separate. +- Evidence: [@test](test/design-tokens.test.ts) [@test](test/governance-policy.test.ts) [@manual](docs/security.md) diff --git a/specs/changes/renderer-design-tokens/proposal.md b/specs/archive/2026-08-17-renderer-design-tokens/proposal.md similarity index 100% rename from specs/changes/renderer-design-tokens/proposal.md rename to specs/archive/2026-08-17-renderer-design-tokens/proposal.md diff --git a/specs/archive/2026-08-17-renderer-design-tokens/tasks.md b/specs/archive/2026-08-17-renderer-design-tokens/tasks.md new file mode 100644 index 0000000..9c2bb1b --- /dev/null +++ b/specs/archive/2026-08-17-renderer-design-tokens/tasks.md @@ -0,0 +1,8 @@ +# Tasks: Add bounded renderer design tokens + +- [x] Confirm proposal validation and human approval. +- [x] Implement the versioned allowlisted token schema, bounded project discovery, and precedence/provenance resolver. +- [x] Emit fixed CSS-variable slots with contrast and injection validation and unchanged CSP. +- [x] Add precedence, fallback, hostile-value, deterministic-byte, contrast, and real-surface tests. +- [x] Retain screenshots and update `specs/current/portable-renderer.spec.md`. +- [x] Run repository validation and archive the packet. diff --git a/specs/changes/renderer-design-tokens/evidence.md b/specs/changes/renderer-design-tokens/evidence.md deleted file mode 100644 index a6953fd..0000000 --- a/specs/changes/renderer-design-tokens/evidence.md +++ /dev/null @@ -1,13 +0,0 @@ -# Evidence: Add bounded renderer design tokens - -## Requirement: RENDER-07 -- Validation: project/prompt visual choices need safe deterministic precedence. -- Verification: planned schema, provenance, precedence, fallback, contrast, and screenshot tests. -- Result: pending implementation and visual evidence. -- Evidence: [@manual](docs/roadmap.md) - -## Requirement: SEC-04 -- Validation: token configuration must not become raw CSS or an executable Markdown escape hatch. -- Verification: planned CSS/script/URL breakout corpus and unchanged-CSP assertions. -- Result: pending implementation. -- Evidence: [@manual](docs/security.md) diff --git a/specs/changes/renderer-design-tokens/tasks.md b/specs/changes/renderer-design-tokens/tasks.md deleted file mode 100644 index 4a05c6c..0000000 --- a/specs/changes/renderer-design-tokens/tasks.md +++ /dev/null @@ -1,8 +0,0 @@ -# Tasks: Add bounded renderer design tokens - -- [ ] Confirm proposal validation and human approval. -- [ ] Implement the versioned allowlisted token schema, bounded project discovery, and precedence/provenance resolver. -- [ ] Emit fixed CSS-variable slots with contrast and injection validation and unchanged CSP. -- [ ] Add precedence, fallback, hostile-value, deterministic-byte, contrast, and real-surface tests. -- [ ] Retain screenshots and update `specs/current/portable-renderer.spec.md`. -- [ ] Run repository validation and archive the packet. diff --git a/specs/current/portable-renderer.spec.md b/specs/current/portable-renderer.spec.md index 938fd0a..6967bc9 100644 --- a/specs/current/portable-renderer.spec.md +++ b/specs/current/portable-renderer.spec.md @@ -26,6 +26,17 @@ - `RENDER-05`: Markdown source, declaration count, each source asset, aggregate decoded asset bytes, encoded contributions, rendered HTML, and footer-expanded publication bytes are bounded. The final default limit remains 15 MiB and is enforced before lifecycle commit. +- `RENDER-07`: one version-1 `design-tokens` fence supplies prompt-level values and the fixed + `.opencode/artifact-tokens.json` file supplies project values. The deterministic precedence + is prompt > project > curated theme > built-in defaults; each effective token records named + provenance in portable-page metadata. Invalid higher sources are rejected atomically before + permission or writes, while standalone rendering shows an escaped fallback. +- `SEC-04`: token sources are capped at 8 KiB, project discovery refuses worktree/file/parent + symlinks and non-regular files, and values are restricted to six-digit colors plus fixed + font, spacing, radius, and density enums. Effective text/accent pairs are contrast checked; + deterministic output populates only fixed CSS variables and leaves the CSP unchanged. Raw + CSS, selectors, declarations, URLs, markup, imports, expressions, and arbitrary font stacks + are not an authoring surface. - `SEC-02`: resolution is independent of process cwd after the caller supplies its worktree root. It checks every path segment, opens regular files without following the final symlink, compares descriptor identity across a bounded read, repeats realpath containment, detects @@ -39,6 +50,7 @@ | Declared assets | 64 | | One decoded asset | 4 MiB | | All decoded assets | 10 MiB | +| One project or prompt design-token source | 8 KiB | | Final footer-expanded page | 15 MiB | Exact limits are accepted; the next byte or declaration is refused. SVG accepts only a @@ -53,9 +65,16 @@ malformed or unknown markup. - `test/preflight.test.ts` covers ordered multi-error reports, redaction, count/byte ceilings, repeated asset locations, every component kind, chart fallback parity, valid examples, CLI/plugin no-write refusal, visible warnings, and trusted-mode disclosure. +- `test/design-tokens.test.ts` covers schema aggregation, precedence, provenance, atomic + fallback, contrast, hostile values, project file/symlink/byte boundaries, deterministic + serialization, standalone fallback, and real CLI/plugin no-write/application paths. +- `examples/patterns/design-tokens.md` plus + `docs/evidence/renderer/goal-3-design-tokens-2026-08-17.md` retain the offline Chromium 151 + desktop/mobile computed-style, provenance, CSP, console, request, overflow, and screenshot + observations. - `examples/patterns/portable-mixed.md` plus `docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md` retain the real Chromium offline mixed-page and loaded-font observations. - This evidence is one Linux/Chromium observation. It does not certify a supported browser, OS, mobile, or assistive-technology cell, and it does not complete the remaining Goal 3 - declarative, token, accessibility, or performance packets. + accessibility or performance packets. diff --git a/src/cli.ts b/src/cli.ts index 99224f1..bf00b20 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,6 +3,7 @@ import { lstat, mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { renderArtifact, renderRawHtml } from "./render.ts"; import type { PortableAssets } from "./assets.ts"; +import type { ResolvedDesignTokens } from "./design-tokens.ts"; import { formatPreflight, preflightDocument, trustedHtmlDiagnostic } from "./preflight.ts"; import { FilePublisher, slugify } from "./publisher.ts"; import { GitHubPagesPublisher } from "./github-pages.ts"; @@ -109,6 +110,7 @@ async function renderCommand(args: string[]): Promise<void> { process.exit(1); } let assets: PortableAssets | undefined; + let designTokens: ResolvedDesignTokens | undefined; if (format === "html") { console.error(`warning: ${trustedHtmlDiagnostic().message}`); } else { @@ -120,9 +122,10 @@ async function renderCommand(args: string[]): Promise<void> { } for (const warning of preflight.diagnostics) console.error(`warning: ${warning.code} at ${warning.line}:${warning.column}: ${warning.message}`); assets = preflight.assets; + designTokens = preflight.designTokens; } const rendered = - format === "html" ? renderRawHtml(markdown, title ? { title } : {}) : renderArtifact(markdown, { assets }); + format === "html" ? renderRawHtml(markdown, title ? { title } : {}) : renderArtifact(markdown, { assets, designTokens }); const finalTitle = title ?? rendered.meta.title ?? "Artifact"; const finalFindings = scanSensitive(rendered.html); if (finalFindings.length > 0 && !force) { diff --git a/src/design-tokens.ts b/src/design-tokens.ts new file mode 100644 index 0000000..529177a --- /dev/null +++ b/src/design-tokens.ts @@ -0,0 +1,352 @@ +import { constants } from "node:fs"; +import { lstat, open } from "node:fs/promises"; +import { join } from "node:path"; + +export const DESIGN_TOKEN_FILE = join(".opencode", "artifact-tokens.json"); +export const MAX_DESIGN_TOKEN_BYTES = 8 * 1024; + +const TOKEN_NAMES = [ + "pageBackground", + "surface", + "text", + "mutedText", + "border", + "accent", + "font", + "spacing", + "radius", + "density", +] as const; +const COLOR_NAMES = ["pageBackground", "surface", "text", "mutedText", "border", "accent"] as const; + +export type DesignTokenName = typeof TOKEN_NAMES[number]; +export type DesignTokenSource = "default" | "theme" | "project" | "prompt"; +export type DesignTokenValues = Record<DesignTokenName, string>; + +export interface DesignTokenIssue { + code: string; + reason: string; + nextAction: string; + source: "project" | "prompt"; + promptIndex?: number; +} + +export interface ResolvedDesignTokens { + css: string; + values: DesignTokenValues; + provenance: Record<DesignTokenName, DesignTokenSource>; + active: boolean; + fixesColorMode: boolean; +} + +export interface DesignTokenResolution { + designTokens: ResolvedDesignTokens; + issues: DesignTokenIssue[]; +} + +export interface ProjectDesignTokenSource { + value?: unknown; + issue?: DesignTokenIssue; +} + +const BASE: DesignTokenValues = { + pageBackground: "#e9edf2", + surface: "#ffffff", + text: "#111827", + mutedText: "#4b5563", + border: "#e5e7eb", + accent: "#6d6bd6", + font: "system", + spacing: "comfortable", + radius: "round", + density: "comfortable", +}; + +const THEME_VALUES: Record<string, Partial<DesignTokenValues>> = { + report: { + pageBackground: "#f6f0e4", + surface: "#fffdf7", + text: "#2b251a", + mutedText: "#6b5f49", + border: "#e3d9c4", + accent: "#b4541e", + }, + ops: { + pageBackground: "#0f140f", + surface: "#171f17", + text: "#d5e5cf", + mutedText: "#8fa389", + border: "#263026", + accent: "#4ade80", + }, + editorial: { + pageBackground: "#fafafa", + surface: "#ffffff", + text: "#141414", + mutedText: "#525252", + border: "#e5e5e5", + accent: "#141414", + radius: "sharp", + }, +}; + +const FONT_STACKS: Record<string, string> = { + system: `system-ui,-apple-system,"Segoe UI",sans-serif`, + serif: `Georgia,Charter,"Times New Roman",serif`, + mono: `ui-monospace,SFMono-Regular,Menlo,monospace`, +}; +const SPACING: Record<string, [string, string, string]> = { + compact: ["1rem", "2rem", ".9rem"], + comfortable: ["1.5rem", "3rem", "1.25rem"], + spacious: ["2rem", "4rem", "1.75rem"], +}; +const RADII: Record<string, string> = { square: "0", sharp: "4px", soft: "8px", round: "16px" }; +const DENSITY: Record<string, [string, string, string]> = { + compact: ["1rem", "1.15rem", ".82rem"], + comfortable: ["1.5rem", "1.75rem", ".86rem"], + airy: ["2rem", "2.25rem", ".92rem"], +}; + +function isRecord(value: unknown): value is Record<string, unknown> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function issue(source: "project" | "prompt", code: string, reason: string, nextAction: string, promptIndex?: number): DesignTokenIssue { + return { code, reason, nextAction, source, ...(promptIndex === undefined ? {} : { promptIndex }) }; +} + +function parsePacket(value: unknown, source: "project" | "prompt", promptIndex?: number): { tokens?: Partial<DesignTokenValues>; issues: DesignTokenIssue[] } { + if (!isRecord(value)) return { issues: [issue(source, "design-shape", "design token source must be an object", "use the documented schemaVersion and tokens object", promptIndex)] }; + const issues: DesignTokenIssue[] = []; + for (const key of Object.keys(value)) { + if (key !== "schemaVersion" && key !== "tokens") issues.push(issue(source, "design-root-key", `unknown design root key '${key}'`, "keep only schemaVersion and tokens", promptIndex)); + } + if (value["schemaVersion"] !== 1 || !isRecord(value["tokens"])) { + issues.push(issue(source, "design-schema", "design token schema is not version 1", "use {\"schemaVersion\":1,\"tokens\":{...}}", promptIndex)); + return { issues }; + } + const tokenObject = value["tokens"]; + for (const key of Object.keys(tokenObject)) { + if (!(TOKEN_NAMES as readonly string[]).includes(key)) issues.push(issue(source, "design-token-unknown", `unknown design token '${key}'`, "remove it or use a documented token name", promptIndex)); + } + const tokens: Partial<DesignTokenValues> = {}; + for (const name of TOKEN_NAMES) { + const tokenValue = tokenObject[name]; + if (tokenValue === undefined) continue; + if (typeof tokenValue !== "string") { + issues.push(issue(source, "design-token-type", `design token '${name}' must be a string`, "use a documented string value", promptIndex)); + continue; + } + if ((COLOR_NAMES as readonly string[]).includes(name)) { + if (!/^#[0-9a-fA-F]{6}$/.test(tokenValue)) { + issues.push(issue(source, "design-color", `design token '${name}' is not a six-digit hex color`, "use a value such as #1f2937", promptIndex)); + continue; + } + tokens[name] = tokenValue.toLowerCase(); + } else if (name === "font") { + if (!(tokenValue in FONT_STACKS)) { + issues.push(issue(source, "design-font", "font is not allowlisted", "use system, serif, or mono", promptIndex)); + continue; + } + tokens[name] = tokenValue; + } else if (name === "spacing") { + if (!(tokenValue in SPACING)) { + issues.push(issue(source, "design-spacing", "spacing is not allowlisted", "use compact, comfortable, or spacious", promptIndex)); + continue; + } + tokens[name] = tokenValue; + } else if (name === "radius") { + if (!(tokenValue in RADII)) { + issues.push(issue(source, "design-radius", "radius is not allowlisted", "use square, sharp, soft, or round", promptIndex)); + continue; + } + tokens[name] = tokenValue; + } else { + if (!(tokenValue in DENSITY)) { + issues.push(issue(source, "design-density", "density is not allowlisted", "use compact, comfortable, or airy", promptIndex)); + continue; + } + tokens[name] = tokenValue; + } + } + return { tokens, issues }; +} + +function channel(value: number): number { + const normalized = value / 255; + return normalized <= 0.04045 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4; +} + +function luminance(color: string): number { + return 0.2126 * channel(Number.parseInt(color.slice(1, 3), 16)) + + 0.7152 * channel(Number.parseInt(color.slice(3, 5), 16)) + + 0.0722 * channel(Number.parseInt(color.slice(5, 7), 16)); +} + +export function contrastRatio(first: string, second: string): number { + const [lighter, darker] = [luminance(first), luminance(second)].sort((a, b) => b - a); + return (lighter + 0.05) / (darker + 0.05); +} + +function contrastIssue(values: DesignTokenValues, source: "project" | "prompt", promptIndex?: number): DesignTokenIssue | undefined { + const pairs: Array<[string, string, number, string]> = [ + [values.text, values.pageBackground, 4.5, "text/pageBackground"], + [values.text, values.surface, 4.5, "text/surface"], + [values.mutedText, values.pageBackground, 4.5, "mutedText/pageBackground"], + [values.mutedText, values.surface, 4.5, "mutedText/surface"], + [values.accent, values.pageBackground, 3, "accent/pageBackground"], + [values.accent, values.surface, 3, "accent/surface"], + ]; + const failed = pairs.find(([foreground, background, minimum]) => contrastRatio(foreground, background) < minimum); + return failed === undefined + ? undefined + : issue(source, "design-contrast", `design color pair ${failed[3]} does not meet its contrast floor`, "choose colors meeting WCAG 2.2 AA contrast", promptIndex); +} + +function cssFor(values: DesignTokenValues, provenance: Record<DesignTokenName, DesignTokenSource>, fixesColorMode: boolean): string { + const [bodyPad, bodyPadBottom, sectionGap] = SPACING[values.spacing]; + const [sectionPadY, sectionPadX, tableSize] = DENSITY[values.density]; + const font = FONT_STACKS[values.font]; + const customized = (name: DesignTokenName): boolean => provenance[name] === "project" || provenance[name] === "prompt"; + const declarations: string[] = []; + if (fixesColorMode) { + declarations.push(`--page-bg:${values.pageBackground}`, `--card-bg:${values.surface}`, `--ink:${values.text}`, `--ink-2:${values.mutedText}`, `--line:${values.border}`, `--accent:${values.accent}`); + } + if (customized("font")) declarations.push(`--artifact-font:${font}`, `--artifact-heading-font:${font}`); + if (customized("spacing")) declarations.push(`--body-pad:${bodyPad}`, `--body-pad-bottom:${bodyPadBottom}`, `--section-gap:${sectionGap}`); + if (customized("density")) declarations.push(`--section-pad-y:${sectionPadY}`, `--section-pad-x:${sectionPadX}`, `--table-font-size:${tableSize}`); + if (customized("radius")) declarations.push(`--radius:${RADII[values.radius]}`); + return `:root[data-design-tokens]{${declarations.join(";")}}`; +} + +function parsedJson(source: string, index: number): { value?: unknown; issue?: DesignTokenIssue } { + if (Buffer.byteLength(source, "utf8") > MAX_DESIGN_TOKEN_BYTES) { + return { issue: issue("prompt", "design-prompt-too-large", "prompt design tokens exceed the 8192-byte limit", "reduce the design token declaration", index) }; + } + try { + return { value: JSON.parse(source) as unknown }; + } catch { + return { issue: issue("prompt", "design-json", "prompt design tokens are not valid JSON", "provide the version 1 JSON object", index) }; + } +} + +export function resolveDesignTokens(theme: string | undefined, project: ProjectDesignTokenSource | undefined, promptSources: readonly string[]): DesignTokenResolution { + const namedTheme = theme !== undefined && THEME_VALUES[theme] !== undefined; + let values: DesignTokenValues = { ...BASE, ...(namedTheme ? THEME_VALUES[theme] : {}) }; + const baseline: DesignTokenSource = namedTheme ? "theme" : "default"; + const provenance = Object.fromEntries(TOKEN_NAMES.map((name) => [name, baseline])) as Record<DesignTokenName, DesignTokenSource>; + const issues: DesignTokenIssue[] = []; + let active = false; + let fixesColorMode = false; + + const apply = (tokens: Partial<DesignTokenValues>, source: "project" | "prompt", promptIndex?: number): void => { + const candidate = { ...values, ...tokens }; + const invalidContrast = contrastIssue(candidate, source, promptIndex); + if (invalidContrast) { + issues.push(invalidContrast); + return; + } + values = candidate; + for (const name of TOKEN_NAMES) { + if (tokens[name] !== undefined) provenance[name] = source; + } + active ||= Object.keys(tokens).length > 0; + fixesColorMode ||= COLOR_NAMES.some((name) => tokens[name] !== undefined); + }; + + if (project?.issue) issues.push(project.issue); + else if (project?.value !== undefined) { + const parsed = parsePacket(project.value, "project"); + if (parsed.issues.length > 0) issues.push(...parsed.issues); + else apply(parsed.tokens ?? {}, "project"); + } + + if (promptSources.length > 1) { + issues.push(issue("prompt", "design-prompt-duplicate", "only one design-tokens fence is allowed", "merge prompt overrides into one version 1 declaration", 1)); + for (const [index, source] of promptSources.entries()) { + const decoded = parsedJson(source, index); + if (decoded.issue) { + issues.push(decoded.issue); + continue; + } + const parsed = parsePacket(decoded.value, "prompt", index); + if (parsed.issues.length > 0) { + issues.push(...parsed.issues); + continue; + } + const invalidContrast = contrastIssue({ ...values, ...(parsed.tokens ?? {}) }, "prompt", index); + if (invalidContrast) issues.push(invalidContrast); + } + } else if (promptSources.length === 1) { + const decoded = parsedJson(promptSources[0], 0); + if (decoded.issue) issues.push(decoded.issue); + else { + const parsed = parsePacket(decoded.value, "prompt", 0); + if (parsed.issues.length > 0) issues.push(...parsed.issues); + else apply(parsed.tokens ?? {}, "prompt", 0); + } + } + + return { + designTokens: { css: active ? cssFor(values, provenance, fixesColorMode) : "", values, provenance, active, fixesColorMode }, + issues, + }; +} + +function projectIssue(code: string, reason: string, nextAction: string): ProjectDesignTokenSource { + return { issue: issue("project", code, reason, nextAction) }; +} + +export async function loadProjectDesignTokens(worktreeRoot: string): Promise<ProjectDesignTokenSource | undefined> { + let rootInfo; + try { + rootInfo = await lstat(worktreeRoot); + } catch { + return projectIssue("design-project-root", "design token worktree root is unavailable", "select an existing worktree directory"); + } + if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) return projectIssue("design-project-root", "design token worktree root is unsafe", "select a real worktree directory"); + const directory = join(worktreeRoot, ".opencode"); + try { + const directoryInfo = await lstat(directory); + if (!directoryInfo.isDirectory() || directoryInfo.isSymbolicLink()) return projectIssue("design-project-path", "the .opencode design token directory is unsafe", "use a real project directory without symlinks"); + } catch (error) { + if (isRecord(error) && error["code"] === "ENOENT") return undefined; + return projectIssue("design-project-path", "the .opencode design token directory cannot be inspected", "repair the project directory"); + } + const path = join(worktreeRoot, DESIGN_TOKEN_FILE); + try { + const info = await lstat(path); + if (info.isSymbolicLink() || !info.isFile()) return projectIssue("design-project-file", "project design tokens are not a regular file", `replace ${DESIGN_TOKEN_FILE} with a regular file`); + } catch (error) { + if (isRecord(error) && error["code"] === "ENOENT") return undefined; + return projectIssue("design-project-file", "project design tokens cannot be inspected", `repair ${DESIGN_TOKEN_FILE}`); + } + let handle; + try { + handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW); + const before = await handle.stat(); + if (!before.isFile()) return projectIssue("design-project-file", "project design tokens are not a regular file", `replace ${DESIGN_TOKEN_FILE} with a regular file`); + if (before.size > MAX_DESIGN_TOKEN_BYTES) return projectIssue("design-project-too-large", "project design tokens exceed the 8192-byte limit", "reduce the file to the documented schema"); + const buffer = Buffer.alloc(MAX_DESIGN_TOKEN_BYTES + 1); + let offset = 0; + while (offset < buffer.length) { + const read = await handle.read(buffer, offset, buffer.length - offset, offset); + if (read.bytesRead === 0) break; + offset += read.bytesRead; + } + const after = await handle.stat(); + if (offset > MAX_DESIGN_TOKEN_BYTES) return projectIssue("design-project-too-large", "project design tokens exceed the 8192-byte limit", "reduce the file to the documented schema"); + if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs) { + return projectIssue("design-project-changed", "project design tokens changed while being read", "retry after project writers finish"); + } + try { + return { value: JSON.parse(buffer.subarray(0, offset).toString("utf8")) as unknown }; + } catch { + return projectIssue("design-json", "project design tokens are not valid JSON", "provide the version 1 JSON object"); + } + } catch { + return projectIssue("design-project-file", "project design tokens cannot be opened safely", `repair ${DESIGN_TOKEN_FILE}`); + } finally { + await handle?.close(); + } +} diff --git a/src/markdown.ts b/src/markdown.ts index 9475dfa..653a64e 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -24,11 +24,17 @@ export interface ComponentBlock { json: string; } +export interface DesignTokenBlock { + json: string; + line: number; +} + export interface ParsedDocument { meta: Frontmatter; bodyHtml: string; charts: ChartSpec[]; components: ComponentBlock[]; + designTokens: DesignTokenBlock[]; warnings: string[]; } @@ -39,9 +45,9 @@ export interface ParseDocumentOptions { const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/; const KEY_VALUE_RE = /^([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/; -function parseFrontmatter(source: string, warnings: string[]): { meta: Frontmatter; body: string } { +function parseFrontmatter(source: string, warnings: string[]): { meta: Frontmatter; body: string; lineOffset: number } { const match = source.match(FRONTMATTER_RE); - if (!match) return { meta: {}, body: source }; + if (!match) return { meta: {}, body: source, lineOffset: 0 }; const meta: Frontmatter = {}; for (const line of match[1].split(/\r?\n/)) { if (!line.trim()) continue; @@ -60,14 +66,15 @@ function parseFrontmatter(source: string, warnings: string[]): { meta: Frontmatt else if (key === "font") meta.font = value; else warnings.push(`frontmatter key ignored: ${key}`); } - return { meta, body: source.slice(match[0].length) }; + return { meta, body: source.slice(match[0].length), lineOffset: (match[0].match(/\n/g) ?? []).length }; } export function parseDocument(source: string, options: ParseDocumentOptions = {}): ParsedDocument { const warnings: string[] = []; - const { meta, body } = parseFrontmatter(source, warnings); + const { meta, body, lineOffset } = parseFrontmatter(source, warnings); const charts: ChartSpec[] = []; const components: ComponentBlock[] = []; + const designTokens: DesignTokenBlock[] = []; const md = new MarkdownIt({ html: false, linkify: true }); const escapeHtml = md.utils.escapeHtml; @@ -100,9 +107,13 @@ export function parseDocument(source: string, options: ParseDocumentOptions = {} components.push({ kind: info as ComponentKind, json: token.content }); return `<div class="component" data-component-index="${index}"></div>\n`; } + if (info === "design-tokens") { + designTokens.push({ json: token.content, line: (token.map?.[0] ?? 0) + lineOffset + 1 }); + return ""; + } return `<pre><code class="language-${escapeHtml(info)}">${escapeHtml(token.content)}</code></pre>\n`; }; const bodyHtml = md.render(body); - return { meta, bodyHtml, charts, components, warnings }; + return { meta, bodyHtml, charts, components, designTokens, warnings }; } diff --git a/src/plugin.ts b/src/plugin.ts index 8105a9a..5bd407a 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -10,6 +10,7 @@ import { type RenderedArtifact, } from "./render.ts"; import { AssetPreflightError, type PortableAssets } from "./assets.ts"; +import type { ResolvedDesignTokens } from "./design-tokens.ts"; import { parseDocument } from "./markdown.ts"; import { formatPreflight, preflightDocument, trustedHtmlDiagnostic, type AuthoringDiagnostic } from "./preflight.ts"; import { FilePublisher, slugify, StaleArtifactError } from "./publisher.ts"; @@ -199,6 +200,7 @@ export const ArtifactsPlugin: Plugin = async (_input, options) => { } let preflightWarnings: AuthoringDiagnostic[] = []; let portableAssets: PortableAssets | undefined; + let designTokens: ResolvedDesignTokens | undefined; if (args.format === "html") { preflightWarnings = [trustedHtmlDiagnostic()]; } else { @@ -207,11 +209,12 @@ export const ArtifactsPlugin: Plugin = async (_input, options) => { if (errors.length > 0 || preflight.omitted > 0) return formatPreflight(preflight); preflightWarnings = preflight.diagnostics; portableAssets = preflight.assets; + designTokens = preflight.designTokens; } const rendered: RenderedArtifact = args.format === "html" ? renderRawHtml(args.markdown, args.title ? { title: args.title } : {}) - : renderArtifact(args.markdown, { assets: portableAssets }); + : renderArtifact(args.markdown, { assets: portableAssets, designTokens }); const finalFindings = scanSensitive(rendered.html); if (finalFindings.length > 0 && args.force !== true) { return `Publish blocked: the final portable bytes contain credential-looking strings: ${formatFindings(finalFindings)}. If these are intentional, call again with force: true.`; diff --git a/src/preflight.ts b/src/preflight.ts index e0c0da1..ec567f7 100644 --- a/src/preflight.ts +++ b/src/preflight.ts @@ -3,9 +3,11 @@ import { validateComponent, COMPONENT_KINDS, type ComponentKind } from "./compon import { resolvePortableAssets, AssetPreflightError, type PortableAssets } from "./assets.ts"; import { validateChartSpec } from "./render.ts"; import { slugify } from "./text.ts"; +import { parseDocument } from "./markdown.ts"; +import { loadProjectDesignTokens, resolveDesignTokens, type ResolvedDesignTokens } from "./design-tokens.ts"; export type DiagnosticSeverity = "error" | "warning"; -export type DiagnosticSource = "frontmatter" | "component" | "chart" | "markdown" | "asset" | "mode"; +export type DiagnosticSource = "frontmatter" | "component" | "chart" | "markdown" | "asset" | "design" | "mode"; export interface AuthoringDiagnostic { code: string; @@ -27,6 +29,7 @@ export interface PreflightResult { diagnostics: AuthoringDiagnostic[]; omitted: number; assets?: PortableAssets; + designTokens?: ResolvedDesignTokens; } const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/; @@ -179,6 +182,15 @@ function bounded(diagnostics: AuthoringDiagnostic[], options: PreflightOptions): export async function preflightDocument(markdown: string, options: PreflightOptions = {}): Promise<PreflightResult> { const all = [...frontmatterDiagnostics(markdown), ...markdownDiagnostics(markdown)]; + const parsed = parseDocument(markdown); + const projectDesign = options.worktreeRoot === undefined ? undefined : await loadProjectDesignTokens(options.worktreeRoot); + const design = resolveDesignTokens(parsed.meta.theme, projectDesign, parsed.designTokens.map((block) => block.json)); + for (const designIssue of design.issues) { + const line = designIssue.source === "project" + ? 1 + : parsed.designTokens[designIssue.promptIndex ?? 0]?.line ?? 1; + all.push(diagnostic(designIssue.code, "error", "design", line, designIssue.reason, designIssue.nextAction)); + } let assets: PortableAssets | undefined; if (options.worktreeRoot !== undefined) { const assetResult = await preflightAssets(markdown, options.worktreeRoot); @@ -186,7 +198,11 @@ export async function preflightDocument(markdown: string, options: PreflightOpti assets = assetResult.assets; } const result = bounded(all, options); - return { ...result, ...(assets === undefined ? {} : { assets }) }; + return { + ...result, + ...(assets === undefined ? {} : { assets }), + designTokens: design.designTokens, + }; } export function trustedHtmlDiagnostic(): AuthoringDiagnostic { diff --git a/src/render.ts b/src/render.ts index 342d1bc..ae9b786 100644 --- a/src/render.ts +++ b/src/render.ts @@ -4,6 +4,7 @@ import { renderComponent } from "./components.ts"; import { runtimeBundle, type RuntimeName } from "./runtime.ts"; import { escapeHtmlText, slugify } from "./text.ts"; import { resolvePortableAssets, type AssetLimits, type PortableAssets } from "./assets.ts"; +import { resolveDesignTokens, type ResolvedDesignTokens } from "./design-tokens.ts"; export { escapeHtmlText } from "./text.ts"; @@ -24,6 +25,7 @@ export class ArtifactTooLargeError extends Error { export interface RenderOptions { maxBytes?: number; assets?: PortableAssets; + designTokens?: ResolvedDesignTokens; } export interface PortableRenderOptions extends AssetLimits { @@ -58,7 +60,9 @@ export const ARTIFACT_CSS = `:root{color-scheme:light; --accent:#6d6bd6;--good:#2f9e6e;--good-bg:#e4f4ec;--bad:#d64550;--bad-bg:#fdeeee; --warn:#b45309;--warn-bg:#fdf0dc;--info:#33526e;--info-bg:#dce6f2; --card-info-bg:#e3eaf4;--card-warn-bg:#fdeccd;--code-bg:#f3f4f6; ---radius:16px;--shadow:0 1px 3px rgb(15 23 42/.06)} +--radius:16px;--shadow:0 1px 3px rgb(15 23 42/.06); +--artifact-font:system-ui,-apple-system,"Segoe UI",sans-serif;--artifact-heading-font:var(--artifact-font); +--body-pad:1.5rem;--body-pad-bottom:3rem;--section-gap:1.25rem;--section-pad-y:1.5rem;--section-pad-x:1.75rem;--table-font-size:.86rem} @media (prefers-color-scheme: dark){:root:not([data-theme="light"]){color-scheme:dark; --page-bg:#151a21;--card-bg:#1f2630;--ink:#e5e7eb;--ink-2:#9ca3af;--ink-3:#6b7280;--line:#333d4d; --good:#4ade80;--good-bg:#14312a;--bad:#f87171;--bad-bg:#3a2226;--warn:#fbbf24;--warn-bg:#3a2f16; @@ -69,17 +73,17 @@ export const ARTIFACT_CSS = `:root{color-scheme:light; --good:#4ade80;--good-bg:#14312a;--bad:#f87171;--bad-bg:#3a2226;--warn:#fbbf24;--warn-bg:#3a2f16; --info:#7ea4c7;--info-bg:#1e2c3d;--card-info-bg:#1e2c3d;--card-warn-bg:#3a2f16;--code-bg:#262e3a; --shadow:0 1px 3px rgb(0 0 0/.4)} -body{margin:0;background:var(--page-bg);color:var(--ink);font-family:system-ui,-apple-system,"Segoe UI",sans-serif;line-height:1.6} +body{margin:0;background:var(--page-bg);color:var(--ink);font-family:var(--artifact-font);line-height:1.6} .artifact-header{display:flex;align-items:center;gap:.6rem;padding:.9rem 1.5rem;background:var(--card-bg);border-bottom:1px solid var(--line)} .theme-toggle{margin-left:auto;background:none;border:1px solid var(--line);border-radius:999px;padding:.25rem .8rem;font-size:.75rem;font-weight:600;color:var(--ink-2);cursor:pointer} .theme-toggle:hover{border-color:var(--accent);color:var(--accent)} .artifact-header h1{font-size:1.1rem;margin:0;letter-spacing:-.01em} .artifact-icon{font-size:1.25rem} -.artifact-body{max-width:1080px;margin:0 auto;padding:1.5rem 1.5rem 3rem} +.artifact-body{max-width:1080px;margin:0 auto;padding:var(--body-pad) var(--body-pad) var(--body-pad-bottom)} .artifact-body>*:first-child{margin-top:0} .artifact-footer{max-width:1080px;margin:0 auto;padding:1rem 1.5rem 2rem;font-size:.8rem;color:var(--ink-3)} .artifact-footer a{color:inherit} -.section-card{background:var(--card-bg);border-radius:var(--radius);box-shadow:var(--shadow);padding:1.5rem 1.75rem;margin:1.25rem 0} +.section-card{background:var(--card-bg);border-radius:var(--radius);box-shadow:var(--shadow);padding:var(--section-pad-y) var(--section-pad-x);margin:var(--section-gap) 0} .section-card> :first-child{margin-top:0} .section-card p,.section-card li{max-width:68ch} td,.stat-value,.tl-time,.progress-label,.delta{font-variant-numeric:tabular-nums} @@ -179,7 +183,7 @@ pre.mermaid{background:var(--card-bg);border:1px solid var(--line);border-radius .table-wrap{margin:1rem 0} .table-filter{width:100%;max-width:320px;padding:.45rem .8rem;border:1px solid var(--line);border-radius:8px;background:var(--card-bg);color:var(--ink);font:inherit;font-size:.88rem;margin-bottom:.5rem} .table-scroll{overflow-x:auto;border:1px solid var(--line);border-radius:10px} -.data-table{margin:0;font-size:.86rem} +.data-table{margin:0;font-size:var(--table-font-size)} .data-table th{padding:.35rem .55rem;white-space:nowrap} .data-table td{padding:.3rem .55rem} .th-sort{background:none;border:none;padding:0;font:inherit;font-weight:600;color:inherit;cursor:pointer} @@ -223,14 +227,14 @@ th[data-dir="desc"] .th-sort::after{content:"↓";opacity:1;color:var(--accent)} @media print{body{background:#fff}.section-card,.stat,.variant,.card{box-shadow:none;border:1px solid #ddd}}`; const THEME_CSS: Record<string, string> = { - report: `:root{color-scheme:light;--page-bg:#f6f0e4;--card-bg:#fffdf7;--ink:#2b251a;--ink-2:#6b5f49;--ink-3:#a29378;--line:#e3d9c4;--accent:#b4541e;--code-bg:#f1e9d8} -h2,.callout-title,.stat-value{font-family:Georgia,Charter,"Times New Roman",serif}`, - ops: `:root{color-scheme:dark;--page-bg:#0f140f;--card-bg:#171f17;--ink:#d5e5cf;--ink-2:#8fa389;--ink-3:#5c6b57;--line:#263026;--accent:#4ade80;--code-bg:#131c13;--good:#4ade80;--good-bg:#14311f;--bad:#f87171;--bad-bg:#3a1d1d;--warn:#fbbf24;--warn-bg:#3a2f16;--info:#7ea4c7;--info-bg:#1c2a38;--card-info-bg:#1c2a38;--card-warn-bg:#33290f} -h2,.callout-title{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:0}`, - editorial: `:root{color-scheme:light;--page-bg:#fafafa;--card-bg:#ffffff;--ink:#141414;--ink-2:#525252;--ink-3:#a3a3a3;--line:#e5e5e5;--accent:#141414;--code-bg:#f5f5f5;--radius:4px;--shadow:none} -h2{font-family:Georgia,Charter,"Times New Roman",serif;font-size:1.6rem;font-weight:500} + report: `:root{color-scheme:light;--page-bg:#f6f0e4;--card-bg:#fffdf7;--ink:#2b251a;--ink-2:#6b5f49;--ink-3:#a29378;--line:#e3d9c4;--accent:#b4541e;--code-bg:#f1e9d8;--artifact-heading-font:Georgia,Charter,"Times New Roman",serif} +h2,.callout-title,.stat-value{font-family:var(--artifact-heading-font)}`, + ops: `:root{color-scheme:dark;--page-bg:#0f140f;--card-bg:#171f17;--ink:#d5e5cf;--ink-2:#8fa389;--ink-3:#5c6b57;--line:#263026;--accent:#4ade80;--code-bg:#131c13;--good:#4ade80;--good-bg:#14311f;--bad:#f87171;--bad-bg:#3a1d1d;--warn:#fbbf24;--warn-bg:#3a2f16;--info:#7ea4c7;--info-bg:#1c2a38;--card-info-bg:#1c2a38;--card-warn-bg:#33290f;--artifact-heading-font:ui-monospace,SFMono-Regular,Menlo,monospace} +h2,.callout-title{font-family:var(--artifact-heading-font);letter-spacing:0}`, + editorial: `:root{color-scheme:light;--page-bg:#fafafa;--card-bg:#ffffff;--ink:#141414;--ink-2:#525252;--ink-3:#a3a3a3;--line:#e5e5e5;--accent:#141414;--code-bg:#f5f5f5;--radius:4px;--shadow:none;--artifact-heading-font:Georgia,Charter,"Times New Roman",serif} +h2{font-family:var(--artifact-heading-font);font-size:1.6rem;font-weight:500} .section-card,.stat,.variant,.card{border:1px solid var(--line)} -.artifact-header h1{font-family:Georgia,Charter,"Times New Roman",serif;font-size:1.35rem;font-weight:500}`, +.artifact-header h1{font-family:var(--artifact-heading-font);font-size:1.35rem;font-weight:500}`, }; const BOOT = `(function () { @@ -699,6 +703,7 @@ interface AssembleInput { needsMermaid: boolean; maxBytes: number; assetCss?: string; + designTokens?: ResolvedDesignTokens; } function assemblePage(input: AssembleInput): string { @@ -714,11 +719,17 @@ function assemblePage(input: AssembleInput): string { const themeAttr = input.theme !== undefined && THEME_CSS[input.theme] !== undefined ? ` data-page-theme="${input.theme}"` + : input.designTokens?.fixesColorMode + ? ' data-page-theme="tokens"' : ""; + const designAttr = input.designTokens?.active ? " data-design-tokens" : ""; + const designMetadata = input.designTokens?.active + ? `<meta name="artifact-design-provenance" content="${escapeHtmlText(JSON.stringify(input.designTokens.provenance))}">` + : ""; const parts: string[] = [ "<!doctype html>", - `<html lang="en"${themeAttr}>`, + `<html lang="en"${themeAttr}${designAttr}>`, "<head>", '<meta charset="utf-8">', `<meta http-equiv="Content-Security-Policy" content="${CSP}">`, @@ -727,8 +738,9 @@ function assemblePage(input: AssembleInput): string { input.description !== undefined ? `<meta name="description" content="${escapeHtmlText(input.description)}">` : "", + designMetadata, `<title>${escapeHtmlText(input.title)}`, - ``, + ``, "", "", `
${escapeHtmlText(input.icon)}

${escapeHtmlText(input.title)}

`, @@ -759,8 +771,18 @@ function assemblePage(input: AssembleInput): string { export function renderArtifact(markdown: string, options: RenderOptions = {}): RenderedArtifact { const doc = parseDocument(markdown, { assets: options.assets?.bySource }); + const inlineDesign = options.designTokens === undefined + ? resolveDesignTokens(doc.meta.theme, undefined, doc.designTokens.map((block) => block.json)) + : undefined; + const designTokens = options.designTokens ?? inlineDesign?.designTokens; let bodyHtml = doc.bodyHtml; + if (inlineDesign !== undefined && inlineDesign.issues.length > 0) { + const errors = inlineDesign.issues + .map((item) => `
${escapeHtmlText(`Design tokens failed validation: ${item.reason}. ${item.nextAction}`)}
`) + .join(""); + bodyHtml = `${errors}${bodyHtml}`; + } let needsBoot = false; let needsMermaid = false; doc.components.forEach((block, index) => { @@ -783,7 +805,8 @@ export function renderArtifact(markdown: string, options: RenderOptions = {}): R maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES, assetCss: options.assets?.font === undefined ? undefined - : `@font-face{font-family:"Artifact Project";src:url(${options.assets.font.dataUri}) format("${fontFormat(options.assets.font.mime)}");font-display:swap}body{font-family:"Artifact Project",system-ui,-apple-system,"Segoe UI",sans-serif}`, + : `@font-face{font-family:"Artifact Project";src:url(${options.assets.font.dataUri}) format("${fontFormat(options.assets.font.mime)}");font-display:swap}:root{--artifact-font:"Artifact Project",system-ui,-apple-system,"Segoe UI",sans-serif;--artifact-heading-font:var(--artifact-font)}`, + designTokens, }); return { html, meta: doc.meta, chartCount: doc.charts.length }; } diff --git a/test/design-tokens.test.ts b/test/design-tokens.test.ts new file mode 100644 index 0000000..77fcfbf --- /dev/null +++ b/test/design-tokens.test.ts @@ -0,0 +1,219 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { mkdir, mkdtemp, readFile, rm, symlink, unlink, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import type { PluginInput, ToolContext } from "@opencode-ai/plugin"; +import { + DESIGN_TOKEN_FILE, + MAX_DESIGN_TOKEN_BYTES, + loadProjectDesignTokens, + resolveDesignTokens, +} from "../src/design-tokens.ts"; +import { preflightDocument } from "../src/preflight.ts"; +import { renderArtifact, renderRawHtml } from "../src/render.ts"; +import { ArtifactsPlugin } from "../src/plugin.ts"; + +const execFileAsync = promisify(execFile); +const ROOT = resolve(import.meta.dirname, ".."); + +async function withTemp(run: (root: string) => Promise): Promise { + const root = await mkdtemp(join(tmpdir(), "design-tokens-")); + try { await run(root); } finally { await rm(root, { recursive: true, force: true }); } +} + +async function writeProject(root: string, value: unknown): Promise { + await mkdir(join(root, ".opencode"), { recursive: true }); + await writeFile(join(root, DESIGN_TOKEN_FILE), `${JSON.stringify(value)}\n`); +} + +const MARKDOWN = [ + "---", + "title: Token Surface", + "theme: report", + "---", + "```design-tokens", + '{"schemaVersion":1,"tokens":{"accent":"#8b1e3f","radius":"soft"}}', + "```", + "# Token Surface", + "", + "## Summary", + "A deterministic token page.", +].join("\n"); + +test("prompt tokens outrank project and theme with deterministic provenance and CSS", async () => { + await withTemp(async (root) => { + await writeProject(root, { schemaVersion: 1, tokens: { accent: "#005a9c", spacing: "compact", font: "mono" } }); + const first = await preflightDocument(MARKDOWN, { worktreeRoot: root }); + const second = await preflightDocument(MARKDOWN, { worktreeRoot: root }); + assert.deepEqual(first, second); + assert.deepEqual(first.diagnostics, []); + assert.equal(first.designTokens?.values.accent, "#8b1e3f"); + assert.equal(first.designTokens?.values.spacing, "compact"); + assert.equal(first.designTokens?.values.pageBackground, "#f6f0e4"); + assert.equal(first.designTokens?.provenance.accent, "prompt"); + assert.equal(first.designTokens?.provenance.spacing, "project"); + assert.equal(first.designTokens?.provenance.pageBackground, "theme"); + + const firstHtml = renderArtifact(MARKDOWN, { designTokens: first.designTokens }).html; + const secondHtml = renderArtifact(MARKDOWN, { designTokens: second.designTokens }).html; + assert.equal(firstHtml, secondHtml); + assert.match(firstHtml, //); + assert.match(firstHtml, /--accent:#8b1e3f/); + assert.match(firstHtml, /--section-gap:\.9rem/); + assert.match(firstHtml, /--radius:8px/); + assert.match(firstHtml, /artifact-design-provenance/); + assert.match(firstHtml, /accent":"prompt/); + assert.ok(!firstHtml.includes("design-tokens\n")); + }); +}); + +test("invalid project schemas and contrast are atomic lower-precedence fallbacks", () => { + const unknown = resolveDesignTokens("report", { + value: { schemaVersion: 1, tokens: { accent: "#005a9c", selector: "body{}" } }, + }, []); + assert.equal(unknown.issues[0]?.code, "design-token-unknown"); + assert.equal(unknown.designTokens.active, false); + assert.equal(unknown.designTokens.values.accent, "#b4541e"); + assert.equal(unknown.designTokens.provenance.accent, "theme"); + + const contrast = resolveDesignTokens(undefined, { + value: { schemaVersion: 1, tokens: { text: "#ffffff", surface: "#ffffff" } }, + }, []); + assert.equal(contrast.issues[0]?.code, "design-contrast"); + assert.equal(contrast.designTokens.active, false); + assert.equal(contrast.designTokens.values.text, "#111827"); +}); + +test("a layout-only override preserves theme color and typography slots", () => { + const resolved = resolveDesignTokens("report", { + value: { schemaVersion: 1, tokens: { spacing: "compact" } }, + }, []).designTokens; + assert.match(resolved.css, /--section-gap:\.9rem/); + assert.ok(!resolved.css.includes("--page-bg")); + assert.ok(!resolved.css.includes("--artifact-font")); + const html = renderArtifact("---\ntheme: report\n---\n# Report", { designTokens: resolved }).html; + assert.match(html, /--page-bg:#f6f0e4/); + assert.match(html, /--artifact-heading-font:Georgia/); +}); + +test("token object order cannot change serialized CSS bytes", () => { + const first = resolveDesignTokens(undefined, { + value: { schemaVersion: 1, tokens: { font: "mono", accent: "#005a9c", radius: "soft" } }, + }, []).designTokens; + const second = resolveDesignTokens(undefined, { + value: { tokens: { radius: "soft", accent: "#005a9c", font: "mono" }, schemaVersion: 1 }, + }, []).designTokens; + assert.equal(first.css, second.css); + assert.match(first.css, /--artifact-font:ui-monospace,SFMono-Regular,Menlo,monospace/); +}); + +test("hostile values never enter CSS and standalone rendering shows an escaped fallback", () => { + const cases: Array<[string, string]> = [ + ["accent", "#fff;}body{display:none}"], + ["font", "url(https://example.com/font.woff2)"], + ["spacing", "1rem;@import'x'"], + ["radius", "expression(alert(1))"], + ["density", ""], + ]; + for (const [name, value] of cases) { + const markdown = `\`\`\`design-tokens\n${JSON.stringify({ schemaVersion: 1, tokens: { [name]: value } })}\n\`\`\`\n# Safe`; + const html = renderArtifact(markdown).html; + assert.match(html, /Design tokens failed validation/); + assert.ok(!html.includes(value)); + assert.ok(!html.includes("")); + } + assert.equal(renderRawHtml(MARKDOWN).html.includes("data-design-tokens"), false); +}); + +test("one invalid token source reports every detectable schema error", async () => { + const markdown = '```design-tokens\n{"schemaVersion":1,"tokens":{"selector":"body","accent":"red","font":"Comic Sans","spacing":"2rem"}}\n```'; + const result = await preflightDocument(markdown); + assert.deepEqual(result.diagnostics.map((item) => item.code), [ + "design-token-unknown", + "design-color", + "design-font", + "design-spacing", + ]); +}); + +test("project discovery accepts the exact byte limit and refuses overflow and symlinks", async () => { + await withTemp(async (root) => { + await mkdir(join(root, ".opencode")); + const path = join(root, DESIGN_TOKEN_FILE); + const packet = JSON.stringify({ schemaVersion: 1, tokens: { radius: "soft" } }); + const exact = `${packet}${" ".repeat(MAX_DESIGN_TOKEN_BYTES - Buffer.byteLength(packet))}`; + await writeFile(path, exact); + assert.deepEqual((await loadProjectDesignTokens(root))?.value, { schemaVersion: 1, tokens: { radius: "soft" } }); + + await writeFile(path, `${exact} `); + assert.equal((await loadProjectDesignTokens(root))?.issue?.code, "design-project-too-large"); + + await unlink(path); + await writeFile(join(root, "outside.json"), packet); + await symlink(join(root, "outside.json"), path); + assert.equal((await loadProjectDesignTokens(root))?.issue?.code, "design-project-file"); + + await rm(join(root, ".opencode"), { recursive: true, force: true }); + await mkdir(join(root, "outside-opencode")); + await writeFile(join(root, "outside-opencode", "artifact-tokens.json"), packet); + await symlink(join(root, "outside-opencode"), join(root, ".opencode")); + assert.equal((await loadProjectDesignTokens(root))?.issue?.code, "design-project-path"); + }); +}); + +test("prompt diagnostics retain absolute source lines and reject duplicate declarations", async () => { + const invalid = "---\ntitle: Lines\n---\n\n```design-tokens\n{bad\n```"; + const result = await preflightDocument(invalid); + assert.equal(result.diagnostics[0]?.code, "design-json"); + assert.equal(result.diagnostics[0]?.line, 5); + + const duplicate = await preflightDocument("```design-tokens\n{\"schemaVersion\":1,\"tokens\":{}}\n```\n\n```design-tokens\n{\"schemaVersion\":1,\"tokens\":{}}\n```"); + assert.equal(duplicate.diagnostics[0]?.code, "design-prompt-duplicate"); +}); + +test("plugin and CLI apply project tokens before permission and publication", async () => { + await withTemp(async (root) => { + await writeProject(root, { schemaVersion: 1, tokens: { spacing: "compact", font: "mono" } }); + const publish = (await ArtifactsPlugin({} as unknown as PluginInput)).tool?.artifact_publish; + assert.ok(publish); + const asked: Array[0]> = []; + const context: ToolContext = { + sessionID: "design", messageID: "design", agent: "test", directory: root, worktree: root, + abort: new AbortController().signal, metadata: () => {}, ask: async (input) => { asked.push(input); }, + }; + assert.match(String(await publish.execute({ markdown: MARKDOWN }, context)), /Artifact published/); + assert.equal(asked.length, 1); + const pluginHtml = await readFile(join(root, ".opencode", "artifacts", "token-surface.html"), "utf8"); + assert.match(pluginHtml, /--section-gap:\.9rem/); + assert.match(pluginHtml, /--accent:#8b1e3f/); + + const input = join(root, "tokens.md"); + const output = join(root, "tokens.html"); + await writeFile(input, MARKDOWN); + await execFileAsync(process.execPath, [join(ROOT, "src", "cli.ts"), "render", input, "-o", output], { cwd: root }); + const cliHtml = await readFile(output, "utf8"); + assert.match(cliHtml, /--section-gap:\.9rem/); + assert.match(cliHtml, /--accent:#8b1e3f/); + assert.match(cliHtml, /artifact-design-provenance/); + }); +}); + +test("invalid project tokens refuse plugin permission and writes without partial CSS", async () => { + await withTemp(async (root) => { + await writeProject(root, { schemaVersion: 1, tokens: { accent: "#005a9c", css: "body{display:none}" } }); + const publish = (await ArtifactsPlugin({} as unknown as PluginInput)).tool?.artifact_publish; + assert.ok(publish); + let asked = false; + const context: ToolContext = { + sessionID: "invalid-design", messageID: "invalid-design", agent: "test", directory: root, worktree: root, + abort: new AbortController().signal, metadata: () => {}, ask: async () => { asked = true; }, + }; + const output = String(await publish.execute({ markdown: "# Refused" }, context)); + assert.match(output, /design-token-unknown/); + assert.equal(asked, false); + await assert.rejects(readFile(join(root, ".opencode", "artifacts", "refused.html"))); + }); +}); From 3b5aee29e1dc6644e8ae1375eedc3b57ff910a24 Mon Sep 17 00:00:00 2001 From: bitgorust Date: Mon, 17 Aug 2026 18:30:38 +0200 Subject: [PATCH 07/10] feat: complete renderer accessibility foundations --- README.md | 5 +- docs/component-spec.md | 23 +- .../goal-3-accessibility-2026-08-17.md | 59 ++++ ...al-3-accessibility-desktop-2026-08-17.json | 248 +++++++++++++++++ ...oal-3-accessibility-desktop-2026-08-17.png | Bin 0 -> 65181 bytes ...cessibility-mobile-reduced-2026-08-17.json | 248 +++++++++++++++++ ...ccessibility-mobile-reduced-2026-08-17.png | Bin 0 -> 39974 bytes ...l-3-accessibility-zoom-200-2026-08-17.json | 248 +++++++++++++++++ ...al-3-accessibility-zoom-200-2026-08-17.png | Bin 0 -> 79616 bytes docs/redistribution-inventory.json | 27 ++ examples/patterns/accessibility-rtl.md | 42 +++ examples/patterns/dashboard.md | 1 + examples/patterns/funnel-analysis.md | 1 + examples/patterns/incident.md | 1 + examples/patterns/plan.md | 1 + examples/patterns/portable-mixed.md | 1 + examples/patterns/tune-controls.md | 2 + scripts/accessibility-browser-evidence.ts | 257 ++++++++++++++++++ skills/artifact-pages/reference/components.md | 11 +- .../renderer-accessibility-i18n/change.json | 4 +- .../renderer-accessibility-i18n/evidence.md | 14 +- .../renderer-accessibility-i18n/tasks.md | 12 +- specs/current/portable-renderer.spec.md | 18 +- src/components.ts | 90 ++++-- src/design-tokens.ts | 11 +- src/locale.ts | 58 ++++ src/markdown.ts | 8 + src/preflight.ts | 23 +- src/render.ts | 235 +++++++++++----- src/served-html.ts | 15 +- src/text.ts | 10 + test/accessibility.test.ts | 110 ++++++++ test/components.test.ts | 18 +- test/design-tokens.test.ts | 4 +- test/preflight.test.ts | 4 +- test/render.test.ts | 4 +- test/renderer-security.test.ts | 2 + test/serve.test.ts | 4 +- 38 files changed, 1680 insertions(+), 139 deletions(-) create mode 100644 docs/evidence/renderer/goal-3-accessibility-2026-08-17.md create mode 100644 docs/evidence/renderer/goal-3-accessibility-desktop-2026-08-17.json create mode 100644 docs/evidence/renderer/goal-3-accessibility-desktop-2026-08-17.png create mode 100644 docs/evidence/renderer/goal-3-accessibility-mobile-reduced-2026-08-17.json create mode 100644 docs/evidence/renderer/goal-3-accessibility-mobile-reduced-2026-08-17.png create mode 100644 docs/evidence/renderer/goal-3-accessibility-zoom-200-2026-08-17.json create mode 100644 docs/evidence/renderer/goal-3-accessibility-zoom-200-2026-08-17.png create mode 100644 examples/patterns/accessibility-rtl.md create mode 100644 scripts/accessibility-browser-evidence.ts create mode 100644 src/locale.ts create mode 100644 test/accessibility.test.ts diff --git a/README.md b/README.md index 616c845..5d61d80 100644 --- a/README.md +++ b/README.md @@ -167,9 +167,10 @@ opencode-artifacts import ./bundle Full reference: [`docs/component-spec.md`](docs/component-spec.md). Short version: -- **Frontmatter**: `title`, `icon` (emoji favicon), `description` (gallery subtitle), and an optional worktree-relative `font` (WOFF/WOFF2/TTF/OTF embedded under `font-src data:`) +- **Frontmatter**: `title`, `icon`, `description`, explicit `lang`/`dir`, `locale`, `timezone`, and an optional worktree-relative `font` (WOFF/WOFF2/TTF/OTF embedded under `font-src data:`) - **Components** (JSON fences): `stats` metric cards, `timeline`, `findings` (severity-coded), `compare` variant cards, `callout` insight cards, `progress`, `diff` (annotated), `copy` (copy-to-session button), `decisions` (workshop rows the session reads back via `artifact_state`) -- **Charts/diagrams**: ```` ```vega-lite ```` / ```` ```vega ```` / ```` ```echarts ```` / ```` ```mermaid ```` fences; runtimes inline only when used +- **Charts/diagrams**: ```` ```vega-lite ```` / ```` ```vega ```` / ```` ```echarts ```` require a top-level text `description`; Mermaid starts with `%% summary:`; runtimes inline only when used +- **Accessible data**: tables require `caption`; `num`, `date`, and `datetime` columns format under the declared locale/time zone, with zoned ISO input for dates - **Markdown extras**: GitHub alerts (`> [!WARNING]` etc.), task lists, heading anchors, `##` sections become cards - **Local images**: ordinary `![meaningful alt](path/to/image.png)` resolves from the worktree root and embeds PNG/JPEG/GIF/WebP or constrained static SVG. Use the exact title `"decorative"` with empty alt only for an intentionally decorative image. URLs, absolute paths, traversal, and symlinks are refused. - **Local fonts**: `font: path/to/project.woff2` uses the same contained, MIME-checked pipeline and a generated `@font-face`; it never permits a viewer network request or arbitrary CSS. diff --git a/docs/component-spec.md b/docs/component-spec.md index ca66ee5..ee22587 100644 --- a/docs/component-spec.md +++ b/docs/component-spec.md @@ -20,10 +20,10 @@ unless explicit redistribution authority is recorded. --ink-2: #4b5563 --ink-3: #9ca3af --line: #e5e7eb ---accent: #6d6bd6 (periwinkle, chart fill / pills) ---good: #2f9e6e on #e4f4ec ---bad: #d64550 on #fdeeee ---warn: #b45309 on #fdf0dc +--accent: #5f5dbf (AA periwinkle, chart fill / pills) +--good: #237a52 on #e4f4ec +--bad: #b42335 on #fdeeee +--warn: #92400e on #fdf0dc --info: #33526e on #dce6f2 --card-bad-bg: #fdeeee (whole metric card tinted when tone=bad) --card-info-bg: #e3eaf4 (insight card, blue-gray) @@ -36,6 +36,21 @@ unless explicit redistribution authority is recorded. Dark mode: same hues, backgrounds shifted (page `#151a21`, card `#1f2630`, ink `#e5e7eb`), via `color-scheme: light dark` + `@media (prefers-color-scheme: dark)` overrides. +### Accessibility and internationalization + +Declarative pages expose a skip link, header/main landmarks, Unicode-safe heading anchors, +visible focus, control names/state, reduced-motion behavior, responsive reflow, and a print +mode that removes interactive chrome. Frontmatter accepts `lang`, `dir`, `locale`, and +`timezone`; the deterministic fallback is English (`en-US`), left-to-right, and UTC. RTL +languages infer RTL direction unless `dir` is explicit. + +Every chart requires a top-level `description`, every Mermaid fence begins with +`%% summary: ...`, and every table requires `caption`. Tables accept `num`, `date`, and +`datetime`; date values are zoned ISO timestamps and render under the declared locale and IANA +time zone. Missing equivalents or invalid locale metadata refuse CLI/plugin publication. +Decisions use radio semantics and arrow-key navigation. Served comments expose a named +launcher and dialog with Escape/cancel/save focus restoration. + ### Bounded project and prompt overrides The renderer discovers only `.opencode/artifact-tokens.json` for project tokens. A document diff --git a/docs/evidence/renderer/goal-3-accessibility-2026-08-17.md b/docs/evidence/renderer/goal-3-accessibility-2026-08-17.md new file mode 100644 index 0000000..b35b999 --- /dev/null +++ b/docs/evidence/renderer/goal-3-accessibility-2026-08-17.md @@ -0,0 +1,59 @@ +# Goal 3 accessibility and internationalization evidence — 2026-08-17 + +Scope: implementation evidence for `renderer-accessibility-i18n`. It is a Linux/Chromium +observation, not screen-reader or supported-platform certification. + +## Automated coverage + +The checked-in `examples/patterns/accessibility-rtl.md` fixture declares Arabic, RTL, +`ar-EG`, and `Asia/Riyadh`, and includes an alert, tasks, progress, ECharts summary, captioned +numeric/zoned-date table, decisions, and served comments. `test/accessibility.test.ts` checks +the semantic output, deterministic locale/time-zone formatting, preflight refusals, logical +CSS, responsive/print/reduced-motion rules, and AA contrast pairs. `test/serve.test.ts` +retains the bridge-before-boot regression that makes persisted state and the comment launcher +available when the renderer initializes. + +## Real Chromium surface + +Harness: `scripts/accessibility-browser-evidence.ts` with Chromium 151 from +`selenium/standalone-chromium`. It retains Chrome's accessibility tree, keyboard state +transitions, computed media/color/layout observations, print observations, browser console, +requests, and screenshots. + +| Cell | CSS viewport | Media | Result | +|---|---:|---|---| +| desktop | 1440 × 1057 | light preference, motion allowed | pass | +| mobile-width | 390 × 701 | dark preference, reduced motion | pass | +| 200%-equivalent reflow | 640 × 500 at DPR 2 from a 1280 × 1000 physical surface | light preference | pass | + +The 200% cell uses Chromium device metrics: half the CSS viewport at two physical pixels per +CSS pixel. This deterministically exercises the same reflow width without claiming that the +headless browser's UI zoom shortcut changed state. + +Across all three cells: + +- the semantic audit was empty, the accessibility tree named the Arabic chart summary and + captioned table, horizontal page overflow was false, browser console entries were zero, + and external HTTP requests were zero; +- Tab focused the skip link and Enter moved focus to `artifact-main`; ArrowRight moved the + decision radio and updated `aria-checked`; Enter sorted the table and updated + `aria-sort="ascending"`; +- Enter on the comment launcher focused the named textarea, Escape restored launcher focus, + and keyboard save created one comment and restored focus without leaving a dialog open; +- the theme control switched state by keyboard with visible focus; the mobile cell reported + dark preference and reduced motion; and the 200%-equivalent RTL cell retained all content + without horizontal overflow or dock obstruction; +- print emulation hid theme/comment/filter controls and used a white page background. + +Retained artifacts: + +- `goal-3-accessibility-desktop-2026-08-17.{json,png}` +- `goal-3-accessibility-mobile-reduced-2026-08-17.{json,png}` +- `goal-3-accessibility-zoom-200-2026-08-17.{json,png}` + +## Open certification cell + +No actual screen reader or equivalent assistive-technology environment is available in this +workspace. Chrome accessibility-tree output is useful automation but is not a substitute for +manual AT operation. The packet therefore remains `implementing`; its archive task and the +manual screen-reader/certification claim remain open while independent Goal 3 work continues. diff --git a/docs/evidence/renderer/goal-3-accessibility-desktop-2026-08-17.json b/docs/evidence/renderer/goal-3-accessibility-desktop-2026-08-17.json new file mode 100644 index 0000000..28a7b53 --- /dev/null +++ b/docs/evidence/renderer/goal-3-accessibility-desktop-2026-08-17.json @@ -0,0 +1,248 @@ +{ + "capturedAt": "2026-08-17T16:25:19.235Z", + "browser": "Chromium 151 via selenium/standalone-chromium", + "fixture": "http://127.0.0.1:4173/accessibility-rtl.html", + "requested": { + "viewportWidth": 1440, + "viewportHeight": 1200, + "colorScheme": "light", + "reducedMotion": "no-preference", + "zoomPercent": 100 + }, + "usefulContentMs": 852, + "keyboard": { + "skipFocus": "skip-link", + "skipTargetFocus": "artifact-main", + "decisionTrace": { + "active": "hold", + "checked": "true", + "selected": "hold" + }, + "tableSortTrace": { + "direction": "ascending", + "label": "الفئة" + }, + "commentDialogFocus": "artifact-comment-input", + "commentEscapeFocus": "comment-launcher", + "commentSaveTrace": { + "active": "comment-launcher", + "count": 1, + "dialogOpen": false + }, + "themeTrace": { + "label": "Theme: dark. Activate to switch.", + "pressed": null, + "state": "dark" + } + }, + "observations": { + "animationDuration": "0s", + "audit": [], + "chartSummary": "ترتفع الإشارة من ثلاث نقاط إلى خمس نقاط خلال يومين.", + "colorScheme": "light", + "computedColors": { + "accent": "#a8a6ff", + "page": "#151a21", + "surface": "#1f2630", + "text": "#e5e7eb" + }, + "dir": "rtl", + "focusOutline": "solid", + "headings": [ + { + "level": 1, + "name": "مراجعة الإشارات" + }, + { + "level": 1, + "name": "مراجعة الإشارات" + }, + { + "level": 2, + "name": "حالة المراجعة" + }, + { + "level": 2, + "name": "اتجاه الإشارة" + }, + { + "level": 2, + "name": "سجل القياسات" + }, + { + "level": 2, + "name": "القرار التالي" + }, + { + "level": "2", + "name": "Comments (1)" + } + ], + "horizontalOverflow": false, + "interactiveVisible": { + "comments": true, + "filter": true, + "theme": true + }, + "landmarks": { + "aside": 2, + "footer": 0, + "header": 1, + "main": 1 + }, + "lang": "ar", + "locale": "ar-EG", + "progress": { + "max": "4", + "name": "تقدم المراجعة", + "now": "3" + }, + "radioStates": [ + { + "checked": "false", + "name": "نشربعد اكتمال المراجعة", + "tabindex": "-1" + }, + { + "checked": "true", + "name": "انتظار", + "tabindex": "0" + } + ], + "readyState": "complete", + "reducedMotion": false, + "tableCaption": "سجل الإشارات", + "tableCount": "2 rows", + "timezone": "Asia/Riyadh", + "viewport": { + "devicePixelRatio": 1, + "height": 1057, + "method": "native 100% viewport", + "preZoomWidth": 1440, + "requestedZoom": 100, + "width": 1440 + } + }, + "accessibilityTree": [ + { + "role": "RootWebArea", + "name": "مراجعة الإشارات" + }, + { + "role": "banner", + "name": "" + }, + { + "role": "main", + "name": "" + }, + { + "role": "button", + "name": "Add comment" + }, + { + "role": "heading", + "name": "مراجعة الإشارات" + }, + { + "role": "button", + "name": "Theme: dark. Activate to switch." + }, + { + "role": "heading", + "name": "مراجعة الإشارات" + }, + { + "role": "note", + "name": "Note" + }, + { + "role": "heading", + "name": "Comments (1)" + }, + { + "role": "button", + "name": "Resolve comment: Page comment" + }, + { + "role": "heading", + "name": "حالة المراجعة" + }, + { + "role": "progressbar", + "name": "تقدم المراجعة" + }, + { + "role": "heading", + "name": "اتجاه الإشارة" + }, + { + "role": "figure", + "name": "" + }, + { + "role": "heading", + "name": "سجل القياسات" + }, + { + "role": "table", + "name": "سجل الإشارات" + }, + { + "role": "status", + "name": "" + }, + { + "role": "heading", + "name": "القرار التالي" + }, + { + "role": "radiogroup", + "name": "ما الخطوة التالية؟" + }, + { + "role": "image", + "name": "ترتفع الإشارة من ثلاث نقاط إلى خمس نقاط خلال يومين." + }, + { + "role": "caption", + "name": "" + }, + { + "role": "radio", + "name": "نشر بعد اكتمال المراجعة" + }, + { + "role": "radio", + "name": "انتظار" + }, + { + "role": "button", + "name": "Sort by الفئة" + }, + { + "role": "button", + "name": "Sort by العدد" + }, + { + "role": "button", + "name": "Sort by وقت الالتقاط" + } + ], + "printObservation": { + "commentsHidden": true, + "filterHidden": true, + "pageBackground": "rgb(255, 255, 255)", + "themeHidden": true + }, + "browserLogs": [], + "requestUrls": [ + "http://127.0.0.1:4173/accessibility-rtl.html", + "http://127.0.0.1:4173/__state/accessibility-rtl", + "http://127.0.0.1:4173/__comments/accessibility-rtl", + "http://127.0.0.1:4173/__sse", + "http://127.0.0.1:4173/__state/accessibility-rtl", + "http://127.0.0.1:4173/__comments/accessibility-rtl" + ], + "externalHttpRequests": [] +} diff --git a/docs/evidence/renderer/goal-3-accessibility-desktop-2026-08-17.png b/docs/evidence/renderer/goal-3-accessibility-desktop-2026-08-17.png new file mode 100644 index 0000000000000000000000000000000000000000..9e6a8313c118f7e9ec9322377cd3bad40e711858 GIT binary patch literal 65181 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 H*{lBpjH4%M literal 0 HcmV?d00001 diff --git a/docs/evidence/renderer/goal-3-accessibility-mobile-reduced-2026-08-17.json b/docs/evidence/renderer/goal-3-accessibility-mobile-reduced-2026-08-17.json new file mode 100644 index 0000000..4ff286d --- /dev/null +++ b/docs/evidence/renderer/goal-3-accessibility-mobile-reduced-2026-08-17.json @@ -0,0 +1,248 @@ +{ + "capturedAt": "2026-08-17T16:25:33.901Z", + "browser": "Chromium 151 via selenium/standalone-chromium", + "fixture": "http://127.0.0.1:4173/accessibility-rtl.html", + "requested": { + "viewportWidth": 390, + "viewportHeight": 844, + "colorScheme": "dark", + "reducedMotion": "reduce", + "zoomPercent": 100 + }, + "usefulContentMs": 841, + "keyboard": { + "skipFocus": "skip-link", + "skipTargetFocus": "artifact-main", + "decisionTrace": { + "active": "hold", + "checked": "true", + "selected": "hold" + }, + "tableSortTrace": { + "direction": "ascending", + "label": "الفئة" + }, + "commentDialogFocus": "artifact-comment-input", + "commentEscapeFocus": "comment-launcher", + "commentSaveTrace": { + "active": "comment-launcher", + "count": 1, + "dialogOpen": false + }, + "themeTrace": { + "label": "Theme: dark. Activate to switch.", + "pressed": null, + "state": "dark" + } + }, + "observations": { + "animationDuration": "1e-05s", + "audit": [], + "chartSummary": "ترتفع الإشارة من ثلاث نقاط إلى خمس نقاط خلال يومين.", + "colorScheme": "dark", + "computedColors": { + "accent": "#a8a6ff", + "page": "#151a21", + "surface": "#1f2630", + "text": "#e5e7eb" + }, + "dir": "rtl", + "focusOutline": "solid", + "headings": [ + { + "level": 1, + "name": "مراجعة الإشارات" + }, + { + "level": 1, + "name": "مراجعة الإشارات" + }, + { + "level": 2, + "name": "حالة المراجعة" + }, + { + "level": 2, + "name": "اتجاه الإشارة" + }, + { + "level": 2, + "name": "سجل القياسات" + }, + { + "level": 2, + "name": "القرار التالي" + }, + { + "level": "2", + "name": "Comments (1)" + } + ], + "horizontalOverflow": false, + "interactiveVisible": { + "comments": true, + "filter": true, + "theme": true + }, + "landmarks": { + "aside": 2, + "footer": 0, + "header": 1, + "main": 1 + }, + "lang": "ar", + "locale": "ar-EG", + "progress": { + "max": "4", + "name": "تقدم المراجعة", + "now": "3" + }, + "radioStates": [ + { + "checked": "false", + "name": "نشربعد اكتمال المراجعة", + "tabindex": "-1" + }, + { + "checked": "true", + "name": "انتظار", + "tabindex": "0" + } + ], + "readyState": "complete", + "reducedMotion": true, + "tableCaption": "سجل الإشارات", + "tableCount": "2 rows", + "timezone": "Asia/Riyadh", + "viewport": { + "devicePixelRatio": 1, + "height": 701, + "method": "native 100% viewport", + "preZoomWidth": 390, + "requestedZoom": 100, + "width": 390 + } + }, + "accessibilityTree": [ + { + "role": "RootWebArea", + "name": "مراجعة الإشارات" + }, + { + "role": "banner", + "name": "" + }, + { + "role": "main", + "name": "" + }, + { + "role": "button", + "name": "Add comment" + }, + { + "role": "heading", + "name": "مراجعة الإشارات" + }, + { + "role": "button", + "name": "Theme: dark. Activate to switch." + }, + { + "role": "heading", + "name": "مراجعة الإشارات" + }, + { + "role": "note", + "name": "Note" + }, + { + "role": "heading", + "name": "Comments (1)" + }, + { + "role": "button", + "name": "Resolve comment: Page comment" + }, + { + "role": "heading", + "name": "حالة المراجعة" + }, + { + "role": "progressbar", + "name": "تقدم المراجعة" + }, + { + "role": "heading", + "name": "اتجاه الإشارة" + }, + { + "role": "figure", + "name": "" + }, + { + "role": "heading", + "name": "سجل القياسات" + }, + { + "role": "table", + "name": "سجل الإشارات" + }, + { + "role": "status", + "name": "" + }, + { + "role": "heading", + "name": "القرار التالي" + }, + { + "role": "radiogroup", + "name": "ما الخطوة التالية؟" + }, + { + "role": "image", + "name": "ترتفع الإشارة من ثلاث نقاط إلى خمس نقاط خلال يومين." + }, + { + "role": "caption", + "name": "" + }, + { + "role": "radio", + "name": "نشر بعد اكتمال المراجعة" + }, + { + "role": "radio", + "name": "انتظار" + }, + { + "role": "button", + "name": "Sort by الفئة" + }, + { + "role": "button", + "name": "Sort by العدد" + }, + { + "role": "button", + "name": "Sort by وقت الالتقاط" + } + ], + "printObservation": { + "commentsHidden": true, + "filterHidden": true, + "pageBackground": "rgb(255, 255, 255)", + "themeHidden": true + }, + "browserLogs": [], + "requestUrls": [ + "http://127.0.0.1:4173/accessibility-rtl.html", + "http://127.0.0.1:4173/__state/accessibility-rtl", + "http://127.0.0.1:4173/__comments/accessibility-rtl", + "http://127.0.0.1:4173/__sse", + "http://127.0.0.1:4173/__state/accessibility-rtl", + "http://127.0.0.1:4173/__comments/accessibility-rtl" + ], + "externalHttpRequests": [] +} diff --git a/docs/evidence/renderer/goal-3-accessibility-mobile-reduced-2026-08-17.png b/docs/evidence/renderer/goal-3-accessibility-mobile-reduced-2026-08-17.png new file mode 100644 index 0000000000000000000000000000000000000000..a71e72a23e6f7d9db36311248cb0f62f47f71b10 GIT binary patch literal 39974 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%0DDk{Mv-tHeE(A8UP9JQ?JT*5UDZ-@jmp;vaM+ zf~e$A-GyOXB}9_=Mtil#uG5CP_bkWZzvd2pMz;os$pm1@NF-L=e3@F7I6osvS1lL?xH(Yp^8kc290Q=C09S2_J4pRtr7h_NAj@BwWFrd!7JN&_B!@$4q z&Y*EvtwGS&@ifnYXLTrY;_$8V?s9uWi7Y_u3Zh$m8i%D7pXk@xQ{Y3MHDvBGGM^yg zRr&8UVbQ}+@NeLtDxKM6N>$oIuVN6q&m+5l)B2+IHKqmIoUhi1$EDN6!y5uU_BQM> zUBe?DWfg0M*UX=Y8@F`?CY8jJTd)uR7JP=}wchP)&)lu=hi{jr;g!^a=|K&7NzpBR zOdj$Ldf-fny#~6ZAlVrvs4%jO9yTqh;XvV&?eLdQ;DDo`~6<5N&riG_1Jwou=opb zdZ!FBo8grfh&Xjo4!Pri6H@qJXy&Hd&G-FnUGI}WU9u<_tnz6`(+x9WKQNcguLNlv zq~iKk(`AhuaaqlT%L&J8&9Z&FYT4EWo$C8CzQe06q3{|xF)UeTcb2P@9OY@OE1%Ag zPSy<{nYY#Bn=};$B*{9@tv9x7KP6%b9&DG;;qA%JEylv@Ql{T7fYIB3zURf2?$sz4 zRErO=FQjKs!wnLJ#m*?ZK~H4xrt$o@y81{ur-~YXwE`Q z3I~mrwnP-gFC{3Ov@*1bW@c@CG#%az+#U>NYW8~t%X!nh?%XZ*6xY|4yFcNr(Lj2` zOU0B)OKjCufV=2+o9KYMoZ3x&K4EPLX64ymT{o$0CubZexfLs}!JjIb*yvb>F`|*I zur}FlHo6md(P(R0((m(%m(R133%~Ts7=u(l!ihyYwZ}y;o|Vj zp3CINl#uPKqTk#B#6})Z8Z=xaN=hy>DQSB%{ulXSa_7wLWk~9~AGE&`uPU`B>vb2ncJt%g!ETf!r10GBjKBiJsnVsJXuhWo#iGr`EcuTAlB8smXR)lxY!S9nC z6RP*AHbkAeZ>;xL_trXl(r%TW^zk(A$Y!*i!UjEB83Rs<1qYn7;qM*f&0Ljf71hLd zKly_jlXY($e=Y6YIP7O8%Mhv^&0UYIlBmiX1M(w*4_w*#sSRN*^`9LGUg-ykZIJ_k z#uxjBlA^7=_QN^+WiZ$4?K0jsP47*mAIPeX#Aim;RS)-By5R?(H`h0G&)=mG7B0Vg zI?mJYv)^*`iB1vr3qO%nLKwbiluhvmP(9-&b? zz3>#Qni3bUKZ;X7pTK(L0dC*j-d>Ii4Nv}a5E}GR?aX}) zA+ru|y%f_p6QT}Y3-hpM)#OGCFf!kcjl2jE&)w})o=97vY`~b&3LfEG$3iOs%fqG| zxg1?pQ}Lgu{!j?tuZZtzi&$HH=+1x?N>Ai*HO-0vd!j5EC7>QbiA)>|g@sSEstIU0QV$OJiLRZHKe#ur z-AT6j&b-PxYlyDP@*lCujhB4HCo~f|&YQs2(}H}eOdS(pl*e~F?0=R4JIsE(deH$t z>EJiLxe*-F9EZGbx0^qa;TRv8qlfz;1RW+uuJ+4!WAyKWYHx>iZ&pI_#+atDPt?x4 zcUddT4w4HDOKa_^mJw>E1dR^lt~GUHY!ooF1)Rklq_Xs@@_FZ19_ANEK78NK*`8O- zSrj93uz7wcieaW&4L@oF^{RX~<8r7=d(mMJKBABM*s0`h>)jXoXcud-MjPG~^Vrr0 zujr2XLG-BMlYyRt49|E(oq`2G5A*Y_0s>N1HynC`!M^zJ`!=Zy#g9fOj&oUBsWRmGU(6e%|(Jo3wa289j4jXfdKWno}{l;`buB{ozdB!S$j) zC&n@?yz5~X%Dy+%hYz6Cu4es^+vA7ACY=TZ?BVLcNXtlaSGf*7r28L=Doc#`%2l0# zWXS$exZA2Qnxs~S1J%uO8T_f0#j2N&_Se6T<;Imoy;>3N>IpPnYJOshnR9ovNw+H` zhZC2z2jM?HY!&iCgZ6lV|SjStk4Uyo~tJ>%)XXyIy&KO^%suC37`a3e7w_}#M8kRtOPeR*xM5GTe;T7 z86JSao^MK|B7nci$P5YsNzvnJ?`^47a{r^xzEs|D&Gh{9x-!O+4g{I!O{12?|Bl|Z zG$S>v36Pe`4@2{A(XGKt8!D)&nRF@5`tnP)+qx(+w$O%3=hELl%L;Cvd}1Gk?8v9H zA$vgBvdAs5Nro~EXYz_S1za+WFn6Yz;g5S4n+c4)eNi!Xn8xv<4)Ef)vXnzAzx$O| zPXEkeMi$Y&DDuL@3S@<-o+P-7qlJ@ZweFZegzcDMM(I!O8AI0Tby0QHwX3Ke7v=rq zp^{x|6*p5$Ll!_fRy4&$=TnG{N&xkfeP#%hhI09rXTJ}*JJr)gqegq>Gx=RER~*ao zR24+sEs&Q%6|FR>Y#Vf_eQ}`q{N@R~T5T3^$yAJt6>Seb#AN+A{2snH;&2=L0$1(_ zKe{~RX6RWeBBM2;OF%*7#=|1Nd%Tss$Ukp9cD7OInQ$zv7n}FPQ1*rYJ@hdG`Ln>hYzh49YZmehmTevl7G$vD+N<0y*$j=vI zQv|$|aq`Yh%^37ewj-aTnn`1rn%P3eMF=W+&6u08C4_|EMD3ZBjK-SoZM)L-97|Bd zZX$^=cl@p=_C;&wpufKf;4!3CWW?4SlTjJX_l?D6A?{M^JAUbhMGlGkqG>r52)DSd z4~VEwLtGwKk_#(l@qA@uJ_+s&R$`Tzv)$~mZ>=5~Bvv3K(-5Dto7u!W6k4ff4AX$e zN1w(E&YzDFI3c8IT-EGnc`_K=ns!N;GhFM=5e4-f9$k8@>t(f3=wL*VPPSY0)ZEvR4$LGrRR=C28!mUnW|Kah<_$6jYy zjN)2KtuSxjD)+oQ)WPi9sSon|vtx3pQp{dDx$(kAOA8Le_)lCcqgv!O%%&oa^M!$- znPnh8^S?X(Qaw`nW+z6Kwmhj-=1F{?{v23`%~=knUS{lJ$iEw=FsL<>&!cy1M5;Pl zsgk8WK3>6FU1FNB>`%o{AQ-!)DY13y-EkH?ta^q{HN@WQ-}ui@b)e?;Md>z*RX6@q zn%bgVmKL4cm*ZctZ~rb}El;VlHYji?RdRA_>Tt)Q-(lFiT#@M*O{LZ{Y8IdDB})1U zU6G%ypp3cNWacdP5>Uz;Ra}9(4LJ8Ey1Sgjf>}##1PR&(jRhQ(_*v=LF4lzK?&-DGJI*#NOlmIi7kyS5p0O{t(w@c-WBm9 zkl~lm?U;mi7fm|^+Xp~htzIqDzDQLn7}-JV-5Bs8;OuUN&bE$#wsgy?nirrA>QX)2 zU48Cq3k(qvX2}%S;uFkAMPX+qriQ(n4z=SNA7kLaapL2bvuM9~y4iNo$xGl3Nahw8 zCLocZkiE<)t1d<+pNWXHr(5*rFLcKdl|1V<(N0MMHV?R@eSSv5-jn~5$Zu1bT^^SR zTMDFWm1)gYuRUE9g7UA9Axif;w{*e3bZ8;U@if<{MFE>8Va{OugmpZJIWyp*_B3Zk zQ4Y)1@C3a*br#LaZ0XB?_bgLMvyUrVya77MKU*~zsC|~Y$7%lEDCs`rA78`FRx+34 zrd!}y_0cMHeqJlr3dlCa#4TX2b3|0dlsmng3#AW0-Gb^n=mnX}bV!F7u+l?zSTD+l}^)xloCg zsMi@y+A;|WT-delh%bGirqi2)7T*iV`|YW8kvB?=8-yduIloo$zf%Q0_xR_MUG|*D z!TF!9aNIvU%3pz93OXC-?l{oehA>D}x;_3G)H>KjxL2}u_dAs1CSM}KHnoBdR z)8|Zp#$5X1n`c!43d$!m$^UkD!vBwAQnEG4MhNukVEU5CMZUVJpg(@``4R&iXwl&R zpzSS#;*6p#QCx#t@Zj!F7y>5?NU23$UGxyRkiY z^o$g<#}S~^lmX}?C-x)twG9mrA~pZ%SeJ4*{5UtAWFOVY%)}!Yq&PwTteuwl9SW)% ziB(@nPO&%W(xPzbt&`0w@)Vq2x-P2>40ez=B=v1W^V!`>oBtsT)!?Whf9;g53l4Pf zCuk=t2k}rRIR}h;9rxZ0-BUq{tvZW5EGAyC@Nz=t$QFLc`35}ATz(y(! z->fIDE2+mosy#8(KldbnYAe|QvL0-oPJmU?g|i(OOe1w1dm}Ye0my??VEkFD_v)d8 zvAN!Rz2k6#ly9kmOBc+3KDgf(89G@)dH5T(EAL=f#`;g413{X)i=WOUASaC3H!fH3 ze7p^OzRMFYoj2b+L+*7}icYJfVvP+1B{1kUK8jpe6ni0*# zz$S2dIrzs|Wi3GI?v~6^wjg$jt?T6}IUzj=D^rnexrl4F(gV``WG5BJEsi7+iQ64q z@k`6kFjVtJjG<~^?|WCkF`c==Ol4}#%8DT%_~KQG&&}K=ZAJFBxZ*WL-lMDX{?JGl z_^GG*5~LYS(aF1bz4d|!x?MsqQZ~i6-!aQpc6fx~OI~F?eNHw;HP-PI zGBXDo>UAWkp{KBMaf@ZiM7DOD%F9OSRf`ofLWFz{%)-XPRL~_<{5huw#_`hw=xvV8 zDo=ykycyLjMbthKP%LAO?67W>={F*)$cC62-0(u80E<-K-s#&EA731P`sOXS2<1y< z{y0=wh3M(E4pwe>i(_e&0N(323DSL(H32K$uZclZEe19aR#!-G!X_bibJ^%mrx(Oc zA2GcpepIq^!zb|e985p;R2rOEOradoGVxpO4vUuU@M69b>USm0t2UGon33EmHXVCF zuHH8kGQ$so{=A_z3&_>&N#4^P#eFB;Z-NhBa{m5mwt9#5%vHC{D)y;zaBb}vT&=JB zduYOSWL~kkmg>e!ws!c=00Y$^&z0u1_6RcXBs1-qZS+JV;h*a3D|&)SOia`T8qFI) zq9;^Ara`+W3jommHx7ebq$BXvcUjw75eAAod1q%wG7`P>>9`8magCBbs?C`C{&F)} zqL_YpW-!^pD|Np?+%S#K{nrn6>hhj4BBEQhHF%ZW9$L#>h>FmCz2qUL8O)=q99$ z{&*F0IW!ZJLA@Yp)8|>!QH(QW7BP86XzzFC%xXGYtpE=X5j0{sFehE!yPof8 z2gV!B_q7afRr~erxQ(9sWs%(NHhbE2liqLt0#xzIh%T&IuLMmj>IVR32`UWaZ8ctU zGhz}sbT34I{@BpUyy9q!fsfi{_gwhoX!+7sJ`^Ru&r9{16CJjtD)P?q=A$QXvAIF$ zE!MGJB<;47)crv*(1<^!>lKl6-7b1F=VqH*RhA-p?t4RwxTvFWUa|Lt8*cF?k^sQ- zQO-EBo`M8>TdBf}8q1PWpCciuU#eup_m=$YxHw!7O~u2hfyDq{DiNoXY19Gb+j2&m z^>M)lO#Bxx`=iWod@cn?Yq@-_AUOZ-jk5dA6q~^!`nHr`deTzr^J8G=$?Hbeoqq{4 zivY#B=^VI4hsVfh^hU3Z#kwo~cwRjD|CU}-J#F8&Vg2|f zpI;8WH>O%OpS4M5#Tk-VY4==NNTh_;S-#Jm(4oKMKdc|@D|T;ubQg%;fTg!NFr(2H zYfe!5av7eFS|OL5k8ZrvmwjzT-L_+O4a?wK;AS})DY8oXTM;C91kR?l zd44McC0@BVt#~pANTfy8yrbh)COKNv`aL0n9H9xa#{DTb3HPtnbt14d&D@agWHzVK;QT${nXeJUT z_j&^1*W2kOxC$!~6gK*`OvPfg?0#-QKRxcmVby6mVt&?F!>XAzugU#6xc0K|86!WL zqEM5d?#s^QQ`hsrnmGEAcwObqW$S2n`Qb-+gD?3v18L!Tl==|SicZn6*v;SOJ&44C z04)3_FI740<5SaB2(dH(7YEDr>tVvlg8%DC#HPx+ce%{1u54bmt|CQeeB7pKL$I8w z2kx@x%$XJ(YV#+0lp|mlBNu~;%oSX2D;@#uSXfJ8@FE1Bz4lre+a51@6J;i1)G*j+ z+bfc13M?AEWHul;ip5RmtNASK^qP-t&edYISh*?pv*=H(w1ude)-!em0}7tn?RW=k zr-Q)Xu&ub0ZX>dfMY#MJtSt0cQnE>(l0xk-z(@R43M6FT3*7dz+L1sZ7p< zy|RdX4krr$IVPy0Bvof!(?S&DuI|KzTRosoQDe2%vGv=VaGcd9R-&_z8P+wQ&(A64 z%oH7*Ge;+eMiHrcOjCqfcp+GY3~^v6(8 zADJt%C-7mhN-R(JcgA0ApN{tZ_WjwM9}?kngdAn(LURtejO50K#N>&^4293V9AB!O z6p#763*Vc&a;o&xgFKIeg|sPYIyQ5T%DVbMXD7mI=rr&&+od`)3x1XPu<7&DU1ixZ zP(g0?r2*jZ%cpMDx#6kuAM7~y=O^^k*ETlD2bmViEJOEVDZD zBiq*f_9CeUo=lX(!7Wk3cSY}l@_LTo{t_WhHqb<JFE){zy5c4#@{z3Q?n(PJ(h+1xD6QV_E@+!3Yj@j+eEnIHUzR=` z*-2htx}*b3ov!*56Hbd zAZ)PCoUY9Y2{Dxnwi899Yf&B*qTJF`wl6hI|lCg13O}U!K z2&QwAjh614#ro2~gWO1cw>uz)0ExWr0G!V2;1js48|`}oY7Ye>r5+A}3aN-hO0t!y z%Hkk{P47ObcqE3baQO>M8DQG;O920@+Z6q{hdi3iEx&O4H>Z<>-gf3uRy6E*?}wH* z2}YZshJ(WzF;}5W+38-dNFNN3={2@fz%AM>Ge+a?7Ptrg(eX8+gO{3(+~tGYu0-J4 zM@pX!RnkC;kZ}se^E55Xhe0UBX}&H^VaWrBrFlmXAV7GXc^PB3_g#s0J;DdQX{4`0 zyxir+_Og^M1UPiKFuiQKuxfSt88DvK>K5pvmeFw7p07eAB-9O+S$oN=^s|kBsrAD& zbAe1)ZCTmna&m~L42EM^ouHBjUD%{cP7* zMX$8B5{52xSKW}(BooAoet~#|8o}wLCh~Z$tz_hIDz;pj26OTgtZd)P&m;Vs^6DM0 zhVuf>_-R|^w-MvZ1eJ0YjK|yv&#NBkwOW(L?4Bj#$097nB#{lsWhldbOVKggPbqZadbCwpAuYWgmkXhUuBNse~dDgxp2Kcp?P@gSkJ|8 zh~m8)p$XJl1@XT|uf1R4Xw}-5-d|#wW`4)T)E4df!rN6@K$@BF;xIG;t6PuNM+oT= zPM<*k$EhZDOYr47A75Y%<#U(dF9mywyrD=@|NT)_f$sZD4+d%7{WbqV<>OTjhNBZ7 zzg?#JXlI`)^!{DB{86hPOv@j zu`NnAmaUsliIf~J;K!n>8zAqgLtgM<#L=wTpDUFdBzn?*l8U0qeATb~<~FsBkw(IH zk9-<$?Uvl9`_qLpV!|BRu+;nJO=T?v!vodhbh281vxdNB^+Uv9COr<2_3@>=q-t3N zHQ%u_d+)Esxz=5SsGv~F$^)b zB-{%_%5pU47!PNro^k5e+;4LSUcG_%0@2dcg<~q;RjcR3ifn3~iO=_i$7T^ORE$ZY z5<4$jaI|gr1`~i6wRJ8{y$(!KHB|IuG-V=c9DY)$G%?HdZxpGo8FF#zB@00x%{xVq zv~3@Poj08VHs5X1S3z4h1n0CgOwN5BH}v`9QvC_URAfi_bIifjZcae)qK=3={5_-f z-vLhUjp~-}fW_`|;QgAOy#Bqh3LPoJ^MlH5U#@%fb13n4sAE9E&cdf(tT^4v4dry} zDHS>|#6eW{@4-`c%f3O~NT`#In{0$XK_4~$VmnDolVKotrXg?6`p({mZbivXJUS-= z%?2EG8ksGf{_r~F>vm=ePQLg~Qjd2@?$fTf2Kv8m&+>S}e`+HW_E-*S4FIHfo63xB z{`R3$h(b~;Rx@IediXg?8y2Me> zRSwmSHQcU6*!#@HS2CvbebRr$b@jS{B{~zu{N8DF z3YEz_$Nyds25qqbgYrWCdR=zpD7>6=7ie+(TPXGU6YUc;%p|N8L=g6W)q?%s;+YUp z(>%s^yCIV;l1#|(edK*W4ip56Bb67JaW}G3gy;eIdE*%?ec8Uh97fRXKReYvSMmvS3l9JJ$jImW^~aEAmnmFDg<@Vk@H z7&{1urzGOG<+YLR>(K+~-#c5KWEaon(S$B7zLSJH1_sp1?HAkkDqng@Azut@L@X-2 zlR%!@n(*Uo*b|(!u2Myj7*;`cxF;2EO<#AGo zrhyt_Cna*c3+#XOZvW2`2>(MH`F{gyMse8M)6mdBZj~uS{i7hgY?`vd6+5*6`%Dya zQGW_v$V2M_9!tzMsiZW**?+PT5fTw%!M&~i`GZOLk%*{ktREgqNY{mfRdTEQdrVBs zca-vx>LM3=68HIItDREb5xAyLLHVJ*1k zX_Z3MbSu^9qpJAjtYx5KI!3u9G#S(R|qC@?GYk3PL zj=agQJbLZaV|4LteojeEa1PBt-ot{bCfyiPRvM{ey)7l%pZ!y6y(Z{1gMNs`O$I&n zw5?^D?Rpg7T3TjIZ3Bz_K5mLN$cztxz|fe@U@pKG$ltPJ-%6-`JrtrmOaDD`n6#z0 zAFU6x*4&|^Z=$RbLldB^?LMHPU3D4H^s^}V(kddKgJt2eYN8fC@p&869A_4Qa)@p#BR27yN>7 zQ1ReTK&f(;@F|v@s_H|2n`b9*xqAF`~ix%b(*~ zG)-mmC&R@|8i|2rM>T@33I)>!U_ZE~E)iB-ICC=132%#uZ*vAdAP#6nD$z_V1IbDF z12*|KolU72S*bqAV%ugG>>xlysQ**oSxJIQ{tfr~LW^EUtHsHHjU-RqNqRl>IETOD zTt(hH=Fnek;t5AHTHxJsW{4L=dvx0=G`Av74NXfG#-FrND`TrCNQ3E;n9~6f)v<-u z57YmccZ1aZdm`*hRw4G6n?wa~w$ruF4ju9#$n4KN<=GmdS2LbovVxwauZ)bZf@Q={ za5^U=1#`|b*!|p*NviHP<@7doZZFmzuU-?45}94kXzVVNZ1t*U;P?4CH6?QKUl}+F zJTJ!Hv95MUU`)Fg-PePr$^nbSKj!J+*4L+llX9>y7&2xdj({E`-^!o2cXXDnvVx z&y$hBwLjRVXPlR2GaE+pQE)M+HD7YTP$kp(>sdMgxX8&E$>i)akMW)vLvQ21x4jN{ zw+_1kkenFh!M0_q6HXHV%NU6~*y!Nb*AO1Z4Y!yi;hP zHJ|Hm-kd=;xPo*0;ozS}XIyPUaubymo54eb=CZyOj!-e3LZa`vwVYt5Co^`pS1@5Z zHM~TgW422*;)iQj{*RdyW@^D$kd?tR;jzEUY6R-*wk0~y_IqS}@Fk#>OHw^{)S;Cf z#0)@;;dYU9mV4R^i5-oEWz+SSh|#rTHsCGiAne?T>jsydKXt0o#I1?_U)rrvv7C zA7^C*PI4W`5e8|XElrUT#tq6xA-Zf-08fJ?8`c{s7>)KajXJ=g()-tDO@$k|Oy`HHgoBH&_ccmv6=&yeFEsRQ_GnNzgk|NJ!GqQ zl+KSpAI_%rS}b8>`vg9>><(vB5EuEo%TR^8az%_4%g%4LzTdwhSzm2Ve%1*ky>*&x ztbq?Rw4@{;M3xG<=Rg(zr;yWAKBhD$(ZF;65CgAv2OC<8{8bWX{w|HmVYai+oq#q4 zN-UU!aN_RFRpVx+zd-l^UN8Br$2s49tXB!fR;Bh|jrwd=Gf9Ms)>c(ELS;!4r=Kt0 z*W1=jKv{sWb-(Y!b&Ir$i)ock4)F~{Q;IkJe~I;V1IV1&Pj4NRN$RT+mXQCVs2Jp( zN6+u8{^}INgh5va%L`24bUke=8Yl85&NGzpvSgeG2}PLrgh2wvM^*N(gIUIOI4$nQ z4%%2K~^Ufnwj z7tkknJP!|kz59KscI|oH_vQ56QKAnHbmQnMNYBbyMJ7i->)@Tejgr=gTpunT6 z_!q&R+cFE`rCVuS(sRYw(jD(}v=|!`6tb4ifwqI~U{#CR-G$&F&T$Pacy9lJ1*ld`@b7oi7C<{p!cA_} z*s}+J{Zl>y%ea|P6^C;s%(Ovy`hIZwmYOEtO_pacsgcRJecN-{lkbO$#el1gV$k12 z_HuWtZ~+M%=S{3%@$M-dTJE1Lq-K@P*Hy`SIj#}VP}xV0=ha`v<-HtFO5b}zx_v~R zEJYO3utq=^vvs67=ak!-Jdfw|h332{l%z>}??a=d?BpS|dMe1!@I-Dx3uApooBy)Z z;a}u%{r(r&NsA*C;}m8_XE)5C$Ks8btObgH_K|9hW?|5(Q2Ewu_MN2qNKqid?s=_YmT6oKt$Qn_U0RJlQhj zJRlP7+MT`%diqR__)db1M;MZ~5!5PgZzzT=s8d~JjTT*tZ^A4#yI9LztAs?{FYY?a zE*j_R^_rO>36W(uMzo&q?ZZq0=k9{V8a|R)Y^QIz9CNUj^>}Vp#E?YK8wK_OSt3*Mq|IZ?#Adp^w|%FhoRSZ?(T6E%eVeo~R)^s8nAfMF`1cq>zVTikP=1>vPq(=(k6*MN!fyOHCJIUa-#pL**MC^ed6H(k0jGg z<_Vvyw>KvyV!`13TM{%Afa_fYw0Z9~5avElRl9JII#q#O*f)&L{bT{{x?Ip&(bk3k zmEhvl?d0=GsXZ*^uOUZ_jds`Gob?{Sq4jFs7lHWIm(HN`jK{lU*dRv|i)x3)A}tUh zVO)|dJUsjgYKwD9T3Q4fq_%Z#HGr!O*3gJaCJ5qo=E-E4Ejy8gY)#w46)lA{#bPN& z&Fj2+?jEeqtJf7{d$DV!1{Jia>HeI~;70=n530=Jw0(vN(m0amlKb9)*%{Gw;v3W7 zDY?11d3hV1eqQ)7sTmnjP8@uE1SsK1b8wWP5-vZF$NT(iN_0&lv*`^VJF2{Fp32yE zj`C>r<*Vj;7Z&h?zlaT-m252~WVAZh zMV4Ndd>19E^)+5<4S*z?_35+0uz^~S$iDkYm#Pgnm5ffopuTetvopJ6EjqY!r^$SQ zy7s2ZfgLarkJTML9~mkrlIDoVbAK=wGEK&_P+g|z#brpE*gy*Kn}Nb&cn0j*BTyWR zI!=e>d9&a)hr;ll30!th)h}WfhiSj_SyrPg;bGUgnRE;K{qu{dYaXFO&1dOTlIU(M$A3&T|}twq-%(Zef83{e}WA zU?~2I*m7w0T;k=t%SrXp1OHR!<XHhDY3bJXk-D*m)%(#be2F7kKE++M$$=QvK zjsAYPV93Fw6Atc`T>P3jj3>?AdtGKC{b+5qQOK&l?GB$8iAxju7WY#(c^n#jxt8gAPF_$Q*>YHj9$Qel~4{ zOqWjZi(NoeV;2{%kt7w_ z$+&>}$*-6R4e9r)+L|q6PRFes9jM+k zepk${V=OyH*F(Wq!~Rdx`9;4@*bu|9qnrAO24}_M>%#d_p;)n46slcU)k0$WyU*v( z{hU~y#B2FwX{Ez*?L9Y(5BG`_eD=DRO5%hW%l9|#TU0(uQ*>|7z!8+ZZanVR*szWK z8cU4vzkHlqE8WLoir7x9?&S7{mDG*J$V#&2CG@5mQ(vl8>kD`e|EN)o0}zeuyrHYmu19u0Whv)n4fGU3hB@^B zOZOy7hgc;tVN(uKm&-x$XR&oP;W;|X=)||jKPY)dwRD}*j8^L%jT>Ni`g8q8zygD4 z!6+XD^;YJrTCuBlC>>?p;QpZYp52dj>pv3-HeT<7)7`MF)&Fy0rOE$O1u`7{ z@ffC3LrzKhg4H|L+KwmOrT(>4Nq$%j4^v3r$}S}kI1-6ulNjQ77t*n}n#sf4fmxjD z1|)-h0(+b+E=tP{1J6Ep-Ff@(lKq0tl1@%bf(U%pfBLnt(PEcKyzUmRUw}a_(|QitXXmj*c>j=M~3Q#lQek%D-BoaIrYTBI+E+D>8RQ zcv%O?^`_;kMVuK{huq@P%)-e0a=$s&pS87m)#*(L#|W`7E2zMPW1^!gi-*7b?=O8P zhEgsO;acvsZ`KxLsMt5vOIW@=4N?7EvwYY`@#@#Ye{9v{I!` z+X@ecE-6l{67vjrKaPc|2ubx9W_yI6xb7it*eQG5yGov0;+UI|hCV#@$ofLs>w9`o zeLE)ob41v5f%P8>V99EVJzpIPeWKQcOVeMZ#2=|p50Omjpz+!Vh$@mcc(ezkuaE!_k0p*w9p z4uS0z5{Zg@1oU7%MFMCGseeIYUgoO4n~q%xH_hY5dQ3VM(5MU|nV+R0`R{ERR-^h; z54$Ey+lc+Qq2$8iIpzWukj0GBEB`{A2$noqL;VmHrC?(OoOO_|ntdKF|;{T;~Av0g{BH zzDF}>`swivjw|x?E{V3l_jpoS2^GV(#9x$Bz&QQe{n7%>Ap88a>YCD8OUUy7_1pz# z^B;QHF$%oc$4WdLHwZ}D8GYaugwz$0wM0Tb3#*}Ua+)#)F^tWc7KHa^j_JzyQYh8q zEse1Vl-@cLA>RRQTwibEXF<3OwNonEIu@QIe_fL&Woas~bicCTUM|UbfR&0p1wS?Y zS!r$OQJv&<4WKLRm#)mX846k@YYCkHK45pB*F~$jOPtDCK?$s}HK-bzJt5yaK%SW)-et!6V#Qp%5L%xZfZ zgI!M!A(m9QgqbUkTa1&e(MTbYUP7#piNczeF00pbfI`;J$4UW?`37EUVt8m-gh>6x6GVSFN# zdhkDdTxot+bHdV3H4?<)==|vW_L9?9{p0>cVCDKNS@U1Wa_<+K>rSry2n2Q70?&rK zFN#lAG(S@((BXcLK80_YD^fv6yWAw#48R|;8vfQt%h2zccU#T%Y5)>S$jt8=q(B1z zK2)-|n~VEpi&Of)XN21UXm``>Bt8Ag0)<=u#!{#L*#=v@nVU-({Z3S;TkL@>ziw0a z^MxkWEbD}~Xo}%@c{|z{pgcX!`zR9wXXzXQNueR6Whh6~>S3}Kz1j+1YRxvmR-m%8 z0$!h$PK+|`soP524*qolStTdF(z2eaYkU=3YTX{s_7y5JIz(fmr)THm1DZ8ZcQt>r zUGZ$dM=XW)ZvEVGzM%<`%3VmXfUTF0h)psV`<)?U#HX~!onulswgWib+zw4 zXU^p<$Hdd=u4CyH8_vtq8?L0PNcgx0h0D`FfB%q&`{NAM8!NPXY%wmVb-qsZOV(k7 zBR%Ng2BT?HTf1vLXo!+eJ0he3AG;)3%@PwmE3-Zo#HVt)(R%NSw$D7Sb#P0aLfKV% zT9h580TIS3v&a4$exeiK$mjnuw{)wwe^%=7ZE`qGvv_zyM+(tVWaE_7na17g>sIF6 z6Srscg4x>F2<)kd9_o}aSWU%{r%I7_YOci20}_J;#% z;Aji&S0M=VEaVjjB5o(x|3Z`Fe~$tDPwL_SU#+wMBC-HUInmC){CqkD%d@|UJyF81 zKVi6f;X5CVdY%J!MuqTgi6K`niM2rHws0nd@6UMLyL|Zr_L-M8pgmtYyfb^3H^^x& zT(g~SDQ#o8F8|W-D7;=>T+w*HFg*b?HtT)9x#|$TX@O7qb8n&B>tax&GrbB|{A)(| zW{t5xBLU8ah8w#K6gxJ<5#T`^K=Cuh6|aYY|CWFs={o7~JXYQ2{6GNh#40YuYhrz( zfSV7~GtkZQc8^jG2hx#9$H7pKD!{$RPQrtas`z)I3^f;eoy^$lF)BI~b&EOqFhkLD zTp}Y9jJ{qw9`{6_B>viM>vD1X5&XjzilM_4;^cU3sY}{9#6K-}0+cr5@?*BZxvjp- zUqdA9=a+#ZPuKpe6h{Y^vO9c{1}}3|>hM ztzsDh7-vjUl~soqe~$v9Erd-E#@_m?yB#l$@9_%<)lK+HSp&h-*Eh@MG*jki7z7bB z7weO1Aje`{zPp;&>lVm#l9NR=!Z_G?E9}BlQBow6*jqXHBMb!pComDVy@{9Zlti*R7!smCz6UBJ)i44>qtsDNR`7?lA z%p(5-MnCbJBqUl}_&ngxv_;nQY)reA)P2yaPcKHAkuZ0Nz|9<~6I!>e`e8lREB)$o ztq6UyaNP9qr)#oPnc#gL4c@ZGxI(G%#%}|iFA5qI2auBGgUo(uS@ES0H3KHnv+A?E z=bcqH6(>DDFg=}^eB?bost$7D z=K}c_j3=r)s8ikdMh8}l@amMbf$~&mxC99sUsbBn{B>BjIj*_#re;Aj6{Xd$KPBK=yb7YIo=WI1tuKt!}G!*|3Kcf;dL3+u7> z;fp|1=lOA)Z|f*`7A5aht^4;Y-rRnz9VY@$GUNtCP9>hPvf+82~)};6}NXa{Vy#|*&Vh}z9 zqKDZtnf*^Z?^z(YeCq=b9zpYkQ0}!>DuivuX(YaC!gavxUR31;N)HJFGRdtuui&L`vj+?ZyI;g#KWjsR&P)O7Ix>4(hluKb$j90m1+<%djfR;LG>w7m~6 zTU8L+n}aWwTpoiaJ5w^`pX5@%_Cp3q29O@rVbG|m{s9+tKKCZ6H+x0ytm6@&VP`cc z>$v#650VRTMXtCaXBwz~a&^N7yVuXev!rbb_;YJ>TdqMJLg$r#=G*wp5YuPZ0%$o` zz%*HR_*Qz^-*`3;ye*Vla%)j*{#M&wb@%gDV+y(L<8TGYM($Z$I6SJY@$9A(rK|^n z64As6kb9mRkvaVsT59&^nG};ZqfGY63vF+|t`f?!yFdP_C3o@iy$UfD$XT%v@sIpg zG3iS4Wk7l@{s8G4Zv?Kpo2N_d*#q|*G9kp zpy@Z?OBIPSTdyjM(xNu*20HwtggbZB3F4W~R2zhY&l{#5m)BO2xn2i=a8=E%Xr>mF zmB*^bP^r~*i@01pNc}y}9bt-Fyy&`q0TOCIwmv;;1F`2LX(30^!*l!=D&@pP%!I`- z_WBF10$^0?jnhQ^_tDG@RC%AZfs=cpIq7o$9J8<1<^}5c`z+ohT27G3Fm9M$$J0Af zw?`|H{4RkujjOn=rTc65`ufI;c{8yfK-(Y6gtZ6I6AlBL8gGSgQkHD83^v38#2QXZ zMr*55$v^?()JvVaVbEd!tBJETZCwzj!Oi%yzakmZ#@s(BUX${d5$P_K1gGBMtA4X4 zaRo!>K>Z1b+1n7xXzDF9E3h^XRUr?b1lco&dzT68!hfjkjt+ai+h)%uA}~DNIuW&R2F?X3{=>2 z5?4e;xxq5y_R-i%Qr91*8rx0{{vlgn0kS+~s5=kNC?=$=s3uuH+2Q^GJvGw;zLiv( zX-L%42>I}VP)FN=XmAj2^TSGl*r=J+ED-LZJa)Pv(fsQ-h3|y7j@c?E6f~2s1du;0 zE;a-nPS=kYjW9V541||EL@Dw|LRs55Ziqx@6pv6-J?6BGrGC(%$qeTi@J z3wlrXgyo3FinJ>o+M%Jb$I&b%58aVark;O3Wn|Hkmv>P*HHMq6F>FUKJrn!{^^uw9 zY!FnhKN)c$@pO@@^`;5CCTDOVoMnICA%DyGO%pPic?(3vNd~bHnvS+6-8aY&W5v-N&`{&QDsaRSgN~mE& zfBtOZ^kac45EO*Ya+Qz?pp>sC3Wvvq@P)MDQ?lO5MkM5c8rTyup+9>ithW&g9DZ7* zvoLZOz*ban%3pjZj;B$`P58$QO3inYOIJD);)(@bPf4*M9Q$IWu7f1fqgm3|V zeDqPqlP)1rQ~0UqU<>Agkfjvo9#wg0s9{UFozn4BF1sU+t$ZLJ;Sf#|?^Ym7Bav)U zA+&Ap7h>CCXqv2$q{6~&&3&_vm{4N+-vEu*S_x<_WIQfd$RJ{Qgp%S(UrNFP=NQkNt&V=R>MLe86T=(ptqXi_(f=~JHl;vco?afgioKW zFhUxeQIW=wnXmvGwl4{r@jdftS<}o6KgPk-+T32Q8%I)(9_k~yl^XI}5n-lbK_I04 zQV#0noM|FTA5i1k!?JazF1&`g>v`^u(s;*RtunIq?hE@K2?WsPjKF=vFYLHLNsn#t z^U&Ky;?X55B#5TgYD=>ipfL=Hf2)EMhcLu?Kc>9t2G%0j4zkbq7Rt?3k|?coHzdMk z!ikN3ReMwQf#PxdXu~5xZIfEv$(!A9aYEBU0re-whe2Me6hgNf8x8o!wL@#``@dmG z6cX}>t~CwSZ5@i(qZk>8jXa!*RKDUG=JYJ| z9|D~m;F8+J?$BBqp()eN+>#2{T6LapIae9xcyhGaHxCRnu0ysJ}CD`imD z^4$9fRFdXlUZzqRTh>46J)l#8x#VJ8)3P@u{|lcl;|umTe2hhhCEBykaN_%i1h^Uz z2;*N2Y`;>c9;hQv6nUP>k;p@y*6&-`5z!1oQJfQ#lU#FSH`77`B@t2@dKPHF5{5`S z^SOt0PbetbKVb4Tex=E=^31I^LJj!~&-}V;EJsYPU?G@-#=hqkh{OFt_{Ln3KhY9y zoc^KmiVH{&vVE&bRcM+RBF{7Ie4nI~95v38u<>Ic5f<`MwG1duD*b^KrdfVXHSsI#xY?rvjTN|Go??RW!tE&~lBQLPBGqnhT5Y2>y3i z=ODzus2;2QnLty+_!H#N^WVL@N~obKDaO`hl!z>4%g@8f_}$*aPvpfgAbCM+$c=29 zZy{3+MMEXG8)&LEP}&IL6|!32Y{KXH z9N)@nPo2*A@Jav=?^l!kybp>+;8E=MXMRTBJT;n1j>84H=0zo>aOjIZaDU+V2(h%% zqX&V1?8<$627)xOql*{oSIs-WLkc9Pi}NPR4tP$;aWE6aK**_$IzFxdLC!}tez*wi z)mC17JI7C9e(668GnY2Ii2Ob`ODCCxtiV|G0F$IfQ#{MoIpr17YPO=` zT6#qA_~JNxzEeP$96vEFUk`&0-y=LgDkTtuUDZG5wGV@+vUa*6H?_7``r>E`g?SIay%YU)14BxIlt(gdf4HQZzh z^tSaT2!Sr2e&*l#-#D_>6Qv*iYe`@ATErZ7tWSoNzuF+%<&RTX6|v^Tt#Hj|&aIj9 zd3t>eP<+UXzVi^O<2ZUQ<;K;Wgd=}9MtXBQz94Si(VUsJaP?U36pGXZQS7#ro$$Ea z@LijcM0!^h4 z)N)LIes&_c9p*S?c2wUqu&o7o z>47E&eKboZrIQSP+SXn_jjwfhOn)^A&u&o(MO?miXBwcCH`2@E?DoQDbS!``)LQX3 z0mS8=CU;zjOpjXa>~&?atIKm8yL}D{)+kh)p6A$Ox7@Bw`bS@jFtEeOrzr7j0B7G2 zXf#Lnn4Kz+Q+{msCTxpGXtBAoleVp6R&8f>eaiIq4mP<-7j8`O3;7Ti)+uwpNUK|; zr9F9-4ffX5auhT|SUhOSb4#1N@KIy6yMi zo+f5$zElb8uWCKeIp0{ZDk>)Q`F9u~Q^4{P7Jtj4>m2QDI*MHUd(l(P9<(-5^M({+ zwg0pFiQe3Gyq0vJGfH3MVw*pBX(4noPo!lY3;hAn%dSbNLM=2rPqWe(xn8X z2k8N%D+iDwO(_y;Kz>6ns3Xu_XTBTvooljr-oqo`UGukRd1ifCxg158NVX@}-4XJmoo+*z_5H?qyqf z+j%A_X_Onv8`@?E-NL0in-pR;b-=uzADLcNKXZ&Q3>rKdfr(kzbv^^fn&c>++E(dQ zHYLoDJs>@_U{bShj8uo(|6b<|aj9A^_jxW*z5Hq9-ybSi3|DWjUrv=qrmd46zw)1U zHT5=1hI&7OyxgCi4R3at0kZ`^_e;OM`6QV{Xy;)K(AaS)?cE8x+j!Xqc!Kpg~su|Mcom>3`&U$oEokjn#OvsFDZYeE{;bidxv`;SD)ahT1|tZqe_n#oN&k4{)H_tc@w< zfO(mukPtvbL3?c7(TLnI9b^HvL>w%3K-JZ5-B{svJRVDwMFIve)^Y?U&GJ6}9RlWXz}RjUAb-Y#8k-)2x~ptu<*?2oWBIy|R(IGiy` zNkdZow}~ewA}#;;`4g*H8Vj2G|JJ(B!kqUs+NeBl0cDi}ag-+s{?Y?cLBDWI>Usgq zo76ay^5N#7GhC?zRTfa?yPd^TKlhQMOJuT92KZO0rN&R$hw_iGsL^9KHTnvr8^Q7> zRL156Nu^ug zE(m0I4o#N8U3*rLlW)wS`T)R-ybn1})wtKL9tPFRosoVWN}+kF{UiNYY8bRT&&YN1 z&GY3%uvHo)C!w*VjB`P=HI!)3&7a+Aq_Y81c=+&ZlrVhnL_8m-gb3WzcY{N6^aFqE zCl(7hPH=2Bdd+0a>ggFrE;x9F>V4`x+9%kv;0W;OZ`ktadGtGBk?Qy`Ci3U3`HDvb~)>CGa_Q9@1%5mZCRc|#AAjMom0T7Nbq`EZFB+t4u>po@g$^}r>O-;7fVsc)H8Z=oXX^;6 zsO!JS-7R`q@-@|9Q-70w{QU222wvkp%)OZV(9I(nZ0dUFy)f2pQVtQ=F(86;;&Vu> zsSVsj_^bKId5-Qa=x)23MJAb03uC<|xeO;O$XdF&>em!nm1Z^f+OOD0Z5O08RxGmT zWpsFZ8Vi*%6d1!rp7!miK|K>e61y)@n}xy1Vue<1n-?J^pa*X%r`qE(-7b*-^!Dna zQq3z~o4zN8rw<5~bV%ozpmw!U)*-Q*#Isg>vke3g{?{94Gu)N;9lC(XRP;A$~_!*^(liMiF%WE(Vo$&LtyWA_u^5&Tj;oVi? z%*9LV@j#Jhe36NbKdROdYS*tgKOSvK@3vL|Qi+#4@A(<(Xh90^>%M;c$DdR2_4$LC zhbnLO`w00<$4l$cL({kCXIe2<>|4dLUrqxf_8fX-#0Yaqe3%!%pIx6N_4c7#9v4G} z6go(;Rdjakmsto}pd*e}cxpcrS}cA#qWbc!drrinp55KugPwx0%crHy>QP%uDuj~d z;CgOiT9e7bvKzz2P5;gZ)#ChNUF)31>cw;C(=AC!$#dWN^_oNVvb{M5&XfJWHvXn} z`2FCbW0}IFr7#>5!r)BI-v*76@6TMB&uUjKt9{G}oJGgWSY<>3fF4(-e81Z28eTVt z5XQ@~dv3}3)EE1+mTrXw)kbxe3{+7!)6MxN^s!HkRTqdRT%o7bPQSFQ$2a-PL`9uB zZ!Wv496voF+#a3G=DRTvHhz5(9_VfqD|Nb3@KC9GhVfgbVP`mURQ`1x4)R@g>i7ve zUXBKL3kwu!dRr2-NJ?8{+-1ZiC3T=JEFt@|bz(mkw-NB<2}Xs}`|MVS8|ygkqy+iazVMvS$xOiS4wwsx+vI%EtWO0(3xZmB@V4;pK8haT#rS3$!rRd4;$ z?cd96^~(+A;CmWxU6-oQgH%J9*DC$mo0&nb+m#c8Y~8c(mb<*Qm%@VhZ)jh2$qO94 zp6#akL(~#Oe|=WzXxKu6!pg$0m%Ag|Z{G4q;ca#aY)+H(d#c58*mb#(X8}GgMX~qi2dHCt9TpC-8qo3nh;JYe&!rSS6ib1$IOZFFw514_mugmuOC}oA>^q zq&^Jy@Eg7{Eo<)-!G}_**oW&2V^~ z^juhKszkfn-apXEmGz2n%uaE*yZe*tGweSTVJ)=iTgGlKQ*?R0NVyh9s`QUiXAwEQ zKN&i230|TiB)_D6AF94%cVg&Zv3+p+BV}OO%TFb6w=x+XU$sR2hgU7N8=sK(I`Z00 z%RIpR)ph9JohEidQ(Uu50xlOCM2Gc;vcb@1F-*WWb{U#B|Q- zv!)l^N|pakwxOl6JWVJ`qzsA_DouOQ#{mUMy%Z{28UsEhm+i+~z7XkF-7rplR$nRT z)kt;|$7|I;uSidKQ{jbKQgXg0Uz#3d9r>yG`axe_+e{X+F36s!`Kb)S;Z}KKQSwHv@H#%@O z9Y4}L;uJu`w8DPIjS+vn(sP-a)%!Gdv*JuR}wLQYZ)&*N4FO{fas#l@wa z0W11i)bhDfs!IsoOFt8dql8%-lZE;NcTMQA`D6aQE|(ko3%$yIp-*J zyrZRDU%`2wo!znJIB(MhKPF7+_A+4%HqPRqaKZ&FH{yqmYcDhxWCrhT%90~zCb$1i zT8K)<=d=+qPFivjv~@c}tF9YH>9W?HK`Sc%O2ZlbgqU+b8Ctv*h4LrE&1p(}G5sBk zFQzq^z@}LOK+YF^i84$ckF@%1R#Lx82zr{5nW3jBHN7otgspsw#m9b|ik+`)c!e#3@3DSzZ`6r87D<-!j%cXGjW8G6K`W|H0S6tQ{Sl zm`=5dj!LfINSz$;ZOa1z-V;?bY2>mv#yCCEJ6Da51NZgos2Sy$M%NDIHY;or8*wW#l58&kRn9IhNN?aX?Iet3) zk?ZoW6ZQ5Q-@r&_)CI9{86d6$2V7KJZBkPF^Jn_|XRuhT!5dCFdEAXMCyt%qbgX?fd>%nBDN{2ZUR zO7}FL30TRN%hzc+_YQ(5>XUY82+xrA?e6%8y&o2N&p0?Zt-PkqK-r6}HH_J9+n%8L z`+7ReJo+almRyFYCqR4|yM&CiXF6kPo+}+5u*ZZV{>dTeB^@o0JuRa_`EDrg`CgOa zl=|j};F{hSC)?lT9O&0|=mRl!I>^^UC4%gm0TuWN}KE$^Wmj$`B zsne3z?XMl1q1=eCnY6Ig&d@(K`&L#K`A1yNxAAyPirkL+<_j5WoHRc0rLrUdz|&;| z_+U2cX$&+zwzL>$GadIbo6>_=Xi#Kp*<#qU)2px>=97<@*T>#ieJvkAr1x8-=iaYm z@=!S`+-rz~JQDXgdx*lexSDyA?%kro%na>$94r<4`-{RYv-pNnDliYUc=4oOUTAG? zL7K^D)=!8-wC6(sh~jS3^{WJ*YFCI=@1_!@KxYnqnB6)Dj8Ur~!{O{QDfO5u)U(ZO z9~6(jdL&*R*l2BU^aTvJcoB>HsMGSEKi!K;0mDYj&Je*YVmvc_xdd*Y3qZO%D1RFt z2B3_fRbo*?bFvi|ne&l_p^rUz7mD7@GR)Sgq!0ZA0vr;;iNhfcBo$v89KtF?x->Z^ ziF=fpzDIutg#feurdDtf&X$VnOolzIu+JV#H?#DPl+lnbm&~ppvV!5?0HD>V5KadU*?>{w=v`16MP)z4X}Q`LNos~*?Bo$%KHyA!W)eC|1&ks! zQC!A^!_7$&ufi%5KRiUlUSc(k>4>C*UNn*mdkG>TAt4Y9PrlX?A1MFRdXtevlP9+1 T_tA0^-;fyUn&?zLc^>nB#e)0> literal 0 HcmV?d00001 diff --git a/docs/evidence/renderer/goal-3-accessibility-zoom-200-2026-08-17.json b/docs/evidence/renderer/goal-3-accessibility-zoom-200-2026-08-17.json new file mode 100644 index 0000000..895d5fe --- /dev/null +++ b/docs/evidence/renderer/goal-3-accessibility-zoom-200-2026-08-17.json @@ -0,0 +1,248 @@ +{ + "capturedAt": "2026-08-17T16:25:48.147Z", + "browser": "Chromium 151 via selenium/standalone-chromium", + "fixture": "http://127.0.0.1:4173/accessibility-rtl.html", + "requested": { + "viewportWidth": 1280, + "viewportHeight": 1000, + "colorScheme": "light", + "reducedMotion": "no-preference", + "zoomPercent": 200 + }, + "usefulContentMs": 816, + "keyboard": { + "skipFocus": "skip-link", + "skipTargetFocus": "artifact-main", + "decisionTrace": { + "active": "hold", + "checked": "true", + "selected": "hold" + }, + "tableSortTrace": { + "direction": "ascending", + "label": "الفئة" + }, + "commentDialogFocus": "artifact-comment-input", + "commentEscapeFocus": "comment-launcher", + "commentSaveTrace": { + "active": "comment-launcher", + "count": 1, + "dialogOpen": false + }, + "themeTrace": { + "label": "Theme: dark. Activate to switch.", + "pressed": null, + "state": "dark" + } + }, + "observations": { + "animationDuration": "0s", + "audit": [], + "chartSummary": "ترتفع الإشارة من ثلاث نقاط إلى خمس نقاط خلال يومين.", + "colorScheme": "light", + "computedColors": { + "accent": "#a8a6ff", + "page": "#151a21", + "surface": "#1f2630", + "text": "#e5e7eb" + }, + "dir": "rtl", + "focusOutline": "solid", + "headings": [ + { + "level": 1, + "name": "مراجعة الإشارات" + }, + { + "level": 1, + "name": "مراجعة الإشارات" + }, + { + "level": 2, + "name": "حالة المراجعة" + }, + { + "level": 2, + "name": "اتجاه الإشارة" + }, + { + "level": 2, + "name": "سجل القياسات" + }, + { + "level": 2, + "name": "القرار التالي" + }, + { + "level": "2", + "name": "Comments (1)" + } + ], + "horizontalOverflow": false, + "interactiveVisible": { + "comments": true, + "filter": true, + "theme": true + }, + "landmarks": { + "aside": 2, + "footer": 0, + "header": 1, + "main": 1 + }, + "lang": "ar", + "locale": "ar-EG", + "progress": { + "max": "4", + "name": "تقدم المراجعة", + "now": "3" + }, + "radioStates": [ + { + "checked": "false", + "name": "نشربعد اكتمال المراجعة", + "tabindex": "-1" + }, + { + "checked": "true", + "name": "انتظار", + "tabindex": "0" + } + ], + "readyState": "complete", + "reducedMotion": false, + "tableCaption": "سجل الإشارات", + "tableCount": "2 rows", + "timezone": "Asia/Riyadh", + "viewport": { + "devicePixelRatio": 2, + "height": 500, + "method": "Chromium device metrics: half CSS viewport at 2 physical pixels per CSS pixel", + "preZoomWidth": 1280, + "requestedZoom": 200, + "width": 640 + } + }, + "accessibilityTree": [ + { + "role": "RootWebArea", + "name": "مراجعة الإشارات" + }, + { + "role": "banner", + "name": "" + }, + { + "role": "main", + "name": "" + }, + { + "role": "button", + "name": "Add comment" + }, + { + "role": "heading", + "name": "مراجعة الإشارات" + }, + { + "role": "button", + "name": "Theme: dark. Activate to switch." + }, + { + "role": "heading", + "name": "مراجعة الإشارات" + }, + { + "role": "note", + "name": "Note" + }, + { + "role": "heading", + "name": "Comments (1)" + }, + { + "role": "button", + "name": "Resolve comment: Page comment" + }, + { + "role": "heading", + "name": "حالة المراجعة" + }, + { + "role": "progressbar", + "name": "تقدم المراجعة" + }, + { + "role": "heading", + "name": "اتجاه الإشارة" + }, + { + "role": "figure", + "name": "" + }, + { + "role": "heading", + "name": "سجل القياسات" + }, + { + "role": "table", + "name": "سجل الإشارات" + }, + { + "role": "status", + "name": "" + }, + { + "role": "heading", + "name": "القرار التالي" + }, + { + "role": "radiogroup", + "name": "ما الخطوة التالية؟" + }, + { + "role": "image", + "name": "ترتفع الإشارة من ثلاث نقاط إلى خمس نقاط خلال يومين." + }, + { + "role": "caption", + "name": "" + }, + { + "role": "radio", + "name": "نشر بعد اكتمال المراجعة" + }, + { + "role": "radio", + "name": "انتظار" + }, + { + "role": "button", + "name": "Sort by الفئة" + }, + { + "role": "button", + "name": "Sort by العدد" + }, + { + "role": "button", + "name": "Sort by وقت الالتقاط" + } + ], + "printObservation": { + "commentsHidden": true, + "filterHidden": true, + "pageBackground": "rgb(255, 255, 255)", + "themeHidden": true + }, + "browserLogs": [], + "requestUrls": [ + "http://127.0.0.1:4173/accessibility-rtl.html", + "http://127.0.0.1:4173/__state/accessibility-rtl", + "http://127.0.0.1:4173/__comments/accessibility-rtl", + "http://127.0.0.1:4173/__sse", + "http://127.0.0.1:4173/__state/accessibility-rtl", + "http://127.0.0.1:4173/__comments/accessibility-rtl" + ], + "externalHttpRequests": [] +} diff --git a/docs/evidence/renderer/goal-3-accessibility-zoom-200-2026-08-17.png b/docs/evidence/renderer/goal-3-accessibility-zoom-200-2026-08-17.png new file mode 100644 index 0000000000000000000000000000000000000000..cc041580847bb54e080ee5fa9bb2741f1760b5ca GIT binary patch literal 79616 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 literal 0 HcmV?d00001 diff --git a/docs/redistribution-inventory.json b/docs/redistribution-inventory.json index 2ae396d..b0787a0 100644 --- a/docs/redistribution-inventory.json +++ b/docs/redistribution-inventory.json @@ -210,6 +210,33 @@ "attribution": "opencode-artifacts contributors", "status": "approved" }, + { + "path": "docs/evidence/renderer/goal-3-accessibility-desktop-2026-08-17.png", + "sha256": "c0196d055b365b98e5ddc92d7719180956bacccfc5950693d4402b76d8bbc9e9", + "provenance": "repository-generated browser capture", + "source": "examples/patterns/accessibility-rtl.md rendered in Chromium 151 at desktop width", + "license": "MIT", + "attribution": "opencode-artifacts contributors", + "status": "approved" + }, + { + "path": "docs/evidence/renderer/goal-3-accessibility-mobile-reduced-2026-08-17.png", + "sha256": "67a442c420494c9ed75e53c64ffd839ee0a59622f802b5b813f43495bd0af80f", + "provenance": "repository-generated browser capture", + "source": "examples/patterns/accessibility-rtl.md rendered in Chromium 151 at mobile width with dark and reduced-motion preferences", + "license": "MIT", + "attribution": "opencode-artifacts contributors", + "status": "approved" + }, + { + "path": "docs/evidence/renderer/goal-3-accessibility-zoom-200-2026-08-17.png", + "sha256": "b1134713a6c63f842f9bd267a182a76cf50c50a53ea3732bd596c52881495645", + "provenance": "repository-generated browser capture", + "source": "examples/patterns/accessibility-rtl.md rendered in Chromium 151 at a 200%-equivalent CSS viewport", + "license": "MIT", + "attribution": "opencode-artifacts contributors", + "status": "approved" + }, { "path": "docs/evidence/renderer/goal-3-design-tokens-desktop-2026-08-17.png", "sha256": "84ebacab7a6258b611aa73973ad9ca9f1da29eb88b8f4498b4d5e64111f78e84", diff --git a/examples/patterns/accessibility-rtl.md b/examples/patterns/accessibility-rtl.md new file mode 100644 index 0000000..8ae2e8e --- /dev/null +++ b/examples/patterns/accessibility-rtl.md @@ -0,0 +1,42 @@ +--- +title: مراجعة الإشارات +description: RTL accessibility, locale, keyboard, chart, table, and comment fixture +lang: ar +dir: rtl +locale: ar-EG +timezone: Asia/Riyadh +--- +# مراجعة الإشارات + +هذه صفحة تحقق ثابتة لقراءة المحتوى العربي والتنقل بلوحة المفاتيح. + +> [!NOTE] الملخص واضح ولا يعتمد على اللون وحده. + +## حالة المراجعة + +- [x] اكتملت مراجعة البيانات +- [ ] بقيت مراجعة التعليقات + +```progress +{"label":"تقدم المراجعة","done":3,"total":4} +``` + +## اتجاه الإشارة + +```echarts +{"description":"ترتفع الإشارة من ثلاث نقاط إلى خمس نقاط خلال يومين.","xAxis":{"type":"category","data":["الاثنين","الثلاثاء"]},"yAxis":{"type":"value"},"series":[{"type":"line","data":[3,5]}]} +``` + +## سجل القياسات + +```table +{"caption":"سجل الإشارات","columns":[{"key":"label","label":"الفئة"},{"key":"count","label":"العدد","type":"num"},{"key":"captured","label":"وقت الالتقاط","type":"datetime"}],"rows":[{"label":"ألفا","count":1234.5,"captured":"2026-08-17T15:00:00Z"},{"label":"بيتا","count":987.25,"captured":"2026-08-18T06:30:00+03:00"}]} +``` + +## القرار التالي + +```decisions +{"title":"قرار النشر","questions":[{"id":"next","question":"ما الخطوة التالية؟","options":[{"id":"ship","label":"نشر","note":"بعد اكتمال المراجعة"},{"id":"hold","label":"انتظار"}]}]} +``` + +يمكن إضافة تعليق على الصفحة من زر التعليقات عند تشغيل الخادم المحلي. diff --git a/examples/patterns/dashboard.md b/examples/patterns/dashboard.md index ed897e0..e413444 100644 --- a/examples/patterns/dashboard.md +++ b/examples/patterns/dashboard.md @@ -18,6 +18,7 @@ A week of deploy failures across all services, with the two hotspots called out. ```vega-lite { + "description": "Daily failures peak at 14 on Wednesday and fall to 5 by Saturday before ending at 7.", "data": { "values": [ { "day": "Mon", "failures": 6 }, { "day": "Tue", "failures": 9 }, { "day": "Wed", "failures": 14 }, { "day": "Thu", "failures": 12 }, diff --git a/examples/patterns/funnel-analysis.md b/examples/patterns/funnel-analysis.md index 0c34829..92250a1 100644 --- a/examples/patterns/funnel-analysis.md +++ b/examples/patterns/funnel-analysis.md @@ -18,6 +18,7 @@ theme: report ```vega-lite { + "description": "Conversion holds near 33 percent before falling to roughly 24 percent after the March 19 release.", "data": { "values": [ {"day": "Mar 11", "rate": 33.4}, {"day": "Mar 12", "rate": 33.1}, {"day": "Mar 13", "rate": 33.8}, {"day": "Mar 14", "rate": 32.9}, {"day": "Mar 15", "rate": 33.5}, {"day": "Mar 16", "rate": 33.2}, diff --git a/examples/patterns/incident.md b/examples/patterns/incident.md index 6f978d9..55ac92b 100644 --- a/examples/patterns/incident.md +++ b/examples/patterns/incident.md @@ -27,6 +27,7 @@ icon: 🚨 ```echarts { + "description": "Server errors spike from 4 to 63 per minute at 14:05, then recover to 3 by 14:30.", "xAxis": { "type": "category", "data": ["13:50", "13:55", "14:00", "14:05", "14:10", "14:15", "14:20", "14:25", "14:30"] }, "yAxis": { "type": "value", "name": "5xx / min" }, "series": [{ "type": "line", "areaStyle": {}, "smooth": true, "data": [2, 4, 41, 63, 58, 47, 22, 9, 3] }] diff --git a/examples/patterns/plan.md b/examples/patterns/plan.md index 4d9ba37..eb31bef 100644 --- a/examples/patterns/plan.md +++ b/examples/patterns/plan.md @@ -22,6 +22,7 @@ Move session storage from Redis to Postgres over three deploys, keeping both sto ## Schema sketch ```mermaid +%% summary: Sessions contain events and both entities carry stable identifiers and timestamps. erDiagram SESSION ||--o{ EVENT : has SESSION { diff --git a/examples/patterns/portable-mixed.md b/examples/patterns/portable-mixed.md index b7efd9f..f7d34fd 100644 --- a/examples/patterns/portable-mixed.md +++ b/examples/patterns/portable-mixed.md @@ -14,6 +14,7 @@ controls in one strict-CSP file. ```vega-lite { + "description": "Readiness rises steadily from 61 on Monday to 92 on Friday.", "data": { "values": [ { "day": "Mon", "ready": 61 }, { "day": "Tue", "ready": 68 }, diff --git a/examples/patterns/tune-controls.md b/examples/patterns/tune-controls.md index f9f5834..cbd7dd4 100644 --- a/examples/patterns/tune-controls.md +++ b/examples/patterns/tune-controls.md @@ -9,6 +9,7 @@ Drag the sliders: the charts update live. When the shape looks right, copy the v ```vega-lite { + "description": "Interactive controls change the frequency and amplitude of the displayed sine wave.", "params": [ { "name": "freq", "value": 2, "bind": { "input": "range", "min": 0.5, "max": 8, "step": 0.5, "name": "Frequency " } }, { "name": "amp", "value": 1, "bind": { "input": "range", "min": 0.2, "max": 3, "step": 0.2, "name": "Amplitude " } } @@ -27,6 +28,7 @@ Drag the sliders: the charts update live. When the shape looks right, copy the v ```echarts { + "description": "The twelve-week series rises overall from 12 to 40 with several temporary dips.", "xAxis": { "type": "category", "data": ["W1","W2","W3","W4","W5","W6","W7","W8","W9","W10","W11","W12"] }, "yAxis": { "type": "value" }, "dataZoom": [{ "type": "slider" }], diff --git a/scripts/accessibility-browser-evidence.ts b/scripts/accessibility-browser-evidence.ts new file mode 100644 index 0000000..74191ab --- /dev/null +++ b/scripts/accessibility-browser-evidence.ts @@ -0,0 +1,257 @@ +import { writeFile } from "node:fs/promises"; + +interface WebDriverResponse { + value: T; +} + +interface BrowserLog { + level: string; + message: string; + timestamp: number; +} + +interface PerformanceLog { + message: string; +} + +interface AxNode { + role?: { value?: string }; + name?: { value?: string }; + ignored?: boolean; +} + +const endpoint = process.argv[2] ?? "http://127.0.0.1:4444"; +const pageUrl = process.argv[3]; +const screenshotPath = process.argv[4]; +const reportPath = process.argv[5]; +const viewportWidth = Number(process.argv[6] ?? 1440); +const viewportHeight = Number(process.argv[7] ?? 1200); +const colorScheme = process.argv[8] ?? "light"; +const reducedMotion = process.argv[9] ?? "no-preference"; +const zoomPercent = Number(process.argv[10] ?? 100); + +if (!pageUrl || !screenshotPath || !reportPath) { + throw new Error("usage: accessibility-browser-evidence [width] [height] [light|dark] [reduce|no-preference] [100|200]"); +} +if (!Number.isInteger(viewportWidth) || !Number.isInteger(viewportHeight) || viewportWidth < 320 || viewportHeight < 480) throw new Error("viewport is invalid"); +if (!new Set(["light", "dark"]).has(colorScheme)) throw new Error("color scheme is invalid"); +if (!new Set(["reduce", "no-preference"]).has(reducedMotion)) throw new Error("reduced motion is invalid"); +if (!new Set([100, 200]).has(zoomPercent)) throw new Error("zoom must be 100 or 200"); + +async function request(method: string, path: string, body?: unknown): Promise { + const response = await fetch(`${endpoint}${path}`, { + method, + headers: body === undefined ? undefined : { "content-type": "application/json" }, + body: body === undefined ? undefined : JSON.stringify(body), + }); + const responseText = await response.text(); + if (!response.ok) throw new Error(`WebDriver ${method} ${path} failed (${response.status}): ${responseText.slice(0, 1000)}`); + return (JSON.parse(responseText) as WebDriverResponse).value; +} + +const session = await request<{ sessionId: string }>("POST", "/session", { + capabilities: { + alwaysMatch: { + browserName: "chrome", + "goog:loggingPrefs": { browser: "ALL", performance: "ALL" }, + "goog:chromeOptions": { + args: ["--headless=new", "--no-sandbox", "--disable-dev-shm-usage"], + }, + }, + }, +}); +const route = `/session/${session.sessionId}`; + +async function script(source: string): Promise { + return request("POST", `${route}/execute/sync`, { script: source, args: [] }); +} + +async function keys(values: string[]): Promise { + await request("POST", `${route}/actions`, { + actions: [{ + type: "key", + id: "keyboard", + actions: values.flatMap((value) => [{ type: "keyDown", value }, { type: "keyUp", value }]), + }], + }); +} + +async function focused(selector: string): Promise { + return script(`const node=document.querySelector(${JSON.stringify(selector)});if(node)node.focus();return !!node;`); +} + +try { + await request("POST", `${route}/window/rect`, { width: viewportWidth, height: viewportHeight, x: 0, y: 0 }); + await request("POST", `${route}/goog/cdp/execute`, { cmd: "Network.enable", params: {} }); + await request("POST", `${route}/goog/cdp/execute`, { + cmd: "Emulation.setEmulatedMedia", + params: { + media: "screen", + features: [ + { name: "prefers-color-scheme", value: colorScheme }, + { name: "prefers-reduced-motion", value: reducedMotion }, + ], + }, + }); + + const startedAt = Date.now(); + await request("POST", `${route}/url`, { url: pageUrl }); + let ready = false; + for (let attempt = 0; attempt < 120; attempt++) { + ready = await script("return document.readyState==='complete'&&!!document.querySelector('.chart canvas,.chart svg');"); + if (ready) break; + await new Promise((resolve) => setTimeout(resolve, 50)); + } + const usefulContentMs = Date.now() - startedAt; + + await script("document.activeElement&&document.activeElement.blur();return true;"); + await keys(["\uE004"]); + const skipFocus = await script("return document.activeElement?.className||'';"); + await keys(["\uE007"]); + const skipTargetFocus = await script("return document.activeElement?.id||'';"); + + await focused(".decision-opt"); + await keys(["\uE014"]); + const decisionTrace = await script>(` + const selected=document.querySelector('.decision-opt[aria-checked="true"]'); + return {selected:selected?.getAttribute('data-option')||null,active:document.activeElement?.getAttribute('data-option')||null,checked:selected?.getAttribute('aria-checked')||null}; + `); + + await focused(".th-sort"); + await keys(["\uE007"]); + const tableSortTrace = await script>(` + const sorted=document.querySelector('th[aria-sort]:not([aria-sort="none"])'); + return {label:sorted?.textContent?.trim()||null,direction:sorted?.getAttribute('aria-sort')||null}; + `); + + await focused(".comment-launcher"); + await keys(["\uE007"]); + const commentDialogFocus = await script("return document.activeElement?.id||'';"); + await keys(["\uE00C"]); + const commentEscapeFocus = await script("return document.activeElement?.className||'';"); + await keys(["\uE007"]); + await keys(Array.from("تعليق لوحة المفاتيح")); + await keys(["\uE004", "\uE007"]); + for (let attempt = 0; attempt < 40; attempt++) { + const saved = await script("return !!document.querySelector('.comment:not(.comment-empty)');"); + if (saved) break; + await new Promise((resolve) => setTimeout(resolve, 50)); + } + const commentSaveTrace = await script>(`return { + count:document.querySelectorAll('.comment:not(.comment-empty)').length, + active:document.activeElement?.className||'', + dialogOpen:!!document.querySelector('[role="dialog"]') + };`); + + await focused(".theme-toggle"); + await keys(["\uE007"]); + const themeTrace = await script>(`return { + state:document.documentElement.getAttribute('data-theme')||'system', + pressed:document.querySelector('.theme-toggle')?.getAttribute('aria-pressed')||null, + label:document.querySelector('.theme-toggle')?.getAttribute('aria-label')||null + };`); + + const preZoomWidth = await script("return innerWidth;"); + if (zoomPercent === 200) { + await request("POST", `${route}/goog/cdp/execute`, { + cmd: "Emulation.setDeviceMetricsOverride", + params: { + width: Math.floor(viewportWidth / 2), + height: Math.floor(viewportHeight / 2), + deviceScaleFactor: 2, + mobile: false, + screenWidth: viewportWidth, + screenHeight: viewportHeight, + }, + }); + } + + const observations = await script>(` + const root=getComputedStyle(document.documentElement); + const progress=document.querySelector('[role="progressbar"]'); + const chart=document.querySelector('.chart[role="img"]'); + const table=document.querySelector('table'); + const caption=table?.querySelector('caption'); + const radios=Array.from(document.querySelectorAll('[role="radio"]')); + const visible=(selector)=>{const node=document.querySelector(selector);return !!node&&getComputedStyle(node).display!=='none'&&getComputedStyle(node).visibility!=='hidden';}; + const audit=[]; + if(document.documentElement.lang!=='ar')audit.push('html language'); + if(document.documentElement.dir!=='rtl')audit.push('html direction'); + if(!document.querySelector('main'))audit.push('main landmark'); + if(!document.querySelector('.skip-link'))audit.push('skip link'); + if(!chart?.getAttribute('aria-labelledby'))audit.push('chart description'); + if(!caption?.textContent?.trim())audit.push('table caption'); + if(!document.querySelector('label[for="component-1-filter"]'))audit.push('table filter label'); + if(!progress?.getAttribute('aria-valuenow'))audit.push('progress state'); + if(radios.some((node)=>!node.getAttribute('aria-checked')))audit.push('radio state'); + return { + readyState:document.readyState, + lang:document.documentElement.lang, + dir:document.documentElement.dir, + locale:document.documentElement.dataset.locale, + timezone:document.documentElement.dataset.timezone, + viewport:{width:innerWidth,height:innerHeight,devicePixelRatio:devicePixelRatio,preZoomWidth:${preZoomWidth},requestedZoom:${zoomPercent},method:${zoomPercent === 200 ? "'Chromium device metrics: half CSS viewport at 2 physical pixels per CSS pixel'" : "'native 100% viewport'"}}, + horizontalOverflow:document.documentElement.scrollWidth>document.documentElement.clientWidth, + reducedMotion:matchMedia('(prefers-reduced-motion: reduce)').matches, + colorScheme:matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light', + computedColors:{page:root.getPropertyValue('--page-bg').trim(),surface:root.getPropertyValue('--card-bg').trim(),text:root.getPropertyValue('--ink').trim(),accent:root.getPropertyValue('--accent').trim()}, + landmarks:{main:document.querySelectorAll('main').length,header:document.querySelectorAll('header').length,footer:document.querySelectorAll('footer').length,aside:document.querySelectorAll('aside').length}, + headings:Array.from(document.querySelectorAll('h1,h2,[role="heading"]')).map((node)=>({level:node.tagName==='H1'?1:node.tagName==='H2'?2:node.getAttribute('aria-level'),name:node.textContent?.trim()})), + chartSummary:document.querySelector('.chart-summary')?.textContent?.trim()||null, + tableCaption:caption?.textContent?.trim()||null, + tableCount:document.querySelector('.table-count')?.textContent?.trim()||null, + radioStates:radios.map((node)=>({name:node.textContent?.trim(),checked:node.getAttribute('aria-checked'),tabindex:node.getAttribute('tabindex')})), + progress:{name:progress?.getAttribute('aria-label')||null,now:progress?.getAttribute('aria-valuenow')||null,max:progress?.getAttribute('aria-valuemax')||null}, + focusOutline:getComputedStyle(document.querySelector('.theme-toggle')).outlineStyle, + animationDuration:getComputedStyle(document.querySelector('.progress-fill')).animationDuration, + interactiveVisible:{theme:visible('.theme-toggle'),comments:visible('.comment-launcher'),filter:visible('.table-filter')}, + audit + }; + `); + + const axResult = await request<{ nodes?: AxNode[] }>("POST", `${route}/goog/cdp/execute`, { + cmd: "Accessibility.getFullAXTree", + params: {}, + }); + const retainedRoles = new Set(["RootWebArea", "banner", "main", "contentinfo", "heading", "note", "figure", "image", "table", "caption", "progressbar", "radiogroup", "radio", "button", "textbox", "status"]); + const accessibilityTree = (axResult.nodes ?? []) + .filter((node) => !node.ignored && retainedRoles.has(node.role?.value ?? "")) + .map((node) => ({ role: node.role?.value ?? "", name: node.name?.value ?? "" })) + .slice(0, 120); + + const screenshot = await request("GET", `${route}/screenshot`); + await writeFile(screenshotPath, Buffer.from(screenshot, "base64")); + + await request("POST", `${route}/goog/cdp/execute`, { cmd: "Emulation.setEmulatedMedia", params: { media: "print" } }); + const printObservation = await script>(` + const hidden=(selector)=>{const node=document.querySelector(selector);return !node||getComputedStyle(node).display==='none';}; + return {themeHidden:hidden('.theme-toggle'),commentsHidden:hidden('.comment-launcher'),filterHidden:hidden('.table-filter'),pageBackground:getComputedStyle(document.body).backgroundColor}; + `); + + const browserLogs = await request("POST", `${route}/log`, { type: "browser" }); + const performanceLogs = await request("POST", `${route}/log`, { type: "performance" }); + const rawRequestUrls = performanceLogs.flatMap((entry) => { + const parsed = JSON.parse(entry.message) as { message?: { method?: string; params?: { request?: { url?: string } } } }; + const url = parsed.message?.method === "Network.requestWillBeSent" ? parsed.message.params?.request?.url : undefined; + return url === undefined ? [] : [url]; + }); + const origin = new URL(pageUrl).origin; + const report = { + capturedAt: new Date().toISOString(), + browser: "Chromium 151 via selenium/standalone-chromium", + fixture: pageUrl, + requested: { viewportWidth, viewportHeight, colorScheme, reducedMotion, zoomPercent }, + usefulContentMs, + keyboard: { skipFocus, skipTargetFocus, decisionTrace, tableSortTrace, commentDialogFocus, commentEscapeFocus, commentSaveTrace, themeTrace }, + observations, + accessibilityTree, + printObservation, + browserLogs, + requestUrls: rawRequestUrls, + externalHttpRequests: rawRequestUrls.filter((url) => /^https?:/i.test(url) && new URL(url).origin !== origin), + }; + await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8"); + console.log(JSON.stringify(report, null, 2)); +} finally { + await request("DELETE", route); +} diff --git a/skills/artifact-pages/reference/components.md b/skills/artifact-pages/reference/components.md index 49842c1..bb0dfb7 100644 --- a/skills/artifact-pages/reference/components.md +++ b/skills/artifact-pages/reference/components.md @@ -1,8 +1,10 @@ # Authoring reference — artifact pages The `artifact_publish` tool takes `markdown`. Frontmatter (`---` fences) sets `title:`, -`icon:` (emoji favicon), and `description:` (gallery subtitle). `##` sections become white -cards on the page. Republish with the same title to update in place; `version: true` keeps +`icon:` (emoji favicon), `description:` (gallery subtitle), `lang:` (BCP 47), `dir:` +(`ltr|rtl`), `locale:` (Intl locale), and `timezone:` (IANA zone). Locale defaults to +`en-US`, time zone to `UTC`, and direction is inferred from the language when omitted. `##` +sections become white cards on the page. Republish with the same title to update in place; `version: true` keeps numbered history; `open: true` opens the browser; `expectedHash` guards against overwriting unseen changes; `force: true` overrides the credential scan. @@ -18,9 +20,9 @@ unseen changes; `force: true` overrides the credential scan. | ```` ```progress ```` | `{label?, done, total}` | progress bars | | ```` ```diff ```` | unified diff text; lines starting `## note:` become annotation rows | annotated diffs | | ```` ```copy ```` | `{label?, text}` | copy-to-clipboard button (for handing text back to the session) | -| ```` ```mermaid ```` | raw mermaid source (not JSON) | diagrams: graph/sequence/ER/... | +| ```` ```mermaid ```` | first line `%% summary: meaningful equivalent`, then raw Mermaid | diagrams: graph/sequence/ER/... | | ```` ```decisions ```` | `{title?, questions: [{id, question, options: [{id, label, note?}]}]}` | workshop rows; answers read back via `artifact_state` | -| ```` ```table ```` | `{caption?, columns: [{key, label, type?: num}], rows: [{...}]}` | sortable, filterable data tables | +| ```` ```table ```` | `{caption, columns: [{key, label, type?: num\|date\|datetime}], rows: [{...}]}` | captioned sortable/filterable tables; dates require zoned ISO values | ## Data honesty (non-negotiable) @@ -43,6 +45,7 @@ unseen changes; `force: true` overrides the credential scan. ## Charts ```` ```vega-lite ```` / ```` ```vega ```` / ```` ```echarts ```` fences take one JSON spec. +Every chart spec requires a top-level `description` containing its meaningful text equivalent. Vega-Lite compiles at render time and runs in the CSP-safe interpreter. Interactivity needs no custom JS: vega-lite `params` with `bind` render as sliders/dropdowns; echarts `dataZoom` gives pan/zoom. Title the finding, not the axes. diff --git a/specs/changes/renderer-accessibility-i18n/change.json b/specs/changes/renderer-accessibility-i18n/change.json index babd0da..5ecb0d1 100644 --- a/specs/changes/renderer-accessibility-i18n/change.json +++ b/specs/changes/renderer-accessibility-i18n/change.json @@ -3,10 +3,10 @@ "id": "renderer-accessibility-i18n", "title": "Complete renderer accessibility and internationalization", "lane": "standard", - "status": "approved", + "status": "implementing", "affectedRequirements": ["RENDER-06", "QUAL-04"], "currentSpecs": ["specs/current/portable-renderer.spec.md"], - "currentSpecsUpdated": false, + "currentSpecsUpdated": true, "approval": { "by": "aaron.zeng", "at": "2026-08-17T15:02:07Z" diff --git a/specs/changes/renderer-accessibility-i18n/evidence.md b/specs/changes/renderer-accessibility-i18n/evidence.md index 2f4ce3e..aeb21a3 100644 --- a/specs/changes/renderer-accessibility-i18n/evidence.md +++ b/specs/changes/renderer-accessibility-i18n/evidence.md @@ -3,11 +3,17 @@ ## Requirement: RENDER-06 - Validation: Phase 2 requires WCAG, keyboard, zoom, motion, Unicode, locale, and RTL correctness. - Verification: planned semantic/automated checks plus real desktop/mobile-width keyboard and assistive-technology matrix. -- Result: pending implementation; supported-browser and manual screen-reader access are external inputs. -- Evidence: [@manual](docs/roadmap.md) +- Result: semantic/unit checks and Chromium desktop/mobile/200%-equivalent RTL runs passed with + empty audits, no horizontal overflow, no console errors, and no external requests. Manual + screen-reader access is unavailable, so the certification cell remains open. +- Evidence: `test/accessibility.test.ts`; `test/serve.test.ts`; + `docs/evidence/renderer/goal-3-accessibility-2026-08-17.md`; [@manual](docs/roadmap.md) ## Requirement: QUAL-04 - Validation: user-visible accessibility cannot be certified from source assertions alone. - Verification: planned retained browser versions, console results, screenshots, keyboard traces, and manual outcomes. -- Result: pending real-surface evidence. -- Evidence: [@manual](docs/goal-runbook.md) +- Result: Chromium 151 retained keyboard traces, accessibility-tree roles/names, light/dark, + reduced-motion, 390-pixel, 640-CSS-pixel-at-2x, print, console, request, and screenshots. + Manual screen-reader result is explicitly unavailable rather than inferred. +- Evidence: `docs/evidence/renderer/goal-3-accessibility-{desktop,mobile-reduced,zoom-200}-2026-08-17.{json,png}`; + [@manual](docs/goal-runbook.md) diff --git a/specs/changes/renderer-accessibility-i18n/tasks.md b/specs/changes/renderer-accessibility-i18n/tasks.md index 13e72bf..c63e038 100644 --- a/specs/changes/renderer-accessibility-i18n/tasks.md +++ b/specs/changes/renderer-accessibility-i18n/tasks.md @@ -1,9 +1,9 @@ # Tasks: Complete renderer accessibility and internationalization -- [ ] Confirm proposal validation and human approval. -- [ ] Implement semantic summaries/captions/labels, focus/keyboard behavior, zoom/reflow, and reduced-motion support. -- [ ] Add explicit language/direction plus deterministic Unicode, locale, time-zone, and RTL behavior. -- [ ] Add semantic/automated checks and real desktop/mobile-width keyboard/color/zoom/motion/console tests. -- [ ] Retain screenshots and available manual screen-reader evidence; keep unavailable cells explicit. -- [ ] Update `specs/current/portable-renderer.spec.md`. +- [x] Confirm proposal validation and human approval. +- [x] Implement semantic summaries/captions/labels, focus/keyboard behavior, zoom/reflow, and reduced-motion support. +- [x] Add explicit language/direction plus deterministic Unicode, locale, time-zone, and RTL behavior. +- [x] Add semantic/automated checks and real desktop/mobile-width keyboard/color/zoom/motion/console tests. +- [x] Retain screenshots and available manual screen-reader evidence; keep unavailable cells explicit. +- [x] Update `specs/current/portable-renderer.spec.md`. - [ ] Run repository validation and archive the packet. diff --git a/specs/current/portable-renderer.spec.md b/specs/current/portable-renderer.spec.md index 6967bc9..704e9a2 100644 --- a/specs/current/portable-renderer.spec.md +++ b/specs/current/portable-renderer.spec.md @@ -41,6 +41,15 @@ root. It checks every path segment, opens regular files without following the final symlink, compares descriptor identity across a bounded read, repeats realpath containment, detects MIME from bytes, and never fetches. +- `RENDER-06`: pages expose a skip link and header/main landmarks, Unicode-safe anchors, + meaningful chart/Mermaid equivalents, table captions/filter labels/sort state, progress and + decision state, visible focus, keyboard decision/comment flows, reduced motion, narrow and + 200%-equivalent reflow, and print behavior. `lang`, `dir`, `locale`, and `timezone` are + explicit and deterministic; RTL direction can be inferred and numbers/zoned timestamps use + the declared locale context. Missing equivalents and invalid metadata refuse publication. +- `QUAL-04`: semantic tests and retained Chromium desktop, 390-pixel dark/reduced-motion, and + 200%-equivalent RTL evidence are green. Manual screen-reader evidence is unavailable in the + current environment, so accessibility certification and the packet archive gate remain open. ## Limits @@ -75,6 +84,11 @@ malformed or unknown markup. - `examples/patterns/portable-mixed.md` plus `docs/evidence/renderer/goal-3-portable-assets-2026-08-17.md` retain the real Chromium offline mixed-page and loaded-font observations. +- `test/accessibility.test.ts`, the served-bridge regression in `test/serve.test.ts`, and + `examples/patterns/accessibility-rtl.md` cover semantic equivalents, contrast, Unicode, + locale/time-zone/RTL output, keyboard state, reduced motion, reflow, and print behavior. + `docs/evidence/renderer/goal-3-accessibility-2026-08-17.md` retains the Chromium 151 + accessibility-tree, keyboard, desktop/mobile/zoom, console, request, and screenshot results. - This evidence is one Linux/Chromium observation. It does not certify a supported browser, - OS, mobile, or assistive-technology cell, and it does not complete the remaining Goal 3 - accessibility or performance packets. + OS, physical mobile device, or assistive-technology cell. The screen-reader gate and Goal 3 + performance packet remain incomplete. diff --git a/src/components.ts b/src/components.ts index 7ef7d47..cc2c2fd 100644 --- a/src/components.ts +++ b/src/components.ts @@ -1,4 +1,5 @@ import { escapeHtmlText } from "./text.ts"; +import { formatZonedTimestamp, isZonedIsoTimestamp, type LocaleContext } from "./locale.ts"; export type ComponentKind = | "stats" @@ -44,7 +45,7 @@ function num(record: Record, key: string): number | undefined { } function errorBox(kind: string, reason: string): string { - return `

${escapeHtmlText(`Component '${kind}' failed to render: ${reason}`)}
`; + return ``; } function toneClass(prefix: string, tone: string | undefined, allowed: readonly string[]): string { @@ -61,6 +62,20 @@ export interface ComponentIssue { nextAction: string; } +export interface ComponentRenderContext { + locale: LocaleContext; +} + +const DEFAULT_CONTEXT: ComponentRenderContext = { + locale: { lang: "en", dir: "ltr", locale: "en-US", timeZone: "UTC" }, +}; + +function mermaidParts(source: string): { summary?: string; body: string } { + const lines = source.trim().split("\n"); + const match = lines[0]?.match(/^%%\s*summary:\s*(.+)$/i); + return { summary: match?.[1].trim(), body: match ? lines.slice(1).join("\n").trim() : source.trim() }; +} + function issue(code: string, reason: string, nextAction: string): ComponentIssue[] { return [{ code, reason, nextAction }]; } @@ -90,7 +105,13 @@ function requiredStrings(value: unknown[], kind: string, fields: string[]): Comp export function validateComponent(kind: ComponentKind, source: string): ComponentIssue[] { if (kind === "diff") return source.trim() === "" ? issue("diff-empty", "diff source is empty", "add unified diff lines") : []; - if (kind === "mermaid") return source.trim() === "" ? issue("mermaid-empty", "Mermaid source is empty", "add a Mermaid diagram") : []; + if (kind === "mermaid") { + if (source.trim() === "") return issue("mermaid-empty", "Mermaid source is empty", "add a Mermaid diagram"); + const parsed = mermaidParts(source); + if (!parsed.summary) return issue("mermaid-summary", "Mermaid diagram needs a text summary", "start with %% summary: followed by the diagram's meaning"); + if (parsed.body === "") return issue("mermaid-empty", "Mermaid diagram body is empty", "add diagram syntax after the summary"); + return []; + } let value: unknown; try { value = JSON.parse(source); @@ -159,8 +180,10 @@ export function validateComponent(kind: ComponentKind, source: string): Componen } if (kind === "table") { if (!Array.isArray(item["columns"]) || !Array.isArray(item["rows"])) return issue("table-shape", "columns and rows must be arrays", "add both documented arrays"); + if (!str(item, "caption")) return issue("table-caption", "table caption is required", "add a concise caption describing the table"); const keys = new Set(); const issues: ComponentIssue[] = []; + const dateColumns: Array<{ key: string; type: "date" | "datetime" }> = []; for (const [index, columnValue] of item["columns"].entries()) { const column = asRecord(columnValue); const key = column && str(column, "key"); @@ -171,10 +194,21 @@ export function validateComponent(kind: ComponentKind, source: string): Componen if (keys.has(key)) issues.push(...issue("table-key", `column key '${key}' is duplicated`, "assign unique column keys")); keys.add(key); const type = str(column, "type"); - if (type !== undefined && type !== "num") issues.push(...issue("table-type", `column ${index + 1} type is unsupported`, "use 'num' or omit type")); + if (type !== undefined && type !== "num" && type !== "date" && type !== "datetime") issues.push(...issue("table-type", `column ${index + 1} type is unsupported`, "use num, date, datetime, or omit type")); + if (type === "date" || type === "datetime") dateColumns.push({ key, type }); } for (const [index, row] of item["rows"].entries()) { - if (asRecord(row) === undefined) issues.push(...issue("table-row", `row ${index + 1} must be an object`, "replace non-object rows")); + const record = asRecord(row); + if (record === undefined) { + issues.push(...issue("table-row", `row ${index + 1} must be an object`, "replace non-object rows")); + continue; + } + for (const column of dateColumns) { + const value = record[column.key]; + if (value !== undefined && (typeof value !== "string" || !isZonedIsoTimestamp(value))) { + issues.push(...issue("table-date", `row ${index + 1} '${column.key}' needs an ISO timestamp with time zone`, "use a timestamp ending in Z or an explicit offset")); + } + } } return issues; } @@ -296,7 +330,7 @@ function renderProgress(spec: unknown): string { const label = str(item, "label"); const percent = Math.min(100, Math.round((done / total) * 100)); return [ - '
', + `
`, `
${escapeHtmlText(label ?? "Progress")} — ${done}/${total}
`, `
`, "
", @@ -331,9 +365,9 @@ function renderCopy(spec: unknown, id: string | undefined): string { const item } function renderMermaid(source: string): string { - const trimmed = source.trim(); - if (trimmed === "") return errorBox("mermaid", "empty diagram source"); - return `
${escapeHtmlText(trimmed)}
`; + const parsed = mermaidParts(source); + if (!parsed.summary || parsed.body === "") return errorBox("mermaid", "missing summary or diagram source"); + return `
${escapeHtmlText(parsed.summary)}
`; } function renderDecisions(spec: unknown): string { @@ -342,21 +376,22 @@ function renderDecisions(spec: unknown): string { const questions = item["questions"]; if (!Array.isArray(questions)) return errorBox("decisions", "missing 'questions' array"); - const blocks = questions.map((entry) => { + const blocks = questions.map((entry, questionIndex) => { const question = asRecord(entry); if (!question) return errorBox("decisions", "questions must be objects"); const qid = str(question, "id") ?? "q"; const text = str(question, "question") ?? ""; const options = Array.isArray(question["options"]) ? question["options"] : []; + const groupId = `decision-question-${questionIndex}`; const buttons = options - .map((optEntry) => { + .map((optEntry, optionIndex) => { const opt = asRecord(optEntry); if (!opt) return ""; const oid = str(opt, "id") ?? "opt"; const label = str(opt, "label") ?? ""; const note = str(opt, "note"); return [ - ``, + ``, ) .join(""); @@ -418,9 +455,13 @@ function renderTable(spec: unknown): string { .map((c) => { const raw = record[c!.key]; const isNum = c!.type === "num" && typeof raw === "number" && Number.isFinite(raw); + const isDate = (c!.type === "date" || c!.type === "datetime") && typeof raw === "string"; + const formattedDate = isDate ? formatZonedTimestamp(raw, context.locale, c!.type === "datetime") : undefined; const display = isNum - ? raw.toLocaleString("en-US") - : escapeHtmlText(raw === undefined || raw === null ? "—" : String(raw)); + ? new Intl.NumberFormat(context.locale.locale).format(raw) + : formattedDate !== undefined + ? `` + : escapeHtmlText(raw === undefined || raw === null ? "—" : String(raw)); const dataV = isNum ? ` data-v="${raw}"` : ` data-v="${escapeHtmlText(String(raw ?? ""))}"`; return `${display}`; }) @@ -430,17 +471,20 @@ function renderTable(spec: unknown): string { .join("\n"); const caption = str(item, "caption"); + const tableId = escapeHtmlText(id ?? "table-0"); + const countId = `${tableId}-count`; return [ '
', - '', - `
${head}`, + ``, + ``, + `
${head}`, `${body}
${escapeHtmlText(caption ?? "Table")}
`, - `
${rows.length} rows${caption ? `${escapeHtmlText(caption)}` : ""}
`, + `
${rows.length} rows
`, "
", ].join("\n"); } -export function renderComponent(kind: ComponentKind, json: string, id?: string): string { +export function renderComponent(kind: ComponentKind, json: string, id?: string, context: ComponentRenderContext = DEFAULT_CONTEXT): string { const issues = validateComponent(kind, json); if (issues.length > 0) return errorBox(kind, issues[0].reason); if (kind === "diff") return renderDiff(json); @@ -469,6 +513,6 @@ export function renderComponent(kind: ComponentKind, json: string, id?: string): case "decisions": return renderDecisions(spec); case "table": - return renderTable(spec); + return renderTable(spec, context, id); } } diff --git a/src/design-tokens.ts b/src/design-tokens.ts index 529177a..0514d11 100644 --- a/src/design-tokens.ts +++ b/src/design-tokens.ts @@ -55,7 +55,7 @@ const BASE: DesignTokenValues = { text: "#111827", mutedText: "#4b5563", border: "#e5e7eb", - accent: "#6d6bd6", + accent: "#5f5dbf", font: "system", spacing: "comfortable", radius: "round", @@ -69,7 +69,7 @@ const THEME_VALUES: Record> = { text: "#2b251a", mutedText: "#6b5f49", border: "#e3d9c4", - accent: "#b4541e", + accent: "#8f3f13", }, ops: { pageBackground: "#0f140f", @@ -194,8 +194,8 @@ function contrastIssue(values: DesignTokenValues, source: "project" | "prompt", [values.text, values.surface, 4.5, "text/surface"], [values.mutedText, values.pageBackground, 4.5, "mutedText/pageBackground"], [values.mutedText, values.surface, 4.5, "mutedText/surface"], - [values.accent, values.pageBackground, 3, "accent/pageBackground"], - [values.accent, values.surface, 3, "accent/surface"], + [values.accent, values.pageBackground, 4.5, "accent/pageBackground"], + [values.accent, values.surface, 4.5, "accent/surface"], ]; const failed = pairs.find(([foreground, background, minimum]) => contrastRatio(foreground, background) < minimum); return failed === undefined @@ -210,7 +210,8 @@ function cssFor(values: DesignTokenValues, provenance: Record provenance[name] === "project" || provenance[name] === "prompt"; const declarations: string[] = []; if (fixesColorMode) { - declarations.push(`--page-bg:${values.pageBackground}`, `--card-bg:${values.surface}`, `--ink:${values.text}`, `--ink-2:${values.mutedText}`, `--line:${values.border}`, `--accent:${values.accent}`); + const accentInk = contrastRatio(values.accent, "#ffffff") >= 4.5 ? "#ffffff" : "#111827"; + declarations.push(`--page-bg:${values.pageBackground}`, `--card-bg:${values.surface}`, `--ink:${values.text}`, `--ink-2:${values.mutedText}`, `--line:${values.border}`, `--accent:${values.accent}`, `--accent-ink:${accentInk}`); } if (customized("font")) declarations.push(`--artifact-font:${font}`, `--artifact-heading-font:${font}`); if (customized("spacing")) declarations.push(`--body-pad:${bodyPad}`, `--body-pad-bottom:${bodyPadBottom}`, `--section-gap:${sectionGap}`); diff --git a/src/locale.ts b/src/locale.ts new file mode 100644 index 0000000..8d29bba --- /dev/null +++ b/src/locale.ts @@ -0,0 +1,58 @@ +import type { Frontmatter } from "./markdown.ts"; + +export type TextDirection = "ltr" | "rtl"; + +export interface LocaleContext { + lang: string; + dir: TextDirection; + locale: string; + timeZone: string; +} + +const RTL_LANGUAGES = new Set(["ar", "ckb", "dv", "fa", "he", "ku", "ps", "sd", "ug", "ur", "yi"]); + +export function canonicalLocale(value: string): string | undefined { + try { + return Intl.getCanonicalLocales(value)[0]; + } catch { + return undefined; + } +} + +export function validTimeZone(value: string): boolean { + try { + new Intl.DateTimeFormat("en-US", { timeZone: value }).format(0); + return true; + } catch { + return false; + } +} + +export function resolveLocaleContext(meta: Frontmatter): LocaleContext { + const locale = canonicalLocale(meta.locale ?? meta.lang ?? "en-US") ?? "en-US"; + const lang = canonicalLocale(meta.lang ?? meta.locale ?? "en") ?? "en"; + const language = new Intl.Locale(lang).language; + const dir = meta.dir === "ltr" || meta.dir === "rtl" + ? meta.dir + : RTL_LANGUAGES.has(language) ? "rtl" : "ltr"; + return { + lang, + dir, + locale, + timeZone: meta.timezone !== undefined && validTimeZone(meta.timezone) ? meta.timezone : "UTC", + }; +} + +export function isZonedIsoTimestamp(value: string): boolean { + return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) + && Number.isFinite(Date.parse(value)); +} + +export function formatZonedTimestamp(value: string, context: LocaleContext, includeTime: boolean): string | undefined { + if (!isZonedIsoTimestamp(value)) return undefined; + return new Intl.DateTimeFormat(context.locale, { + timeZone: context.timeZone, + dateStyle: "medium", + ...(includeTime ? { timeStyle: "short" as const } : {}), + }).format(new Date(value)); +} diff --git a/src/markdown.ts b/src/markdown.ts index 653a64e..25f0f72 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -9,6 +9,10 @@ export interface Frontmatter { theme?: string; source?: string; font?: string; + lang?: string; + dir?: string; + locale?: string; + timezone?: string; } export type ChartKind = "vega-lite" | "vega" | "echarts"; @@ -64,6 +68,10 @@ function parseFrontmatter(source: string, warnings: string[]): { meta: Frontmatt else if (key === "theme") meta.theme = value; else if (key === "source") meta.source = value; else if (key === "font") meta.font = value; + else if (key === "lang") meta.lang = value; + else if (key === "dir") meta.dir = value; + else if (key === "locale") meta.locale = value; + else if (key === "timezone") meta.timezone = value; else warnings.push(`frontmatter key ignored: ${key}`); } return { meta, body: source.slice(match[0].length), lineOffset: (match[0].match(/\n/g) ?? []).length }; diff --git a/src/preflight.ts b/src/preflight.ts index ec567f7..6defb5d 100644 --- a/src/preflight.ts +++ b/src/preflight.ts @@ -2,7 +2,8 @@ import MarkdownIt from "markdown-it"; import { validateComponent, COMPONENT_KINDS, type ComponentKind } from "./components.ts"; import { resolvePortableAssets, AssetPreflightError, type PortableAssets } from "./assets.ts"; import { validateChartSpec } from "./render.ts"; -import { slugify } from "./text.ts"; +import { headingSlugify } from "./text.ts"; +import { canonicalLocale, validTimeZone } from "./locale.ts"; import { parseDocument } from "./markdown.ts"; import { loadProjectDesignTokens, resolveDesignTokens, type ResolvedDesignTokens } from "./design-tokens.ts"; @@ -33,7 +34,7 @@ export interface PreflightResult { } const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/; -const KNOWN_FRONTMATTER = new Set(["title", "icon", "description", "theme", "source", "font"]); +const KNOWN_FRONTMATTER = new Set(["title", "icon", "description", "theme", "source", "font", "lang", "dir", "locale", "timezone"]); const THEMES = new Set(["default", "report", "ops", "editorial"]); const CHART_KINDS = new Set(["vega-lite", "vega", "echarts"]); const ALERT_KINDS = new Set(["NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"]); @@ -67,6 +68,10 @@ function frontmatterDiagnostics(markdown: string): AuthoringDiagnostic[] { seen.add(key); if (!KNOWN_FRONTMATTER.has(key)) output.push(diagnostic("frontmatter-unknown", "warning", "frontmatter", index + 2, `frontmatter key '${key}' is ignored`, "remove it or use a documented key")); if (key === "theme" && parsed[2] !== "" && !THEMES.has(parsed[2].trim())) output.push(diagnostic("theme-unknown", "warning", "frontmatter", index + 2, "unknown theme falls back to default", "use default, report, ops, or editorial")); + if (key === "lang" && canonicalLocale(parsed[2].trim()) === undefined) output.push(diagnostic("language-invalid", "error", "frontmatter", index + 2, "lang is not a valid BCP 47 language tag", "use a tag such as en, ar, or zh-Hant")); + if (key === "dir" && parsed[2].trim() !== "ltr" && parsed[2].trim() !== "rtl") output.push(diagnostic("direction-invalid", "error", "frontmatter", index + 2, "dir must be ltr or rtl", "choose the document's logical direction")); + if (key === "locale" && canonicalLocale(parsed[2].trim()) === undefined) output.push(diagnostic("locale-invalid", "error", "frontmatter", index + 2, "locale is not a valid BCP 47 locale", "use a locale such as en-US, de-DE, or ar-EG")); + if (key === "timezone" && !validTimeZone(parsed[2].trim())) output.push(diagnostic("timezone-invalid", "error", "frontmatter", index + 2, "timezone is not a valid IANA time zone", "use a zone such as UTC, Europe/Berlin, or Asia/Tokyo")); } return output; } @@ -87,12 +92,22 @@ function markdownDiagnostics(markdown: string): AuthoringDiagnostic[] { } } else if (CHART_KINDS.has(kind)) { const result = validateChartSpec({ kind: kind as "vega-lite" | "vega" | "echarts", json: token.content }); - if (result.error) output.push(diagnostic(result.code ?? `${kind}-invalid`, "error", "chart", line, `chart '${kind}' is invalid`, "provide valid JSON and a valid chart schema")); + if (result.error) { + const missingSummary = result.code === "chart-summary-missing"; + output.push(diagnostic( + result.code ?? `${kind}-invalid`, + "error", + "chart", + line, + missingSummary ? `chart '${kind}' lacks a semantic equivalent` : `chart '${kind}' is invalid`, + missingSummary ? "add a meaningful top-level description summarizing the chart" : "provide valid JSON and a valid chart schema", + )); + } } } if (token.type === "heading_open") { const inline = tokens[index + 1]; - const anchor = slugify(inline?.content ?? ""); + const anchor = headingSlugify(inline?.content ?? ""); const first = anchors.get(anchor); if (anchor !== "" && first !== undefined) output.push(diagnostic("anchor-duplicate", "error", "markdown", line, `heading anchor '${anchor}' duplicates line ${first}`, "rename one heading so every anchor is unique")); else if (anchor !== "") anchors.set(anchor, line); diff --git a/src/render.ts b/src/render.ts index ae9b786..f303b89 100644 --- a/src/render.ts +++ b/src/render.ts @@ -2,9 +2,10 @@ import { compile as compileVegaLite } from "vega-lite"; import { parseDocument, type ChartSpec, type Frontmatter } from "./markdown.ts"; import { renderComponent } from "./components.ts"; import { runtimeBundle, type RuntimeName } from "./runtime.ts"; -import { escapeHtmlText, slugify } from "./text.ts"; +import { escapeHtmlText, headingSlugify } from "./text.ts"; import { resolvePortableAssets, type AssetLimits, type PortableAssets } from "./assets.ts"; import { resolveDesignTokens, type ResolvedDesignTokens } from "./design-tokens.ts"; +import { resolveLocaleContext, type LocaleContext } from "./locale.ts"; export { escapeHtmlText } from "./text.ts"; @@ -44,6 +45,7 @@ interface ResolvedChart { kind: ResolvedKind; spec?: unknown; error?: string; + summary?: string; } export const CSP = [ @@ -57,25 +59,30 @@ export const CSP = [ export const ARTIFACT_CSS = `:root{color-scheme:light; --page-bg:#e9edf2;--card-bg:#ffffff;--ink:#111827;--ink-2:#4b5563;--ink-3:#9ca3af;--line:#e5e7eb; ---accent:#6d6bd6;--good:#2f9e6e;--good-bg:#e4f4ec;--bad:#d64550;--bad-bg:#fdeeee; ---warn:#b45309;--warn-bg:#fdf0dc;--info:#33526e;--info-bg:#dce6f2; +--accent:#5f5dbf;--accent-ink:#ffffff;--good:#237a52;--good-bg:#e4f4ec;--bad:#b42335;--bad-bg:#fdeeee; +--warn:#92400e;--warn-bg:#fdf0dc;--info:#33526e;--info-bg:#dce6f2; --card-info-bg:#e3eaf4;--card-warn-bg:#fdeccd;--code-bg:#f3f4f6; --radius:16px;--shadow:0 1px 3px rgb(15 23 42/.06); --artifact-font:system-ui,-apple-system,"Segoe UI",sans-serif;--artifact-heading-font:var(--artifact-font); --body-pad:1.5rem;--body-pad-bottom:3rem;--section-gap:1.25rem;--section-pad-y:1.5rem;--section-pad-x:1.75rem;--table-font-size:.86rem} @media (prefers-color-scheme: dark){:root:not([data-theme="light"]){color-scheme:dark; --page-bg:#151a21;--card-bg:#1f2630;--ink:#e5e7eb;--ink-2:#9ca3af;--ink-3:#6b7280;--line:#333d4d; +--accent:#a8a6ff;--accent-ink:#111827; --good:#4ade80;--good-bg:#14312a;--bad:#f87171;--bad-bg:#3a2226;--warn:#fbbf24;--warn-bg:#3a2f16; --info:#7ea4c7;--info-bg:#1e2c3d;--card-info-bg:#1e2c3d;--card-warn-bg:#3a2f16;--code-bg:#262e3a; --shadow:0 1px 3px rgb(0 0 0/.4)}} :root[data-theme="dark"]{color-scheme:dark; --page-bg:#151a21;--card-bg:#1f2630;--ink:#e5e7eb;--ink-2:#9ca3af;--ink-3:#6b7280;--line:#333d4d; +--accent:#a8a6ff;--accent-ink:#111827; --good:#4ade80;--good-bg:#14312a;--bad:#f87171;--bad-bg:#3a2226;--warn:#fbbf24;--warn-bg:#3a2f16; --info:#7ea4c7;--info-bg:#1e2c3d;--card-info-bg:#1e2c3d;--card-warn-bg:#3a2f16;--code-bg:#262e3a; --shadow:0 1px 3px rgb(0 0 0/.4)} -body{margin:0;background:var(--page-bg);color:var(--ink);font-family:var(--artifact-font);line-height:1.6} +html{overflow-wrap:anywhere}body{margin:0;background:var(--page-bg);color:var(--ink);font-family:var(--artifact-font);line-height:1.6} +.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important} +.skip-link{position:fixed;z-index:100;inset-block-start:.5rem;inset-inline-start:.5rem;padding:.55rem .8rem;background:var(--card-bg);color:var(--ink);border:2px solid var(--accent);border-radius:8px;transform:translateY(-160%)} +.skip-link:focus{transform:none} .artifact-header{display:flex;align-items:center;gap:.6rem;padding:.9rem 1.5rem;background:var(--card-bg);border-bottom:1px solid var(--line)} -.theme-toggle{margin-left:auto;background:none;border:1px solid var(--line);border-radius:999px;padding:.25rem .8rem;font-size:.75rem;font-weight:600;color:var(--ink-2);cursor:pointer} +.theme-toggle{margin-inline-start:auto;min-height:2rem;background:none;border:1px solid var(--line);border-radius:999px;padding:.25rem .8rem;font-size:.75rem;font-weight:600;color:var(--ink-2);cursor:pointer} .theme-toggle:hover{border-color:var(--accent);color:var(--accent)} .artifact-header h1{font-size:1.1rem;margin:0;letter-spacing:-.01em} .artifact-icon{font-size:1.25rem} @@ -93,14 +100,15 @@ p{margin:.6rem 0} pre{background:var(--code-bg);padding:.75rem 1rem;overflow:auto;border-radius:10px} code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em} p code,li code,td code{background:var(--code-bg);padding:.1em .35em;border-radius:5px} -.chart{margin:1rem 0;min-height:320px} +.chart-frame,.diagram-frame{margin:1rem 0}.chart{margin:0;min-height:320px;max-width:100%;overflow:hidden} +.chart-summary,.diagram-summary{margin:.5rem 0 0;color:var(--ink-2);font-size:.88rem} .chart-error{padding:.75rem 1rem;border:1px solid var(--bad);border-radius:10px;color:var(--bad);background:var(--bad-bg);margin:1rem 0} table{border-collapse:collapse;width:100%;margin:1rem 0;font-size:.92rem} -th{text-align:left;background:var(--code-bg);font-weight:600} +th{text-align:start;background:var(--code-bg);font-weight:600} td,th{border:1px solid var(--line);padding:.45rem .7rem} img{max-width:100%} a{color:var(--accent)} -blockquote{margin:1rem 0;padding:.25rem 1rem;border-left:3px solid var(--line);color:var(--ink-2)} +blockquote{margin:1rem 0;padding:.25rem 1rem;border-inline-start:3px solid var(--line);color:var(--ink-2)} .stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem;margin:1.25rem 0} .stat{background:var(--card-bg);border-radius:var(--radius);box-shadow:var(--shadow);padding:1.1rem 1.25rem} .stat-emphasis.tone-bad{background:var(--bad-bg)} @@ -113,7 +121,7 @@ blockquote{margin:1rem 0;padding:.25rem 1rem;border-left:3px solid var(--line);c .delta-warn{background:var(--warn-bg);color:var(--warn)} .timeline{list-style:none;margin:1rem 0;padding:0} .tl-item{display:flex;gap:.9rem;position:relative;padding:0 0 1.1rem .25rem} -.tl-item::before{content:"";position:absolute;left:.68rem;top:1.1rem;bottom:-.1rem;width:2px;background:var(--line)} +.tl-item::before{content:"";position:absolute;inset-inline-start:.68rem;top:1.1rem;bottom:-.1rem;width:2px;background:var(--line)} .tl-item:last-child::before{display:none} .tl-dot{flex:none;width:.85rem;height:.85rem;border-radius:50%;background:var(--ink-3);margin-top:.35rem;z-index:1} .dot-bad{background:var(--bad)}.dot-good{background:var(--good)}.dot-warn{background:var(--warn)}.dot-info{background:var(--info)} @@ -140,8 +148,8 @@ blockquote{margin:1rem 0;padding:.25rem 1rem;border-left:3px solid var(--line);c .pill-warn{background:var(--warn-bg);color:var(--warn)} .pill-good{background:var(--good-bg);color:var(--good)} .annotations{margin:.9rem 0 0;padding:0;list-style:none;counter-reset:note} -.annotations li{counter-increment:note;position:relative;padding-left:1.6rem;margin:.4rem 0;font-size:.9rem} -.annotations li::before{content:counter(note);position:absolute;left:0;top:.1rem;width:1.05rem;height:1.05rem;border-radius:50%;background:var(--ink);color:var(--card-bg);font-size:.65rem;font-weight:700;display:flex;align-items:center;justify-content:center} +.annotations li{counter-increment:note;position:relative;padding-inline-start:1.6rem;margin:.4rem 0;font-size:.9rem} +.annotations li::before{content:counter(note);position:absolute;inset-inline-start:0;top:.1rem;width:1.05rem;height:1.05rem;border-radius:50%;background:var(--ink);color:var(--card-bg);font-size:.65rem;font-weight:700;display:flex;align-items:center;justify-content:center} .tradeoff{margin:.9rem 0 0;font-size:.85rem;font-style:italic;color:var(--ink-2);border-top:1px solid var(--line);padding-top:.6rem} .callout{border-radius:var(--radius);padding:1.25rem 1.5rem;margin:1.25rem 0;background:var(--card-info-bg)} .callout-warn{background:var(--card-warn-bg)} @@ -158,9 +166,9 @@ blockquote{margin:1rem 0;padding:.25rem 1rem;border-left:3px solid var(--line);c .alert-warn{border-color:var(--warn);background:var(--warn-bg)} .alert-bad{border-color:var(--bad);background:var(--bad-bg)} li.task{list-style:none} -li.task input{margin-right:.45rem;accent-color:var(--accent)} +li.task input{margin-inline-end:.45rem;accent-color:var(--accent)} .copy-wrap{display:inline-flex;align-items:center;gap:.55rem;margin:.25rem 0} -.copy-btn{background:var(--accent);color:#fff;border:none;border-radius:8px;padding:.45rem 1rem;font-size:.85rem;font-weight:600;cursor:pointer} +.copy-btn{background:var(--accent);color:var(--accent-ink);border:none;border-radius:8px;padding:.45rem 1rem;font-size:.85rem;font-weight:600;cursor:pointer} .copy-btn:hover{filter:brightness(1.08)} .copy-note{font-size:.8rem;color:var(--good)} pre.mermaid{background:var(--card-bg);border:1px solid var(--line);border-radius:10px;padding:1rem;text-align:center} @@ -170,7 +178,7 @@ pre.mermaid{background:var(--card-bg);border:1px solid var(--line);border-radius .decision{margin:0 0 1rem} .decision-question{font-weight:600;margin-bottom:.5rem} .decision-options{display:flex;flex-direction:column;gap:.5rem} -.decision-opt{display:flex;flex-direction:column;gap:.15rem;text-align:left;background:var(--card-bg);border:1px solid var(--line);border-radius:10px;padding:.7rem 1rem;font-size:.92rem;color:var(--ink);cursor:pointer} +.decision-opt{display:flex;flex-direction:column;gap:.15rem;text-align:start;min-height:2.75rem;background:var(--card-bg);border:1px solid var(--line);border-radius:10px;padding:.7rem 1rem;font-size:.92rem;color:var(--ink);cursor:pointer} .section-card .decision-opt{background:var(--page-bg)} .decision-opt:hover{border-color:var(--accent)} .decision-opt.selected{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)} @@ -183,28 +191,30 @@ pre.mermaid{background:var(--card-bg);border:1px solid var(--line);border-radius .table-wrap{margin:1rem 0} .table-filter{width:100%;max-width:320px;padding:.45rem .8rem;border:1px solid var(--line);border-radius:8px;background:var(--card-bg);color:var(--ink);font:inherit;font-size:.88rem;margin-bottom:.5rem} .table-scroll{overflow-x:auto;border:1px solid var(--line);border-radius:10px} +.data-table caption{text-align:start;padding:.55rem;font-weight:650;color:var(--ink);background:var(--code-bg)} .data-table{margin:0;font-size:var(--table-font-size)} .data-table th{padding:.35rem .55rem;white-space:nowrap} .data-table td{padding:.3rem .55rem} .th-sort{background:none;border:none;padding:0;font:inherit;font-weight:600;color:inherit;cursor:pointer} -.th-sort::after{content:"↕";margin-left:.35rem;opacity:.35;font-size:.75em} +.th-sort::after{content:"↕";margin-inline-start:.35rem;opacity:.35;font-size:.75em} th[data-dir="asc"] .th-sort::after{content:"↑";opacity:1;color:var(--accent)} th[data-dir="desc"] .th-sort::after{content:"↓";opacity:1;color:var(--accent)} .data-table .num{text-align:right} .table-meta{display:flex;justify-content:space-between;font-size:.78rem;color:var(--ink-3);margin-top:.35rem} -.comments-dock{position:fixed;right:1rem;bottom:1rem;width:300px;max-height:45vh;overflow:auto;background:var(--card-bg);border:1px solid var(--line);border-radius:12px;box-shadow:0 6px 24px rgb(15 23 42/.14);padding:.75rem .9rem;z-index:10;font-size:.85rem} +.comments-dock{position:fixed;inset-inline-end:1rem;bottom:4rem;width:300px;max-width:calc(100vw - 2rem);max-height:45vh;overflow:auto;background:var(--card-bg);border:1px solid var(--line);border-radius:12px;box-shadow:0 6px 24px rgb(15 23 42/.14);padding:.75rem .9rem;z-index:10;font-size:.85rem} .comments-title{font-weight:700;margin-bottom:.4rem} .comment{border-top:1px solid var(--line);padding:.45rem 0} -.comment-quote{font-size:.75rem;color:var(--ink-3);border-left:2px solid var(--accent);padding-left:.45rem;margin-bottom:.2rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.comment-quote{font-size:.75rem;color:var(--ink-3);border-inline-start:2px solid var(--accent);padding-inline-start:.45rem;margin-bottom:.2rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .comment-text{white-space:pre-wrap} .comment-empty{color:var(--ink-3);font-size:.78rem} -.comment-resolve{margin-top:.3rem;background:none;border:1px solid var(--line);border-radius:6px;padding:.15rem .55rem;font-size:.75rem;cursor:pointer;color:var(--ink-2)} +.comment-resolve{margin-top:.3rem;min-height:2rem;background:none;border:1px solid var(--line);border-radius:6px;padding:.15rem .55rem;font-size:.75rem;cursor:pointer;color:var(--ink-2)} .comment-resolve:hover{border-color:var(--good);color:var(--good)} -.comment-pop{z-index:20;background:var(--accent);color:#fff;border:none;border-radius:999px;padding:.3rem .8rem;font-size:.78rem;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgb(15 23 42/.2)} -.comment-form{position:fixed;right:1rem;bottom:1rem;width:320px;background:var(--card-bg);border:1px solid var(--line);border-radius:12px;box-shadow:0 6px 24px rgb(15 23 42/.14);padding:.9rem;z-index:21} +.comment-pop{z-index:20;background:var(--accent);color:var(--accent-ink);border:none;border-radius:999px;padding:.3rem .8rem;font-size:.78rem;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgb(15 23 42/.2)} +.comment-launcher{position:fixed;inset-inline-end:1rem;bottom:1rem;z-index:19;min-height:2.75rem;background:var(--accent);color:var(--accent-ink);border:2px solid var(--card-bg);border-radius:999px;padding:.5rem 1rem;font:inherit;font-size:.82rem;font-weight:650;cursor:pointer;box-shadow:0 2px 8px rgb(15 23 42/.2)} +.comment-form{position:fixed;inset-inline-end:1rem;bottom:1rem;width:320px;max-width:calc(100vw - 2rem);background:var(--card-bg);border:1px solid var(--line);border-radius:12px;box-shadow:0 6px 24px rgb(15 23 42/.14);padding:.9rem;z-index:21} .comment-input{width:100%;min-height:4.5rem;margin:.5rem 0;border:1px solid var(--line);border-radius:8px;padding:.45rem;font:inherit;background:var(--page-bg);color:var(--ink)} -.comment-save{background:var(--accent);color:#fff;border:none;border-radius:8px;padding:.35rem .9rem;font-weight:600;cursor:pointer} -.comment-cancel{background:none;border:1px solid var(--line);border-radius:8px;padding:.35rem .9rem;margin-left:.4rem;cursor:pointer;color:var(--ink-2)} +.comment-save{min-height:2.75rem;background:var(--accent);color:var(--accent-ink);border:none;border-radius:8px;padding:.35rem .9rem;font-weight:600;cursor:pointer} +.comment-cancel{min-height:2.75rem;background:none;border:1px solid var(--line);border-radius:8px;padding:.35rem .9rem;margin-inline-start:.4rem;cursor:pointer;color:var(--ink-2)} .progress{margin:1.25rem 0} .progress-label{font-size:.85rem;font-weight:600;margin-bottom:.4rem} .progress-track{height:.55rem;border-radius:999px;background:var(--code-bg);overflow:hidden} @@ -223,13 +233,16 @@ th[data-dir="desc"] .th-sort::after{content:"↓";opacity:1;color:var(--accent)} .card .desc{font-size:.85rem;color:var(--ink-2);margin:.1rem 0 .4rem} .card .icon{font-size:1.6rem} .gallery-empty{color:var(--ink-3);text-align:center;padding:3rem 0} -:focus-visible{outline:2px solid var(--accent);outline-offset:2px} -@media print{body{background:#fff}.section-card,.stat,.variant,.card{box-shadow:none;border:1px solid #ddd}}`; +:focus-visible{outline:3px solid var(--accent);outline-offset:3px} +@media (max-width:600px){.artifact-header{padding:.75rem 1rem}.artifact-body{--body-pad:1rem;--body-pad-bottom:2rem}.section-card{--section-pad-y:1rem;--section-pad-x:1rem}.tl-item{gap:.55rem}.tl-time{width:3.6rem}.comments-dock,.comment-form{inset-inline:1rem;width:auto}.stat-grid,.compare-grid{grid-template-columns:1fr}} +@media (max-width:700px){.comments-dock{position:static;margin:1rem;max-width:none;width:auto}} +@media (prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}} +@page{margin:15mm}@media print{body{background:#fff;color:#000}.skip-link,.theme-toggle,.copy-btn,.copy-note,.table-filter,.comments-dock,.comment-pop,.comment-launcher,.comment-form{display:none!important}.section-card,.stat,.variant,.card,.finding,.callout{box-shadow:none!important;break-inside:avoid;border:1px solid #bbb}.chart-frame,.diagram-frame,.table-wrap{break-inside:avoid}.table-scroll{overflow:visible}.data-table{font-size:9pt}a{color:inherit;text-decoration:underline}}`; const THEME_CSS: Record = { - report: `:root{color-scheme:light;--page-bg:#f6f0e4;--card-bg:#fffdf7;--ink:#2b251a;--ink-2:#6b5f49;--ink-3:#a29378;--line:#e3d9c4;--accent:#b4541e;--code-bg:#f1e9d8;--artifact-heading-font:Georgia,Charter,"Times New Roman",serif} + report: `:root{color-scheme:light;--page-bg:#f6f0e4;--card-bg:#fffdf7;--ink:#2b251a;--ink-2:#6b5f49;--ink-3:#a29378;--line:#e3d9c4;--accent:#8f3f13;--code-bg:#f1e9d8;--artifact-heading-font:Georgia,Charter,"Times New Roman",serif} h2,.callout-title,.stat-value{font-family:var(--artifact-heading-font)}`, - ops: `:root{color-scheme:dark;--page-bg:#0f140f;--card-bg:#171f17;--ink:#d5e5cf;--ink-2:#8fa389;--ink-3:#5c6b57;--line:#263026;--accent:#4ade80;--code-bg:#131c13;--good:#4ade80;--good-bg:#14311f;--bad:#f87171;--bad-bg:#3a1d1d;--warn:#fbbf24;--warn-bg:#3a2f16;--info:#7ea4c7;--info-bg:#1c2a38;--card-info-bg:#1c2a38;--card-warn-bg:#33290f;--artifact-heading-font:ui-monospace,SFMono-Regular,Menlo,monospace} + ops: `:root{color-scheme:dark;--page-bg:#0f140f;--card-bg:#171f17;--ink:#d5e5cf;--ink-2:#8fa389;--ink-3:#5c6b57;--line:#263026;--accent:#4ade80;--accent-ink:#0f140f;--code-bg:#131c13;--good:#4ade80;--good-bg:#14311f;--bad:#f87171;--bad-bg:#3a1d1d;--warn:#fbbf24;--warn-bg:#3a2f16;--info:#7ea4c7;--info-bg:#1c2a38;--card-info-bg:#1c2a38;--card-warn-bg:#33290f;--artifact-heading-font:ui-monospace,SFMono-Regular,Menlo,monospace} h2,.callout-title{font-family:var(--artifact-heading-font);letter-spacing:0}`, editorial: `:root{color-scheme:light;--page-bg:#fafafa;--card-bg:#ffffff;--ink:#141414;--ink-2:#525252;--ink-3:#a3a3a3;--line:#e5e5e5;--accent:#141414;--code-bg:#f5f5f5;--radius:4px;--shadow:none;--artifact-heading-font:Georgia,Charter,"Times New Roman",serif} h2{font-family:var(--artifact-heading-font);font-size:1.6rem;font-weight:500} @@ -244,6 +257,7 @@ const BOOT = `(function () { : "00000000-0000-4000-8000-" + Math.random().toString(16).slice(2).padEnd(12, "0").slice(0, 12); } var root = document.documentElement; + var reducedMotion = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches); if (!root.hasAttribute("data-page-theme")) { var header = document.querySelector(".artifact-header"); if (header) { @@ -283,6 +297,7 @@ const BOOT = `(function () { el.textContent = ""; var box = document.createElement("div"); box.className = "chart-error"; + box.setAttribute("role", "alert"); box.textContent = "Chart failed to render: " + message; el.appendChild(box); } @@ -294,6 +309,7 @@ const BOOT = `(function () { }); } else if (entry.kind === "echarts") { var chart = window.echarts.init(el); + if (reducedMotion && entry.spec && typeof entry.spec === "object") entry.spec.animation = false; chart.setOption(entry.spec); window.addEventListener("resize", function () { chart.resize(); }); } @@ -311,6 +327,7 @@ const BOOT = `(function () { el.textContent = ""; var box = document.createElement("div"); box.className = "chart-error"; + box.setAttribute("role", "alert"); box.textContent = "Diagram failed to render: " + (err && err.message ? err.message : String(err)); el.appendChild(box); }); @@ -346,6 +363,32 @@ const BOOT = `(function () { return label + " were not saved: " + reason + "." + next; } var decisionStateMeta = { revision: 0, contentHash: null }; + function selectDecision(opt, moveFocus) { + var group = opt ? opt.parentNode : null; + if (!group) return; + var peers = group.querySelectorAll(".decision-opt"); + for (var i = 0; i < peers.length; i++) { + peers[i].classList.remove("selected"); + peers[i].setAttribute("aria-checked", "false"); + peers[i].setAttribute("tabindex", "-1"); + } + opt.classList.add("selected"); + opt.setAttribute("aria-checked", "true"); + opt.setAttribute("tabindex", "0"); + if (moveFocus) opt.focus(); + } + document.addEventListener("keydown", function (ev) { + var opt = ev.target && ev.target.closest ? ev.target.closest(".decision-opt") : null; + if (!opt || !["ArrowDown", "ArrowRight", "ArrowUp", "ArrowLeft", "Home", "End"].includes(ev.key)) return; + var peers = Array.prototype.slice.call(opt.parentNode.querySelectorAll(".decision-opt")); + var at = peers.indexOf(opt); + var next = ev.key === "Home" ? 0 : ev.key === "End" ? peers.length - 1 + : ev.key === "ArrowDown" || ev.key === "ArrowRight" ? (at + 1) % peers.length + : (at - 1 + peers.length) % peers.length; + ev.preventDefault(); + peers[next].click(); + peers[next].focus(); + }); document.addEventListener("click", function (ev) { var btn = ev.target && ev.target.closest ? ev.target.closest(".copy-btn") : null; if (btn) { @@ -368,12 +411,7 @@ const BOOT = `(function () { if (!opt) return; var q = opt.getAttribute("data-question"); var o = opt.getAttribute("data-option"); - var group = opt.parentNode; - if (group) { - var peers = group.querySelectorAll(".decision-opt"); - for (var i = 0; i < peers.length; i++) peers[i].classList.remove("selected"); - } - opt.classList.add("selected"); + selectDecision(opt, false); var stateKey = "artifact-decisions:" + location.pathname; var state = {}; try { state = JSON.parse(localStorage.getItem(stateKey) || "{}"); } catch (e) {} @@ -407,7 +445,7 @@ const BOOT = `(function () { var saved = JSON.parse(localStorage.getItem("artifact-decisions:" + location.pathname) || "{}"); Object.keys(saved).forEach(function (q) { var el = document.querySelector('.decision-opt[data-question="' + q + '"][data-option="' + saved[q] + '"]'); - if (el) el.classList.add("selected"); + if (el) selectDecision(el, false); }); } catch (e) {} if (window.__ARTIFACT_STATE_URL__) { @@ -429,6 +467,8 @@ const BOOT = `(function () { var dock = null; var popBtn = null; var form = null; + var commentLauncher = null; + var formReturnFocus = null; function slugFromPath() { return decodeURIComponent(location.pathname.split("/").pop() || "").replace(/\.html$/, ""); @@ -471,18 +511,24 @@ const BOOT = `(function () { } function renderDock() { if (!dock) { - dock = make("div", "comments-dock"); + dock = make("aside", "comments-dock"); + dock.setAttribute("aria-label", "Page comments"); document.body.appendChild(dock); } dock.textContent = ""; var open = threads.filter(function (t) { return !t.resolved; }); - dock.appendChild(make("div", "comments-title", "Comments (" + open.length + ")")); + var title = make("div", "comments-title", "Comments (" + open.length + ")"); + title.setAttribute("role", "heading"); + title.setAttribute("aria-level", "2"); + dock.appendChild(title); open.forEach(function (t) { var item = make("div", "comment"); item.appendChild(make("div", "comment-quote", t.quote)); item.appendChild(make("div", "comment-text", t.text)); var resolveBtn = make("button", "comment-resolve", "Resolve"); + resolveBtn.type = "button"; resolveBtn.setAttribute("data-id", t.id); + resolveBtn.setAttribute("aria-label", "Resolve comment: " + String(t.quote || t.text).slice(0, 80)); item.appendChild(resolveBtn); dock.appendChild(item); }); @@ -492,9 +538,36 @@ const BOOT = `(function () { if (popBtn && popBtn.parentNode) popBtn.parentNode.removeChild(popBtn); popBtn = null; } - function closeForm() { + function closeForm(restoreFocus) { if (form && form.parentNode) form.parentNode.removeChild(form); form = null; + if (restoreFocus && formReturnFocus && formReturnFocus.focus) formReturnFocus.focus(); + formReturnFocus = null; + } + function openCommentForm(quote, trigger) { + closeForm(false); + formReturnFocus = trigger || commentLauncher; + form = make("div", "comment-form"); + form.setAttribute("role", "dialog"); + form.setAttribute("aria-labelledby", "artifact-comment-form-title"); + var title = make("div", "comments-title", "Add comment"); + title.id = "artifact-comment-form-title"; + form.appendChild(title); + form.appendChild(make("div", "comment-quote", quote || "Page comment")); + var label = make("label", "comment-label", "Comment"); + label.setAttribute("for", "artifact-comment-input"); + form.appendChild(label); + var textarea = make("textarea", "comment-input"); + textarea.id = "artifact-comment-input"; + form.appendChild(textarea); + var save = make("button", "comment-save", "Save"); + save.type = "button"; + form.appendChild(save); + var cancel = make("button", "comment-cancel", "Cancel"); + cancel.type = "button"; + form.appendChild(cancel); + document.body.appendChild(form); + textarea.focus(); } document.addEventListener("mouseup", function () { setTimeout(function () { @@ -504,6 +577,7 @@ const BOOT = `(function () { var rect = sel.getRangeAt(0).getBoundingClientRect(); hidePop(); popBtn = make("button", "comment-pop", "Comment"); + popBtn.type = "button"; popBtn.style.position = "fixed"; popBtn.style.left = Math.min(rect.left, window.innerWidth - 90) + "px"; popBtn.style.top = rect.bottom + 6 + "px"; @@ -514,16 +588,12 @@ const BOOT = `(function () { document.addEventListener("click", function (ev) { var t = ev.target; if (!t || !t.classList) return; + if (t.classList.contains("comment-launcher")) { + openCommentForm("Page comment", t); + return; + } if (t.classList.contains("comment-pop")) { - closeForm(); - form = make("div", "comment-form"); - form.appendChild(make("div", "comment-quote", t.getAttribute("data-quote") || "")); - form.appendChild(make("textarea", "comment-input")); - form.appendChild(make("button", "comment-save", "Save")); - form.appendChild(make("button", "comment-cancel", "Cancel")); - document.body.appendChild(form); - var input = form.querySelector(".comment-input"); - if (input) input.focus(); + openCommentForm(t.getAttribute("data-quote") || "", commentLauncher || document.querySelector("#artifact-main")); hidePop(); return; } @@ -542,10 +612,10 @@ const BOOT = `(function () { persist(); renderDock(); } - closeForm(); + closeForm(true); return; } - if (t.classList.contains("comment-cancel")) { closeForm(); return; } + if (t.classList.contains("comment-cancel")) { closeForm(true); return; } if (t.classList.contains("comment-resolve")) { var id = t.getAttribute("data-id"); threads.forEach(function (th) { if (th.id === id) th.resolved = true; }); @@ -563,20 +633,30 @@ const BOOT = `(function () { var numeric = th.getAttribute("data-type") === "num"; var asc = th.getAttribute("data-dir") !== "asc"; var heads = table.querySelectorAll("th"); - for (var hi = 0; hi < heads.length; hi++) heads[hi].removeAttribute("data-dir"); + for (var hi = 0; hi < heads.length; hi++) { + heads[hi].removeAttribute("data-dir"); + heads[hi].setAttribute("aria-sort", "none"); + } th.setAttribute("data-dir", asc ? "asc" : "desc"); + th.setAttribute("aria-sort", asc ? "ascending" : "descending"); var rows = Array.prototype.slice.call(tbody.querySelectorAll("tr")); rows.sort(function (a, b) { var av = a.children[colIndex].getAttribute("data-v") || ""; var bv = b.children[colIndex].getAttribute("data-v") || ""; var cmp = numeric ? (parseFloat(av) || 0) - (parseFloat(bv) || 0) - : av.localeCompare(bv); + : av.localeCompare(bv, root.getAttribute("data-locale") || "en-US"); return asc ? cmp : -cmp; }); rows.forEach(function (row) { tbody.appendChild(row); }); } }); + document.addEventListener("keydown", function (ev) { + if (ev.key === "Escape" && form) { + ev.preventDefault(); + closeForm(true); + } + }); document.addEventListener("input", function (ev) { var input = ev.target && ev.target.closest ? ev.target.closest(".table-filter") : null; if (!input) return; @@ -596,6 +676,9 @@ const BOOT = `(function () { }); var initialCommentsUrl = commentsUrl(); if (initialCommentsUrl) { + commentLauncher = make("button", "comment-launcher", "Add comment"); + commentLauncher.type = "button"; + document.body.appendChild(commentLauncher); fetch(initialCommentsUrl + "/" + encodeURIComponent(slugFromPath())) .then(function (r) { return r.json(); }) .then(function (data) { @@ -625,7 +708,7 @@ const ALERT_TONES: Record = { function alertDiv(kind: string, bodyHtml: string): string { const tone = ALERT_TONES[kind] ?? "info"; const label = kind.charAt(0) + kind.slice(1).toLowerCase(); - return `

${label}

${bodyHtml}
`; + return ``; } function enhanceBodyHtml(html: string): string { @@ -644,15 +727,15 @@ function enhanceBodyHtml(html: string): string { (_match, kind: string, body: string) => alertDiv(kind, `

${body}`), ); - out = out.replace( - /

  • \[x\]\s*/gi, - '
  • ', - ); - out = out.replace(/
  • \[ \]\s*/g, '
  • '); + out = out.replace(/
  • \[([ xX])\]\s*([\s\S]*?)<\/li>/g, (_match, marker: string, body: string) => { + const label = body.replace(/<[^>]+>/g, "").trim(); + const checked = marker.toLowerCase() === "x" ? " checked" : ""; + return `
  • ${body}
  • `; + }); out = out.replace(/([\s\S]*?)<\/h\1>/g, (_match, level: string, inner: string) => { const text = inner.replace(/<[^>]+>/g, ""); - return `${inner}`; + return `${inner}`; }); return out; @@ -675,12 +758,20 @@ export function validateChartSpec(chart: ChartSpec): ResolvedChart & { code?: st const kind: ResolvedKind = chart.kind === "echarts" ? "echarts" : "vega"; try { const parsed: unknown = JSON.parse(chart.json); - if (kind === "echarts") return { kind, spec: parsed }; + const record = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) + ? parsed as Record + : undefined; + const description = record?.["description"]; + if (typeof description !== "string" || description.trim().length < 8) { + return { kind, code: "chart-summary-missing", error: "chart needs a meaningful text description" }; + } + const summary = description.trim(); + if (kind === "echarts") return { kind, spec: parsed, summary }; if (chart.kind === "vega-lite") { const compiled = compileVegaLite(parsed as Parameters[0]); - return { kind, spec: compiled.spec }; + return { kind, spec: compiled.spec, summary }; } - return { kind, spec: parsed }; + return { kind, spec: parsed, summary }; } catch (err) { return { kind, code: `${chart.kind}-invalid`, error: err instanceof Error ? err.message : String(err) }; } @@ -704,6 +795,7 @@ interface AssembleInput { maxBytes: number; assetCss?: string; designTokens?: ResolvedDesignTokens; + locale: LocaleContext; } function assemblePage(input: AssembleInput): string { @@ -726,10 +818,11 @@ function assemblePage(input: AssembleInput): string { const designMetadata = input.designTokens?.active ? `` : ""; + const localeMetadata = escapeHtmlText(JSON.stringify({ locale: input.locale.locale, timeZone: input.locale.timeZone })); const parts: string[] = [ "", - ``, + ``, "", '', ``, @@ -739,12 +832,14 @@ function assemblePage(input: AssembleInput): string { ? `` : "", designMetadata, + ``, `${escapeHtmlText(input.title)}`, ``, "", "", - `
    ${escapeHtmlText(input.icon)}

    ${escapeHtmlText(input.title)}

    `, - `
    ${input.bodyHtml}
    `, + '', + `

    ${escapeHtmlText(input.title)}

    `, + `
    ${input.bodyHtml}
    `, FOOTER_PLACEHOLDER, ]; @@ -771,6 +866,7 @@ function assemblePage(input: AssembleInput): string { export function renderArtifact(markdown: string, options: RenderOptions = {}): RenderedArtifact { const doc = parseDocument(markdown, { assets: options.assets?.bySource }); + const locale = resolveLocaleContext(doc.meta); const inlineDesign = options.designTokens === undefined ? resolveDesignTokens(doc.meta.theme, undefined, doc.designTokens.map((block) => block.json)) : undefined; @@ -787,10 +883,18 @@ export function renderArtifact(markdown: string, options: RenderOptions = {}): R let needsMermaid = false; doc.components.forEach((block, index) => { const placeholder = `
    `; - bodyHtml = bodyHtml.replace(placeholder, renderComponent(block.kind, block.json, `component-${index}`)); + bodyHtml = bodyHtml.replace(placeholder, renderComponent(block.kind, block.json, `component-${index}`, { locale })); if (block.kind === "copy" || block.kind === "decisions") needsBoot = true; if (block.kind === "mermaid") needsMermaid = true; }); + const resolved = resolveCharts(doc.charts); + resolved.forEach((chart, index) => { + if (!chart.summary) return; + const placeholder = `
    `; + const summaryId = `chart-${index}-summary`; + const accessible = `
    ${escapeHtmlText(chart.summary)}
    `; + bodyHtml = bodyHtml.replace(placeholder, accessible); + }); bodyHtml = wrapSections(enhanceBodyHtml(bodyHtml)); const html = assemblePage({ @@ -799,7 +903,7 @@ export function renderArtifact(markdown: string, options: RenderOptions = {}): R description: doc.meta.description, theme: doc.meta.theme, bodyHtml, - resolved: resolveCharts(doc.charts), + resolved, needsBoot, needsMermaid, maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES, @@ -807,6 +911,7 @@ export function renderArtifact(markdown: string, options: RenderOptions = {}): R ? undefined : `@font-face{font-family:"Artifact Project";src:url(${options.assets.font.dataUri}) format("${fontFormat(options.assets.font.mime)}");font-display:swap}:root{--artifact-font:"Artifact Project",system-ui,-apple-system,"Segoe UI",sans-serif;--artifact-heading-font:var(--artifact-font)}`, designTokens, + locale, }); return { html, meta: doc.meta, chartCount: doc.charts.length }; } @@ -832,6 +937,7 @@ export function renderRawHtml( meta: Frontmatter = {}, options: RenderOptions = {}, ): RenderedArtifact { + const locale = resolveLocaleContext(meta); const html = assemblePage({ title: meta.title ?? "Artifact", icon: meta.icon ?? "📄", @@ -840,6 +946,7 @@ export function renderRawHtml( needsBoot: false, needsMermaid: false, maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES, + locale, }); return { html, meta, chartCount: 0 }; } diff --git a/src/served-html.ts b/src/served-html.ts index 311dd68..888b21f 100644 --- a/src/served-html.ts +++ b/src/served-html.ts @@ -10,8 +10,15 @@ export const NAME_RE = /^[a-z0-9-]+$/; */ export function prepareServedHtml(text: string, options: { liveReload?: boolean } = {}): string { const relaxed = text.replace("connect-src 'none'", "connect-src 'self'"); - const snippet = - options.liveReload === false ? BRIDGE_SNIPPET : BRIDGE_SNIPPET + LIVE_RELOAD_SNIPPET; - const at = relaxed.lastIndexOf(""); - return at === -1 ? relaxed + snippet : relaxed.slice(0, at) + snippet + relaxed.slice(at); + const headEnd = relaxed.lastIndexOf(""); + const bodyStart = relaxed.indexOf("", bodyStart); + const bridged = bodyOpenEnd === -1 + ? BRIDGE_SNIPPET + relaxed + : relaxed.slice(0, bodyOpenEnd + 1) + BRIDGE_SNIPPET + relaxed.slice(bodyOpenEnd + 1); + if (options.liveReload === false) return bridged; + const bodyEnd = bridged.lastIndexOf(""); + return bodyEnd === -1 + ? bridged + LIVE_RELOAD_SNIPPET + : bridged.slice(0, bodyEnd) + LIVE_RELOAD_SNIPPET + bridged.slice(bodyEnd); } diff --git a/src/text.ts b/src/text.ts index 981ae37..6c95702 100644 --- a/src/text.ts +++ b/src/text.ts @@ -14,3 +14,13 @@ export function slugify(title: string): string { .replace(/-{2,}/g, "-"); return slug.length > 0 ? slug : "artifact"; } + +export function headingSlugify(title: string): string { + const slug = title + .normalize("NFKC") + .toLocaleLowerCase("und") + .replace(/[^\p{L}\p{N}]+/gu, "-") + .replace(/^-+|-+$/g, "") + .replace(/-{2,}/g, "-"); + return slug.length > 0 ? slug : "section"; +} diff --git a/test/accessibility.test.ts b/test/accessibility.test.ts new file mode 100644 index 0000000..e6e3286 --- /dev/null +++ b/test/accessibility.test.ts @@ -0,0 +1,110 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { renderArtifact } from "../src/render.ts"; +import { preflightDocument } from "../src/preflight.ts"; +import { validateComponent } from "../src/components.ts"; +import { contrastRatio } from "../src/design-tokens.ts"; +import { formatZonedTimestamp, resolveLocaleContext } from "../src/locale.ts"; + +const ACCESSIBLE_DOCUMENT = [ + "---", + "title: مراجعة الإشارات", + "lang: ar", + "dir: rtl", + "locale: ar-EG", + "timezone: Asia/Riyadh", + "---", + "# مراجعة الإشارات", + "", + "> [!NOTE] ملخص واضح لا يعتمد على اللون.", + "", + "- [x] اكتملت المراجعة", + "", + "```progress", + '{"label":"التقدم","done":3,"total":4}', + "```", + "", + "```echarts", + '{"description":"ترتفع الإشارة من ثلاث نقاط إلى خمس نقاط خلال يومين.","xAxis":{"type":"category","data":["الاثنين","الثلاثاء"]},"yAxis":{"type":"value"},"series":[{"type":"line","data":[3,5]}]}', + "```", + "", + "```table", + '{"caption":"سجل الإشارات","columns":[{"key":"count","label":"العدد","type":"num"},{"key":"captured","label":"وقت الالتقاط","type":"datetime"}],"rows":[{"count":1234.5,"captured":"2026-08-17T15:00:00Z"}]}', + "```", + "", + "```decisions", + '{"title":"قرار","questions":[{"id":"next","question":"ما الخطوة التالية؟","options":[{"id":"ship","label":"نشر"},{"id":"hold","label":"انتظار"}]}]}', + "```", +].join("\n"); + +test("built-in output exposes landmarks, equivalents, labels, state, and RTL metadata", async () => { + const preflight = await preflightDocument(ACCESSIBLE_DOCUMENT); + assert.deepEqual(preflight.diagnostics.filter((item) => item.severity === "error"), []); + const html = renderArtifact(ACCESSIBLE_DOCUMENT).html; + assert.match(html, //); + assert.match(html, /href="#artifact-main">Skip to main content/); + assert.match(html, /
    /); + assert.match(html, /

    /); + assert.match(html, /
    ]+role="img"[^>]+aria-labelledby="chart-0-summary"/); + assert.match(html, /
    ترتفع الإشارة/); + assert.match(html, /]*>
    سجل الإشارات<\/caption>/); + assert.match(html, /