Skip to content
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -166,14 +167,25 @@ 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`, `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
- Broken specs degrade to inline error boxes; the page always ships
- **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

Expand Down
49 changes: 49 additions & 0 deletions benchmarks/renderer/v1/budgets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"schemaVersion": 1,
"profile": "renderer-linux-container-v1",
"referenceEnvironment": {
"platform": "linux",
"arch": "x64",
"nodeMajor": 24,
"cpuQuotaCores": 2,
"memoryLimitBytes": 4294967296,
"browserName": "chrome",
"browserMajor": 151
},
"sampling": {
"cliSamples": 12,
"browserSamples": 7,
"minimumSamples": 5,
"noiseFloorMs": 250,
"maxRelativeP95Spread": 1
},
"workloads": {
"no-runtime": {
"fixture": "no-runtime.md",
"runtimeBundles": [],
"cliP95Ms": 2000,
"browserUsefulContentMs": 1500,
"browserKeyboardAdditionalMs": 1000,
"warningBytes": 131072,
"hardBytes": 196608
},
"one-chart": {
"fixture": "one-chart.md",
"runtimeBundles": ["vega", "vega-embed"],
"cliP95Ms": 5000,
"browserUsefulContentMs": 3000,
"browserKeyboardAdditionalMs": 1000,
"warningBytes": 1048576,
"hardBytes": 1572864
},
"multi-runtime": {
"fixture": "multi-runtime.md",
"runtimeBundles": ["vega", "vega-embed", "echarts", "mermaid"],
"cliP95Ms": 5000,
"browserUsefulContentMs": 5000,
"browserKeyboardAdditionalMs": 1000,
"warningBytes": 6291456,
"hardBytes": 8388608
}
}
}
34 changes: 34 additions & 0 deletions benchmarks/renderer/v1/multi-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
title: Renderer multi-runtime benchmark
lang: en
dir: ltr
locale: en-US
timezone: UTC
---
# Renderer multi-runtime benchmark

## Vega-Lite trend

```vega-lite
{"description":"Throughput rises from 12 to 21 units across four samples.","width":"container","height":260,"data":{"values":[{"sample":"A","value":12},{"sample":"B","value":15},{"sample":"C","value":18},{"sample":"D","value":21}]},"mark":"line","encoding":{"x":{"field":"sample","type":"ordinal"},"y":{"field":"value","type":"quantitative"}}}
```

## ECharts distribution

```echarts
{"description":"The accepted category is 84 and the rejected category is 16.","xAxis":{"type":"category","data":["accepted","rejected"]},"yAxis":{"type":"value"},"series":[{"type":"bar","data":[84,16]}]}
```

## Mermaid flow

```mermaid
%% summary: Source content passes through preflight, rendering, and publication.
flowchart LR
A[Source] --> B[Preflight]
B --> C[Render]
C --> D[Publish]
```

```decisions
{"title":"Benchmark interaction","questions":[{"id":"continue","question":"Continue?","options":[{"id":"yes","label":"Yes"},{"id":"no","label":"No"}]}]}
```
22 changes: 22 additions & 0 deletions benchmarks/renderer/v1/no-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: Renderer no-runtime benchmark
lang: en
dir: ltr
locale: en-US
timezone: UTC
---
# Renderer no-runtime benchmark

## Summary

```stats
[{"label":"Documents","value":"1,024","delta":"stable","tone":"good"},{"label":"Errors","value":"0","delta":"none","tone":"neutral"}]
```

```table
{"caption":"Build results","columns":[{"key":"name","label":"Name"},{"key":"count","label":"Count","type":"num"}],"rows":[{"name":"accepted","count":1024},{"name":"rejected","count":0}]}
```

```decisions
{"title":"Benchmark interaction","questions":[{"id":"continue","question":"Continue?","options":[{"id":"yes","label":"Yes"},{"id":"no","label":"No"}]}]}
```
18 changes: 18 additions & 0 deletions benchmarks/renderer/v1/one-chart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Renderer one-chart benchmark
lang: en
dir: ltr
locale: en-US
timezone: UTC
---
# Renderer one-chart benchmark

## Trend

```vega-lite
{"description":"Throughput rises from 12 to 21 units across four samples.","width":"container","height":320,"data":{"values":[{"sample":"A","value":12},{"sample":"B","value":15},{"sample":"C","value":18},{"sample":"D","value":21}]},"mark":"line","encoding":{"x":{"field":"sample","type":"ordinal"},"y":{"field":"value","type":"quantitative"}}}
```

```decisions
{"title":"Benchmark interaction","questions":[{"id":"continue","question":"Continue?","options":[{"id":"yes","label":"Yes"},{"id":"no","label":"No"}]}]}
```
42 changes: 36 additions & 6 deletions docs/component-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -36,6 +36,34 @@ 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
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.
Expand Down Expand Up @@ -109,7 +137,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

Expand Down
9 changes: 8 additions & 1 deletion docs/engineering-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -117,6 +118,12 @@ 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.
- 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
Expand Down
2 changes: 1 addition & 1 deletion docs/evidence/governance/redistribution-2026-08-16.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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
Expand Down
77 changes: 77 additions & 0 deletions docs/evidence/renderer/goal-3-accessibility-2026-08-17.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Goal 3 accessibility and internationalization evidence — 2026-08-17

Scope: implementation evidence for `renderer-accessibility-i18n`. It combines the retained
Linux/Chromium automation below with a dated, user-attested Fedora/Orca/Chrome manual
screen-reader observation. It does not establish broad 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}`

## Manual screen-reader observation — 2026-08-18

Aaron Zeng (`aaron.zeng`) opened the served `examples/patterns/accessibility-rtl.md` fixture
from a host device through the VPS loopback SSH tunnel and reported that the full manual
screen-reader checklist passed.

| Field | Observation |
|---|---|
| Operating system | Fedora 44 |
| Screen reader | Orca 50.2 |
| Browser | Chrome 151.0.7922.137 |
| Result | Pass |

The attested checklist covered document language/direction and reading order; skip link,
landmarks, and headings; chart summary and table caption/headers; named controls and exposed
state; keyboard focus through decisions, sorting, theme, and comments; and understandable
state-change announcements without relying on color. No OS-level recording or assistive-
technology transcript was collected, so this record is a named human attestation rather than
an independently replayable trace.

This closes the packet's mandatory manual assistive-technology cell. The observation does not
declare Fedora, Orca, or this Chrome build a broadly supported matrix, and it does not supply
physical-mobile or other browser/OS coverage.
Loading
Loading