diff --git a/.aisteering/LANGUAGE.md b/.aisteering/LANGUAGE.md new file mode 100644 index 00000000..09e51aee --- /dev/null +++ b/.aisteering/LANGUAGE.md @@ -0,0 +1,61 @@ +# Project language + +## Binding contract + +The explicit record connecting a Penpot library component to repository source, package exports, variant semantics, and an immutable source pin. + +## Source pin + +An immutable Git commit plus a SHA-256 checksum of the bound component source file. The commit identifies the upstream repository state; the checksum detects source drift on the feature branch. + +## Design identity + +The Penpot team, project, file, component, and variant identifiers used to resolve an exact design asset. + +## Canonical component ID + +A portable identifier such as `ai.confirmation` shared by Penpot metadata and the repository manifest. + +## Binding contract version + +An integer that versions the metadata shape and resolution semantics. It is separate from the source pin and from future package release versions. + +## Variant mapping + +An exhaustive translation from Penpot variant values to React state, approval data, compound exports, or theme context. Missing mappings are errors; the tooling does not guess. + +## Heterogeneous variant axes + +Component-owned Penpot dimensions such as `State`, `Variant`, `Header`, `Visibility`, or `Change`, followed by `Theme`. The binding validator derives the required Cartesian product from each component's declared axes rather than imposing one shared state model. + +## Penpot page export + +A versioned JSON scene graph captured through Penpot MCP. It records a board's native nodes, linked component identities, reference dimensions, and explicit implementation contract without embedding an expected screenshot or generated code. + +## Implementation contract + +Board metadata that names the target source file, route, theme, and binding manifest for a design-to-code task. Missing contract values block implementation rather than selecting defaults. + +## Blind implementation run + +An evaluation in which an agent receives only the implementation skill, raw design export, binding manifest, target project, and user-level request. It does not receive the design conversation, expected screenshot, or a hand-written solution. + +## Section gallery + +A consumer-lab board containing linked instances for every mapped variant in one rollout section. A section gallery is proof of library consumption, not a source for detached design copies. + +## Reference export + +The explicit Penpot shape UUID and PNG format recorded for visual verification of a component family. It points to the stable variant container rendered and inspected during the release gate. + +## Composite binding dependency + +A linked library instance embedded inside another bound component, such as Code Block or Stack Trace inside Sandbox. The nested instance remains attached to its source component and is never replaced by a detached copy. + +## Device-conditioned state + +A visual state whose live data depends on browser media APIs, permissions, or hardware enumeration. Penpot proofs use named synthetic devices and record permission, loading, and error behavior explicitly rather than querying the designer's host. + +## Animated media snapshot + +A deterministic raster captured from an animated source at a declared component state. Persona snapshots preserve the pinned Rive visual for each style, runtime state, and theme while the React implementation remains responsible for animation and model playback. diff --git a/.aisteering/README.md b/.aisteering/README.md new file mode 100644 index 00000000..15f2da1d --- /dev/null +++ b/.aisteering/README.md @@ -0,0 +1,11 @@ +# Repository steering + +This directory is the source of truth for agent-facing repository knowledge added by the Penpot design-binding work. + +- `LANGUAGE.md` defines stable binding terminology. +- `VALIDATION.md` defines runnable validation commands. +- `policy-exceptions.md` records approved exceptions to explicit-behavior policy. +- `plans/completed/penpot-design-bindings.md` records the completed implementation checklist. +- `scratchpads/` contains selective durable working notes. + +Upstream project conventions remain authoritative for existing AI Elements source. This steering layer must not silently change component behavior or package APIs. diff --git a/.aisteering/VALIDATION.md b/.aisteering/VALIDATION.md new file mode 100644 index 00000000..0c52f714 --- /dev/null +++ b/.aisteering/VALIDATION.md @@ -0,0 +1,100 @@ +# Validation + +## Local component documentation + +```bash +pnpm --filter docs dev --hostname 127.0.0.1 --port 3000 +``` + +Open `http://localhost:3000/en/components/`. The explicit English prefix is required because the docs application stores every rendered route under `[lang]`; hiding the only locale causes a rewrite/redirect loop with the pinned Next.js and Fumadocs versions. The Chatbot release smoke-checks all 19 routes from `attachments` through `tool`, including the source-ahead `question` route. The Code release smoke-checks `agent`, `artifact`, `code-block`, `commit`, `environment-variables`, `file-tree`, `jsx-preview`, `package-info`, `sandbox`, `schema-display`, `snippet`, `stack-trace`, `terminal`, `test-results`, and `web-preview`. The Voice release smoke-checks `audio-player`, `mic-selector`, `persona`, `speech-input`, `transcription`, and `voice-selector`. The Utilities release smoke-checks `image` and `open-in-chat`. + +## Penpot binding tooling + +```bash +pnpm penpot:bindings:test +pnpm penpot:bindings:validate +pnpm penpot:bindings:resolve penpot/fixtures/confirmation-component-lab.json +pnpm penpot:bindings:resolve penpot/fixtures/code-component-lab.json +pnpm penpot:bindings:resolve penpot/fixtures/voice-component-lab.json +pnpm penpot:bindings:resolve penpot/fixtures/utilities-component-lab.json +``` + +The test command covers valid and intentionally corrupted manifests plus successful and failed instance resolution. It synthesizes and resolves every declared variant combination; the Chatbot, Code, Voice, and Utilities manifest resolves 286 variants across 42 components. The validation command verifies the checked-in manifest against the current repository, source checksum, exhaustive runtime/type exports, documentation/example paths, required Penpot identities, PNG reference targets, and exhaustive variant mappings. The resolve command translates a Penpot instance snapshot into explicit React imports and render inputs. + +## Utilities component behavior + +```bash +cd packages/elements +pnpm exec vitest run __tests__/image.test.tsx __tests__/open-in-chat.test.tsx +``` + +The Utilities release runs two focused browser suites, including the Open in Chat example query regression. Reference matrices are stored under `penpot/exports/utilities/` after visual inspection. + +## Voice component behavior + +```bash +cd packages/elements +pnpm exec vitest run \ + __tests__/audio-player.test.tsx __tests__/mic-selector.test.tsx \ + __tests__/persona.test.tsx __tests__/speech-input.test.tsx \ + __tests__/transcription.test.tsx __tests__/voice-selector.test.tsx +``` + +The Voice release runs six focused browser suites. Reference matrices are stored under `penpot/exports/voice/` after visual inspection. Device and media tests use deterministic browser mocks; they do not require a host microphone, permission prompt, audio output, or a live voice service. + +The Audio Player suite includes an SSR regression assertion for the media element's default `tabindex="-1"`. Runtime validation must also load `/en/components/audio-player` in a real browser and confirm hydration produces no console errors. + +## Code component behavior + +```bash +cd packages/elements +pnpm exec vitest run \ + __tests__/agent.test.tsx __tests__/artifact.test.tsx \ + __tests__/code-block.test.tsx __tests__/commit.test.tsx \ + __tests__/environment-variables.test.tsx __tests__/file-tree.test.tsx \ + __tests__/jsx-preview.test.tsx __tests__/package-info.test.tsx \ + __tests__/sandbox.test.tsx __tests__/schema-display.test.tsx \ + __tests__/snippet.test.tsx __tests__/stack-trace.test.tsx \ + __tests__/terminal.test.tsx __tests__/test-results.test.tsx \ + __tests__/web-preview.test.tsx +``` + +The Code release runs 15 focused browser suites. Reference matrices are stored under `penpot/exports/code/` after visual inspection. + +## Chatbot component behavior + +```bash +cd packages/elements +pnpm exec vitest run \ + __tests__/attachments.test.tsx __tests__/chain-of-thought.test.tsx \ + __tests__/checkpoint.test.tsx __tests__/confirmation.test.tsx \ + __tests__/context.test.tsx __tests__/conversation.test.tsx \ + __tests__/inline-citation.test.tsx __tests__/message.test.tsx \ + __tests__/model-selector.test.tsx __tests__/plan.test.tsx \ + __tests__/prompt-input.test.tsx __tests__/question.test.tsx \ + __tests__/queue.test.tsx __tests__/reasoning.test.tsx \ + __tests__/shimmer.test.tsx __tests__/sources.test.tsx \ + __tests__/suggestion.test.tsx __tests__/task.test.tsx \ + __tests__/tool.test.tsx +``` + +Run these focused suites for a Chatbot binding release. The Attachments and Checkpoint tests close the two gaps that existed in the pinned source test inventory. + +On Ubuntu 26.04, Playwright 1.58 requires its explicit compatibility override for the one-time browser download: + +```bash +PLAYWRIGHT_HOST_PLATFORM_OVERRIDE=ubuntu24.04-x64 packages/elements/node_modules/.bin/playwright install chromium +``` + +The full Elements suite now uses explicitly sized Canvas fixtures in `controls.test.tsx` and `panel.test.tsx`, avoiding React Flow's zero-size parent warning in browser tests. Run `pnpm test` before a PR; the September 2026 pre-PR run passed all 967 Elements tests and six Penpot demo tests. + +## Live Penpot validation + +1. Read shared `ai-elements/code-binding` metadata from every component variant. +2. Confirm each source pin, checksum, export inventory, documentation path, and variant axis matches `penpot/component-bindings.json`. +3. Verify the `Chatbot Component Lab` gallery (`fdc5d385-a945-806e-8008-864e7d404d82`) contains 76 linked instances from library `f9c80ed1-fe5b-8098-8008-85f67c505174`, with no detached copies or variant errors. +4. Confirm the gallery has 38 Light and 38 Dark instances and all 18 newly ported component families. Confirmation remains in its preserved six-instance gallery. +5. Export and inspect the PNG target recorded in each binding's `design.referenceExport`. +6. Verify `Code Component Lab` (`cebd822e-5596-8078-8008-865a8fdf6abb`) contains 98 linked instances: 49 Light, 49 Dark, 15 families, zero detached proof shapes, and no foreign-library roots. +7. Verify `Voice Component Lab` (`8633c2af-b930-8087-8008-87a4b8475c0f`) contains 100 linked instances across six families, zero detached proof shapes, no foreign-library roots, and no file validation errors. +8. Verify `Utilities Component Lab` (`ef8ecfcc-e8fd-8021-8008-a99e412158e1`) contains six linked instances across Image and Open in Chat, three Light and three Dark, no detached proof shapes, no foreign-library roots, and no file validation errors. diff --git a/.aisteering/plans/active/ai-elements-penpot-library-rollout.md b/.aisteering/plans/active/ai-elements-penpot-library-rollout.md new file mode 100644 index 00000000..93313ea5 --- /dev/null +++ b/.aisteering/plans/active/ai-elements-penpot-library-rollout.md @@ -0,0 +1,113 @@ +# AI Elements Penpot library rollout + +Date: 2026-08-22 + +## Objective + +Port the remaining AI Elements component catalog into the shared Penpot `AI Elements` file and bind every design component to the pinned React implementation. Execute one documentation section at a time because all sections mutate the same live Penpot library and binding manifest. + +## Shared contract + +- Repository: `/home/igor/Documents/code/design-system/ai-sdk-elements-shared` +- Upstream baseline revision: `6a9d5b1822ffb10bba4bd97175f01edd7d8651cd` +- Audio Player SSR fix revision: `e3292f6b2bb6c247fde4d74f716d3535a07f43e4` +- Penpot library file: `f9c80ed1-fe5b-8098-8008-85f67c505174` +- Consumer lab file: `f9c80ed1-fe5b-8098-8008-8618092b66d2` +- Binding manifest: `penpot/component-bindings.json` +- Canonical IDs: `ai.` +- Every component requires an exact source-file SHA-256, code export inventory, Penpot UUIDs, explicit variant/state mapping, Light and Dark coverage where the component renders UI, focused tests, binding validation, and a consumer-file proof instance. +- Linked Penpot component instances remain opaque during page implementation and must resolve through the binding manifest. +- Publish and create a Penpot file version only after the whole section validates. +- Only one section task may mutate Penpot or `penpot/component-bindings.json` at a time. + +## Section tasks + +### 1. Chatbot — complete + +- [x] attachments +- [x] chain-of-thought +- [x] checkpoint +- [x] confirmation +- [x] context +- [x] conversation +- [x] inline-citation +- [x] message +- [x] model-selector +- [x] plan +- [x] prompt-input +- [x] question +- [x] queue +- [x] reasoning +- [x] shimmer +- [x] sources +- [x] suggestion +- [x] task +- [x] tool + +Question is intentionally included from the pinned repository source and docs even though it is source-ahead of the deployed public sidebar. + +### 2. Code — complete + +- [x] agent +- [x] artifact +- [x] code-block +- [x] commit +- [x] environment-variables +- [x] file-tree +- [x] jsx-preview +- [x] package-info +- [x] sandbox +- [x] schema-display +- [x] snippet +- [x] stack-trace +- [x] terminal +- [x] test-results +- [x] web-preview + +Code adds 15 component families, 98 mapped variants, 98 linked consumer instances, and 15 checked reference PNGs. Code Block filenames and code-content layers use Geist Mono in every Light/Dark variant. Artifact composes linked Numbered Code Block instances, while Sandbox composes linked Code Block or Stack Trace instances according to the pinned examples. The preserved 19-entry Chatbot manifest slice retains its pre-Code canonical hash. + +### 3. Voice — complete + +- [x] audio-player +- [x] mic-selector +- [x] persona +- [x] speech-input +- [x] transcription +- [x] voice-selector + +Voice adds six component families, 100 mapped variants, 100 linked consumer instances, and six checked reference PNGs. Persona uses source-faithful Rive snapshots across six visual styles, five runtime states, and both themes. Device-backed selectors use deterministic synthetic devices and voices for design proof while browser permission, enumeration, and media playback remain runtime concerns. + +Audio Player carries a repository-local source pin at `e3292f6b2bb6c247fde4d74f716d3535a07f43e4`. It preserves the upstream component API while emitting the media controller's default `tabindex="-1"` during SSR so `media-chrome` does not introduce a client-only hydration difference. + +### 4. Workflow — staged + +- [ ] canvas +- [ ] connection +- [ ] controls +- [ ] edge +- [ ] node +- [ ] panel +- [ ] toolbar + +### 5. Utilities — complete + +- [x] image +- [x] open-in-chat + +Utilities adds two component families, six mapped variants, six linked consumer instances, and two checked reference PNGs. Image uses the existing repository example image in Light and Dark. Open in Chat covers closed/open menu states and six provider links in both themes. The documented example now supplies `query` to the parent `OpenIn` context, with a focused regression test. + +## Per-section acceptance gates + +- [x] Source and documentation/example inventory is complete. +- [x] Every component has representative Light and Dark Penpot visuals or an explicit documented nonvisual exception. +- [x] Every design component has stable UUID metadata and a manifest entry. +- [x] Manifest source hashes and variant mappings validate mechanically. +- [x] A section gallery in the consumer lab resolves to library components rather than detached copies. +- [x] Reference PNG exports are visually checked. +- [x] Focused tests, binding tests, and binding validation pass. +- [x] `.aisteering/LANGUAGE.md`, scratchpad, and section summary are current. +- [x] Changes are committed on the feature branch before the next section starts. + +## Execution order + +Chatbot → Code → Voice → Utilities → Workflow. Chatbot, Code, Voice, and Utilities are accepted. On 2026-09-19, the user requested that Utilities be finished ahead of Workflow. Utilities is the completed writer slot; Workflow is next after the Utilities commit handoff and must revalidate the 42-binding baseline before writing. diff --git a/.aisteering/plans/completed/mcp-design-implementation-evaluation.md b/.aisteering/plans/completed/mcp-design-implementation-evaluation.md new file mode 100644 index 00000000..ae936f0b --- /dev/null +++ b/.aisteering/plans/completed/mcp-design-implementation-evaluation.md @@ -0,0 +1,24 @@ +# MCP design-to-implementation evaluation + +Date: 2026-08-22 + +## Objective + +Test whether a fresh agent can implement a simple Penpot page from raw MCP artifacts by following a reusable repo-local skill, without access to the design conversation or an expected implementation. + +## Acceptance criteria + +- [x] A new Penpot page contains ordinary layout/text plus linked `ai.confirmation` component instances. +- [x] The page exposes an explicit implementation contract and can be exported through MCP. +- [x] A repo-local skill explains identity checks, binding resolution, layout translation, asset handling, and validation. +- [x] An empty demo project defines its framework, commands, and acceptance tests without containing the solution. +- [x] A fresh agent with no conversation context implements the page using only the skill and raw artifacts. +- [x] The resulting project builds, runs, resolves linked components correctly, and is visually compared against Penpot. +- [x] Skill gaps discovered by the blind run were corrected and revalidated. + +## Isolation rules used + +- The fresh agent did not receive the expected screenshot, intended DOM, or a hand-written solution. +- The fresh agent did not receive the design conversation or conclusions about likely mistakes. +- The handoff contained only the skill path, raw MCP artifact path, binding manifest path, demo-project path, and user-level request. +- The first attempt was committed before corrections and documented in `penpot/evaluations/2026-08-22-blind-first-attempt.md`. diff --git a/.aisteering/plans/completed/penpot-design-bindings.md b/.aisteering/plans/completed/penpot-design-bindings.md new file mode 100644 index 00000000..1b88b3bc --- /dev/null +++ b/.aisteering/plans/completed/penpot-design-bindings.md @@ -0,0 +1,27 @@ +# Penpot design binding and source pin + +Date: 2026-08-22 + +Status: completed + +## Objective + +Resolve the Penpot `ai.confirmation` component to the exact AI Elements React implementation without relying on a moving branch, implicit package version, or inferred variant behavior. + +## Acceptance criteria + +- [x] A checked-in manifest records the immutable upstream Git commit and SHA-256 checksum for `packages/elements/src/confirmation.tsx`. +- [x] The manifest records the Penpot file/component identities and shared-plugin-data location. +- [x] The manifest maps all `State` and `Theme` values explicitly to runtime semantics. +- [x] Validation fails for a mismatched source checksum, missing required variant, missing export, invalid identity, or non-ancestor source revision. +- [x] Tests cover the valid manifest and representative invalid manifests. +- [x] Penpot shared metadata carries the same source pin and binding contract version. +- [x] A linked consumer instance resolves back to the same component and source pin. +- [x] Existing Confirmation component behavior remains unchanged. + +## Boundaries + +- Do not modify the Confirmation component API for metadata needs. +- Do not infer or default missing mappings. +- Do not use a mutable branch name such as `main` as the authoritative source version. +- Do not publish credentials, browser state, or Penpot MCP tokens. diff --git a/.aisteering/policy-exceptions.md b/.aisteering/policy-exceptions.md new file mode 100644 index 00000000..584f0ad1 --- /dev/null +++ b/.aisteering/policy-exceptions.md @@ -0,0 +1,3 @@ +# Policy exceptions + +No exceptions are approved. diff --git a/.aisteering/scratchpads/2026-08-22-penpot-bindings.md b/.aisteering/scratchpads/2026-08-22-penpot-bindings.md new file mode 100644 index 00000000..c4a4fa78 --- /dev/null +++ b/.aisteering/scratchpads/2026-08-22-penpot-bindings.md @@ -0,0 +1,57 @@ +# Penpot binding scratchpad + +Date: 2026-08-22 + +- Repository cloned from `https://github.com/vercel/ai-elements.git`. +- Feature branch: `codex/penpot-design-bindings`. +- Upstream source pin: `6a9d5b1822ffb10bba4bd97175f01edd7d8651cd`. +- Confirmation source SHA-256: `7cc0befed99499ed9010643e7a90ec619180a3f6e79cba5668cfc84126b96a10`. +- Bound source: `packages/elements/src/confirmation.tsx`. +- Package import inside the monorepo: `@repo/elements/confirmation`. +- Penpot file UUID: `f9c80ed1-fe5b-8098-8008-85f67c505174`. +- Penpot component UUID: `a46a744a-db75-80c4-8008-85fc2b7972aa`. +- Shared metadata: namespace `ai-elements`, key `code-binding`. +- Exact Git commit is authoritative; semantic release labels may be added later without weakening the pin. +- Penpot library file was renamed `AI Elements` and published. +- Penpot file version label: `ai.confirmation @ 6a9d5b1`. +- Consumer file: `AI Elements Component Lab` (`f9c80ed1-fe5b-8098-8008-8618092b66d2`), page `f9c80ed1-fe5b-8098-8008-8618092b66d3`. +- Component-lab gallery: `15a57ee0-be05-809c-8008-8618768ee905`. +- Instantiate concrete `LibraryVariantComponent` objects in consumer files. `switchVariant()` silently left new copies on `Request/Light` during this pilot. +- Binding validation: 9 focused tooling tests pass; Confirmation behavior: 17 tests pass. +- Full Elements baseline currently has eight unrelated React Flow zero-size failures in `controls.test.tsx` and `panel.test.tsx`. +- The docs app must use explicit locale routes such as `/en/components/confirmation`; `hideLocale: "default-locale"` loops between the hidden path and `[lang]` route with the pinned Next.js/Fumadocs runtime. +- Implementation-test page: `Implementation Test` (`15a57ee0-be05-809c-8008-86250cb64c01`), board `Approval Workspace` (`15a57ee0-be05-809c-8008-86250cc51d90`). +- Raw page artifact: `penpot/exports/approval-workspace.penpot.json`; implementation skill: `skills/implement-penpot-design`; demo: `apps/penpot-demo`. +- The blind run reproduced exact major geometry and correct bindings but missed compound-content color/alignment without a reference raster. The skill now requires agents to export the board PNG through MCP themselves. +- Source Sans Pro 400/600/700 assets were copied from the local Penpot frontend into the demo, with the Adobe OFL-1.1 license. +- Full library rollout is section-sequenced in `.aisteering/plans/active/ai-elements-penpot-library-rollout.md`: Chatbot, Code, Voice, Workflow, then Utilities. Only one section task may mutate the shared Penpot file or binding manifest at a time. +- Chatbot rollout adds 18 component families and 76 variants to the preserved Confirmation family, for 19 bindings and 82 mapped variants total. +- The new consumer proof is `Chatbot Component Lab` (`fdc5d385-a945-806e-8008-864e7d404d82`): 76 linked instances, 38 Light and 38 Dark, no variant errors or detached copies. +- Question is included from pinned `packages/elements/src/question.tsx` and its repository docs even though it is source-ahead of the deployed public sidebar. +- Every new variant carries shared `ai-elements/code-binding` metadata with exact source SHA-256, complete runtime/type export inventory, docs/examples, and explicit variant axes. +- Attachments and Checkpoint gained focused component tests; all 19 Chatbot suites pass with 430 tests. Binding tooling passes 17 tests and validates all 19 entries. +- Chatbot visuals were accepted by the user after Light/Dark reference exports; Attachments and Suggestion were corrected before acceptance. +- The already-published `AI Elements` library was verified in published state and saved as explicit version `Chatbot @ 6a9d5b1` after all section gates passed. +- Chatbot is the completed writer slot. Code is next and must revalidate the current 19-binding baseline before mutating the shared file or manifest. +- Code rollout adds 15 families and 98 Light/Dark variants, bringing the manifest to 34 bindings and 180 mapped variants without changing the canonical hash of the first 19 entries (`5a8f01e461e89540d0536d8422cab75947dffac639f187b57ffd28c68a6681a4`). +- Code variant axes are component-owned: `State`, `Variant`, `Header`, `Visibility`, or `Change`, plus the required `Theme` axis. The generalized validator/resolver remains contract version 1 and rejects incomplete Cartesian mappings. +- Code consumer proof is `Code Component Lab` (`cebd822e-5596-8078-8008-865a8fdf6abb`): 98 linked instances, 49 Light and 49 Dark, zero detached proof shapes, and all roots from the shared library. +- Sandbox embeds linked Code Block or Stack Trace dependencies for representative execution states; nested components remain attached. +- Code reference PNG matrices are stored under `penpot/exports/code/`. Focused Code behavior passes 15 files and 256 tests; binding tooling passes 18 tests and validates 34 entries. +- The docs production build requires `NODE_ENV=production`; the host session exports `NODE_ENV=development`, which makes Next.js production validation fail on `/_global-error` before route generation. +- Voice is the next serialized writer slot after the verified Code commit handoff. Do not begin Voice from the Code task. +- The already-published `AI Elements` library was verified in published state and saved as explicit version `Code @ 6a9d5b1` after every Code gate passed. +- 2026-08-23: Corrected all Code Block filename and code-content layers to `Geist Mono`, matching the pinned React `font-mono` contract. Published version `Code Block Geist Mono @ 6a9d5b1`; all six direct consumer proofs and nested Sandbox code content updated in place without detaching or changing component identities. +- 2026-08-23: Rebuilt all four Artifact variants around linked Light/Dark Numbered Code Block instances, matching the example's `ArtifactContent > CodeBlock` composition. The remaining Code examples declare no other cross-family dependencies beyond Sandbox, which already links Code Block and Stack Trace. Published version `Artifact composes Code Block @ 6a9d5b1`; consumer proofs remain linked at both levels. +- 2026-08-23: Voice adds six families and 100 variants, bringing the manifest to 40 bindings and 280 mapped variants. Persona contributes 60 source-faithful Rive snapshots across six styles, five states, and Light/Dark; device selectors use deterministic synthetic device/voice data. +- Voice consumer proof is `Voice Component Lab` (`8633c2af-b930-8087-8008-87a4b8475c0f`): 100 linked instances, zero detached or foreign roots, and no file validation errors. Six reference matrices are stored under `penpot/exports/voice/`. +- Voice focused behavior passes six files and 220 tests; binding tooling passes 19 tests and validates 40 entries. The first 34 manifest entries and the existing Confirmation binding retain their pre-Voice canonical hashes. +- The already-published `AI Elements` library remains published and the validated file state is pinned as version `Voice @ 6a9d5b1`. +- 2026-08-25: Audio Player now pins `e3292f6b2bb6c247fde4d74f716d3535a07f43e4` / SHA-256 `99b4f7321aac91db54bb8dfa605c3f8326fdcd3f4f71c762a477aea332a4311c`. The server markup emits `tabindex="-1"` before `media-chrome` initializes, closing the observed React hydration mismatch while preserving explicit consumer overrides. +- 2026-08-25: Applied the consumer lab's complete shared-library update queue, reconnected MCP to the current task, and verified the consumer reads the updated Audio Player shared binding from library `f9c80ed1-fe5b-8098-8008-85f67c505174` with zero file validation errors. +- 2026-09-19: Utilities adds Image and Open in Chat as two component families, six mapped variants, and six linked consumer instances in `Utilities Component Lab` (`ef8ecfcc-e8fd-8021-8008-a99e412158e1`). Image has Light/Dark; Open in Chat has Closed/Open × Light/Dark and depicts the six exported provider links. Both reference matrices were exported, inspected, and stored under `penpot/exports/utilities/`. +- The six variants carry shared `ai-elements/code-binding` metadata. The consumer gallery uses library-linked concrete variants, with zero file validation errors and no detached or foreign roots. The library remains published; named version `Utilities @ 6a9d5b1` was saved after validation. +- Image and Open in Chat source files retain upstream revision `6a9d5b1822ffb10bba4bd97175f01edd7d8651cd` and SHA-256 values `91e305eda8139c5ee8024bec75576e0608d7fbf471e1c00395a2bdfda4a65747` and `0370a813ed09e9b23857717cff0704a028049d2cf014dbd11e5f56d05db6db33`. The Confirmation binding hash is unchanged from pre-Utilities HEAD. +- Corrected the repository and skill Open in Chat examples to put the required query on the parent `OpenIn`, not provider items. The focused browser test now renders the example, opens the menu, and checks the encoded ChatGPT prompt. +- Utilities focused behavior passes two files and 19 tests. The full Elements suite passes 49 files and 968 tests; the Penpot demo passes six tests. Binding tooling passes 20 tests and validates 42 entries / 286 mapped variants. The `image` and `open-in-chat` docs routes returned HTTP 200 and displayed their previews; the latter opened all six provider links. Standalone examples TypeScript checking still reports unrelated existing errors in other examples and component files, none in `open-in-chat.tsx`. +- Utilities is the completed writer slot. Workflow is next, after this branch's commit/PR handoff; it should revalidate the 42-binding baseline before mutating the shared library or manifest. diff --git a/.aisteering/summaries/2026-08-22-chatbot-penpot-library-rollout.md b/.aisteering/summaries/2026-08-22-chatbot-penpot-library-rollout.md new file mode 100644 index 00000000..b022bec3 --- /dev/null +++ b/.aisteering/summaries/2026-08-22-chatbot-penpot-library-rollout.md @@ -0,0 +1,33 @@ +# Chatbot Penpot library rollout + +Date: 2026-08-22 + +## What changed + +- Added the 18 remaining pinned Chatbot component families to the shared `AI Elements` Penpot library while preserving Confirmation. +- Created 76 Light/Dark variants with shared code-binding metadata and a 76-instance linked consumer gallery. +- Expanded `penpot/component-bindings.json` to 19 exhaustive bindings and generalized the validator/resolver for component-specific variant axes. +- Added focused Attachments and Checkpoint tests, the two missing Chatbot suites. + +## Why + +The Chatbot section is the first serialized writer slot in the full AI Elements library rollout. Question is included from the pinned repository source/docs even though it is not yet in the deployed public sidebar. + +## Validation + +- `pnpm penpot:bindings:test` — 17 tests passed. +- `pnpm penpot:bindings:validate` — 19 bindings passed. +- Focused Elements browser suites — 19 files and 430 tests passed. +- Local docs — all 19 `/en/components/` routes rendered with the expected title. +- Live Penpot — 19 component families, 82 mapped variants, 76 new consumer instances, 38 Light/38 Dark, zero detached instances, zero variant errors, and no metadata mismatches. +- Reference PNGs for all 18 new families were exported and visually accepted. +- Penpot publication status was verified and file version `Chatbot @ 6a9d5b1` was saved after the gates passed. + +## Notes and risks + +- The full Elements suite still has the documented eight unrelated React Flow zero-size failures in `controls.test.tsx` and `panel.test.tsx`. +- `pnpm check` is not a clean repository-wide gate: unrelated pre-existing files have formatting drift, and the current Ultracite/Oxlint setup reports an unavailable `import/no-unresolved` rule. Changed files are checked separately with Oxfmt. + +## Next step + +Release the sole Penpot/manifest writer slot to the Code section. Its task must begin from the committed 19-binding baseline and must not alter Chatbot identities or mappings. diff --git a/.aisteering/summaries/2026-08-22-code-penpot-library-rollout.md b/.aisteering/summaries/2026-08-22-code-penpot-library-rollout.md new file mode 100644 index 00000000..91ff9666 --- /dev/null +++ b/.aisteering/summaries/2026-08-22-code-penpot-library-rollout.md @@ -0,0 +1,34 @@ +# Code Penpot library rollout + +Date: 2026-08-22 + +## What changed + +- Added all 15 pinned Code component families to the shared `AI Elements` Penpot library with 98 representative Light/Dark variants. +- Added exact source pins, complete runtime/type export inventories, heterogeneous variant mappings, stable Penpot identities, and shared code-binding metadata to `penpot/component-bindings.json`. +- Added a 98-instance linked `Code Component Lab` consumer proof, its resolver fixture, and 15 checked PNG reference matrices. +- Extended binding inventory and resolution tests from 19 components / 82 variants to 34 components / 180 variants. + +## Why + +Code is the second serialized writer slot in the AI Elements rollout. Component-owned axes preserve each runtime contract without forcing Confirmation's state model onto unrelated families. + +## Validation + +- `pnpm penpot:bindings:test` — 18 tests passed. +- `pnpm penpot:bindings:validate` — 34 bindings passed. +- Focused Code Elements browser suites — 15 files and 256 tests passed. +- `NODE_ENV=production pnpm -C apps/docs build` — passed; all 15 `/en/components/` routes returned HTTP 200 from the production server. +- Live Penpot library — 15 Code families, 98 variants, zero variant errors, zero identity mismatches, and zero metadata mismatches. +- Live consumer lab — 98 linked roots, 49 Light / 49 Dark, zero detached proof shapes, and no foreign-library instances. +- The original 19-entry Chatbot manifest slice retained canonical SHA-256 `5a8f01e461e89540d0536d8422cab75947dffac639f187b57ffd28c68a6681a4`. +- Penpot publication status was verified and file version `Code @ 6a9d5b1` was saved after all gates passed. + +## Notes and risks + +- The full Elements suite retains eight unrelated React Flow zero-size failures in `controls.test.tsx` and `panel.test.tsx`; every focused Code suite passes. +- The host exports `NODE_ENV=development`; production docs validation must set `NODE_ENV=production` explicitly. + +## Next step + +Release the sole Penpot/manifest writer slot to Voice after the Code commit handoff. Do not start Voice from this task. diff --git a/.aisteering/summaries/2026-08-22-local-component-lab.md b/.aisteering/summaries/2026-08-22-local-component-lab.md new file mode 100644 index 00000000..ab269990 --- /dev/null +++ b/.aisteering/summaries/2026-08-22-local-component-lab.md @@ -0,0 +1,28 @@ +# Local AI Elements component lab + +Date: 2026-08-22 + +## What changed + +- Made the docs application use explicit locale routes instead of hiding its only locale. +- Documented the local launch command and canonical Confirmation URL. +- Launched and visually verified the real Confirmation documentation preview. + +## Why + +With the pinned Next.js and Fumadocs versions, hiding the default locale caused `/components/confirmation` and its internal `[lang]` rewrite to redirect to one another indefinitely. + +## Validate + +```bash +NODE_ENV=production pnpm --filter docs build +NODE_ENV=production pnpm --filter docs start --hostname 127.0.0.1 --port 3000 +curl -I http://localhost:3000/en/components/confirmation +``` + +The localized page must return `200`; the unprefixed route must redirect once to `/en/components/confirmation`. + +## Notes + +- The local lab URL is `http://localhost:3000/en/components/confirmation`. +- This changes documentation routing only; the Confirmation component and its immutable source pin are unchanged. diff --git a/.aisteering/summaries/2026-08-22-mcp-design-implementation-evaluation.md b/.aisteering/summaries/2026-08-22-mcp-design-implementation-evaluation.md new file mode 100644 index 00000000..f7bf8065 --- /dev/null +++ b/.aisteering/summaries/2026-08-22-mcp-design-implementation-evaluation.md @@ -0,0 +1,31 @@ +# MCP design-to-implementation evaluation summary + +Date: 2026-08-22 + +## What changed + +- Created and exported a simple Penpot approval-workspace page containing three linked Confirmation variants. +- Added the validated `implement-penpot-design` repo skill and export-contract reference. +- Added an isolated Vite/React demo app, export-derived tests, exact Penpot fonts, and browser-ready responsive styling. +- Ran a context-free agent implementation, preserved its first attempt, recorded gaps, and iterated the skill and page. + +## Why + +This proves the intended Penpot MCP → stable component UUID → pinned React component → tested page workflow without relying on the original design conversation. + +## Validate + +- `pnpm penpot:bindings:test` +- `pnpm penpot:bindings:validate` +- `pnpm --filter penpot-demo test` +- `pnpm --filter penpot-demo build` +- Run `pnpm --filter penpot-demo dev --port 4175` and inspect `/` at 1280×800 and 720×900. + +## Notes and risks + +- The page export explicitly contracts the Light theme. Dark Confirmation variants remain bound and validated in the component library. +- A live Penpot MCP connection is needed for agents to export their own reference raster; without one, pixel-level comparison must be reported as blocked. + +## Next step + +Repeat the same blind workflow with another AI Elements component and a second page export to test whether the skill generalizes beyond Confirmation. diff --git a/.aisteering/summaries/2026-08-22-penpot-binding-tooling.md b/.aisteering/summaries/2026-08-22-penpot-binding-tooling.md new file mode 100644 index 00000000..b6c313a7 --- /dev/null +++ b/.aisteering/summaries/2026-08-22-penpot-binding-tooling.md @@ -0,0 +1,34 @@ +# Penpot binding tooling summary + +Date: 2026-08-22 + +## What changed + +- Added a checked-in `ai.confirmation` binding manifest with exact Penpot identities, exhaustive variant mappings, upstream commit pin, and source checksum. +- Added validation and resolution tools plus nine focused tests. +- Added a live component-lab snapshot fixture covering all six linked variants. +- Preserved the existing Confirmation React implementation and API. + +## Why + +The design-to-code bridge must resolve to an immutable implementation revision and reject unknown components, variants, exports, or source drift rather than guessing. + +## Validate + +```bash +pnpm penpot:bindings:test +pnpm penpot:bindings:validate +pnpm penpot:bindings:resolve penpot/fixtures/confirmation-component-lab.json +pnpm --filter @repo/elements exec vitest run __tests__/confirmation.test.tsx +``` + +## Notes and risks + +- The full Elements suite has eight unrelated baseline failures in Canvas controls/panel tests caused by a zero-size React Flow parent. +- The authoritative version is Git commit `6a9d5b1822ffb10bba4bd97175f01edd7d8651cd` plus the checked source checksum. +- Semantic package or design release labels can be added later without replacing the immutable pin. + +## Next steps + +- Extend the manifest and Penpot library component-by-component. +- Automate snapshot extraction from Penpot MCP when a stable noninteractive transport is available. diff --git a/.aisteering/summaries/2026-08-23-artifact-code-block-composition.md b/.aisteering/summaries/2026-08-23-artifact-code-block-composition.md new file mode 100644 index 00000000..3344a5cb --- /dev/null +++ b/.aisteering/summaries/2026-08-23-artifact-code-block-composition.md @@ -0,0 +1,28 @@ +# Artifact Code Block composition correction + +Date: 2026-08-23 + +## What changed + +- Replaced the replicated code rectangle and four text layers in every Artifact variant with a linked Numbered Code Block instance. +- Used Light/Dark Code Block variants to match the containing Artifact theme and retained the example's borderless, headerless slot through instance overrides. +- Propagated the published update to all four linked consumer-lab Artifact proofs and refreshed `penpot/exports/code/artifact.png`. +- Saved Penpot version `Artifact composes Code Block @ 6a9d5b1`. + +## Why + +The pinned Artifact example composes `CodeBlock` inside `ArtifactContent`. The previous Penpot implementation copied its appearance with primitive layers, so shared Code Block typography and visual updates could not propagate. + +## Validation + +- Source-composition audit: Artifact and Sandbox are the only Code examples that import another Code-family component; Sandbox was already correctly linked. +- Live shared library: four Artifact variants, one linked Code Block head per variant, zero replicated Artifact content layers, zero Penpot file validation errors. +- Live consumer lab: four original Artifact roots remain linked; every nested Code Block remains linked to the correct Light/Dark Numbered UUID. +- Focused Artifact behavior: 14 tests passed. +- Binding tooling: 18 tests passed and 34 bindings validated. + +## Notes + +- Artifact, Code Block, variant, manifest, source-pin, and consumer-proof UUIDs are unchanged. +- The React API and repository binding manifest are unchanged. +- Voice retains the next shared Penpot writer slot. diff --git a/.aisteering/summaries/2026-08-23-code-block-mono-font.md b/.aisteering/summaries/2026-08-23-code-block-mono-font.md new file mode 100644 index 00000000..b7cc2f83 --- /dev/null +++ b/.aisteering/summaries/2026-08-23-code-block-mono-font.md @@ -0,0 +1,25 @@ +# Code Block monospace correction + +Date: 2026-08-23 + +## What changed + +- Updated Code Block filenames and code-content layers across all six Light/Dark shared-library variants to Geist Mono while preserving existing weights. +- Propagated the published-library update to linked consumer-lab proofs, including nested Sandbox Code Blocks. +- Refreshed `penpot/exports/code/code-block.png` and saved Penpot version `Code Block Geist Mono @ 6a9d5b1`. + +## Why + +The pinned React source applies `font-mono` to `CodeBlockFilename` and the rendered `code` element; the Penpot components previously used Source Sans Pro. + +## Validation + +- Live shared library: 34/34 targeted filename and code-content layers use Geist Mono, with no target overflow. +- Live consumer lab: all six Code Block proof roots remain linked to their original UUIDs; 66/66 direct and nested targets use Geist Mono. +- `pnpm penpot:bindings:test` +- `pnpm penpot:bindings:validate` + +## Notes + +- The binding manifest, source pins, variant mappings, and public React API are unchanged. +- Voice retains the next shared Penpot writer slot; this correction did not begin Voice work. diff --git a/.aisteering/summaries/2026-08-23-voice-penpot-library-rollout.md b/.aisteering/summaries/2026-08-23-voice-penpot-library-rollout.md new file mode 100644 index 00000000..045354c8 --- /dev/null +++ b/.aisteering/summaries/2026-08-23-voice-penpot-library-rollout.md @@ -0,0 +1,34 @@ +# Voice Penpot library rollout + +Date: 2026-08-23 + +## What changed + +- Added the six pinned Voice component families to the shared `AI Elements` Penpot library with 100 representative variants. +- Added exact source pins, public export inventories, explicit state mappings, stable Penpot identities, and shared code-binding metadata to `penpot/component-bindings.json`. +- Added a 100-instance linked `Voice Component Lab` consumer proof, its resolver fixture, and six checked PNG reference matrices. +- Extended binding inventory and resolution tests from 34 components / 180 variants to 40 components / 280 variants. + +## Why + +Voice is the third serialized writer slot in the AI Elements rollout. The design contract represents media controls, device/voice selection, transcription timing, speech capture, and Persona animation states without depending on the designer's host devices or live media services. + +## Validation + +- `pnpm penpot:bindings:test` — 19 tests passed. +- `pnpm penpot:bindings:validate` — 40 bindings passed. +- Focused Voice browser suites — six files and 220 tests passed. +- All six local documentation routes returned HTTP 200. +- Live Penpot library — six Voice families, 100 variants, identical metadata within every family, and zero file validation errors. +- `Voice Component Lab` — 100 linked instances, zero detached roots, and zero foreign-library roots. +- Penpot publication remained active and file version `Voice @ 6a9d5b1` was pinned after all Voice gates passed. + +## Notes and risks + +- Browser microphone enumeration, permission failures, and real audio playback remain runtime-only behaviors; Penpot uses explicit synthetic content and the manifest records nonvisual device failure states. +- Persona preserves the pinned Rive appearance as deterministic state snapshots. Animation timing and model playback remain owned by the React implementation. +- The first 34 manifest entries and the existing Confirmation binding are byte-semantically unchanged. + +## Next step + +Release the sole Penpot/manifest writer slot to Workflow after the Voice commit handoff. diff --git a/.aisteering/summaries/2026-08-25-audio-player-penpot-sync.md b/.aisteering/summaries/2026-08-25-audio-player-penpot-sync.md new file mode 100644 index 00000000..c9058bd5 --- /dev/null +++ b/.aisteering/summaries/2026-08-25-audio-player-penpot-sync.md @@ -0,0 +1,10 @@ +# Audio Player hydration and Penpot synchronization + +Date: 2026-08-25 + +- Added an SSR regression test and default `tabIndex={-1}` to `AudioPlayerElement`, matching the client mutation performed by `media-chrome` and removing the hydration mismatch. +- Repinned `ai.audio-player` to commit `e3292f6b2bb6c247fde4d74f716d3535a07f43e4` and source SHA-256 `99b4f7321aac91db54bb8dfa605c3f8326fdcd3f4f71c762a477aea332a4311c` in both the manifest and Penpot shared metadata. +- Applied the component lab's pending shared-library updates and reconnected the current MCP session. The consumer now reports no pending updates and zero Penpot validation errors. +- Validate with `pnpm --filter @repo/elements exec vitest run __tests__/audio-player.test.tsx`, `pnpm penpot:bindings:test`, `pnpm penpot:bindings:validate`, and `NODE_ENV=production pnpm --filter docs build`. +- Browser proof: `/en/components/audio-player` renders three media elements with `tabindex="-1"` and no console errors. +- Known tooling note: repository-wide `pnpm check` still reports the existing 19-file formatting baseline; the changed Audio Player files pass the pinned `oxlint` binary and targeted Ultracite formatting. diff --git a/.aisteering/summaries/2026-09-19-penpot-pr-handoff.md b/.aisteering/summaries/2026-09-19-penpot-pr-handoff.md new file mode 100644 index 00000000..45035de7 --- /dev/null +++ b/.aisteering/summaries/2026-09-19-penpot-pr-handoff.md @@ -0,0 +1,27 @@ +# Penpot binding PR handoff + +Date: 2026-09-19 +Status: pr-open + +## What changed + +- Added explicit dimensions to the Controls and Panel React Flow test fixtures, resolving the eight zero-size warnings that failed the full suite. No production component API changed. +- Cleaned branch-owned whitespace and documented the now-green suite. +- Pushed commit `2af8fa9` to `igor-im/ai-elements:codex/penpot-design-bindings`. +- Opened [upstream PR #495](https://github.com/vercel/ai-elements/pull/495) and [fork PR #1](https://github.com/igor-im/ai-elements/pull/1). + +## Validation + +- `pnpm test`: 967 Elements tests and six Penpot demo tests passed. +- `pnpm penpot:bindings:test`: 19 tests passed; `pnpm penpot:bindings:validate`: 40 bindings passed. +- Code and Voice consumer fixtures resolved. +- `NODE_ENV=production pnpm build --filter ai-elements --filter docs`: passed. +- Production browser at `/en/components/audio-player`: three audio elements have `tabindex="-1"`; no hydration error. Local analytics/prefetch endpoints returned 404s. +- `git diff --check origin/main...HEAD`: passed. +- `pnpm check` remains red on 16 pre-existing or non-CI formatting targets, including pinned upstream files and design artifacts; do not bulk-format pinned source because it would invalidate source hashes. + +## Blockers and next step + +- `vercel/ai-elements/main` cannot be merged by the authenticated account: it has read-only repository permission, requires Vercel review, and its Vercel deployment check currently fails at an authorization handoff. +- The fork PR is clean, but merging it into `igor-im/ai-elements/main` was rejected by the approval guard because that separate destination was not explicitly authorized. Do not retry indirectly; obtain explicit user approval for the fork destination if desired. +- A separate Utilities task owns the uncommitted rollout-plan edit. Do not include that edit in this PR handoff or overwrite its Penpot/manifest work. diff --git a/.aisteering/summaries/2026-09-19-utilities-penpot-library-rollout.md b/.aisteering/summaries/2026-09-19-utilities-penpot-library-rollout.md new file mode 100644 index 00000000..844f00a6 --- /dev/null +++ b/.aisteering/summaries/2026-09-19-utilities-penpot-library-rollout.md @@ -0,0 +1,27 @@ +# Utilities Penpot library rollout + +Date: 2026-09-19 +Status: pr-open + +## What changed + +- Added Image and Open in Chat to the published `AI Elements` Penpot library with two and four Light/Dark variants respectively. Open in Chat covers closed/open states and six provider links. +- Bound both families to exact pinned source hashes and complete public exports, with shared metadata on all six variants. The upstream component source files remain untouched. +- Added six linked consumer proofs in `Utilities Component Lab`, a resolver fixture, and two visually checked PNG matrices. The library was saved as version `Utilities @ 6a9d5b1`. +- Corrected both Open in Chat examples to supply `query` to the parent context, with a regression test for the resulting ChatGPT URL. +- Updated the rollout plan and validation map to 42 component bindings / 286 mapped variants. + +## Validation + +- Binding tooling: 20 tests passed; manifest validation: 42 entries passed; all six consumer proofs resolved. +- Focused Utilities browser suites: 19 tests passed. Full Elements suite: 968 tests passed. Penpot demo: six tests passed. +- Local `/en/components/image` and `/en/components/open-in-chat` returned HTTP 200; Image rendered its preview and Open in Chat opened six provider items. +- Live library and consumer files each returned zero Penpot validation errors. All six consumer instances remain linked to the expected shared-library variants. +- Pinned Image/Open in Chat SHA-256 values and the pre-Utilities Confirmation binding hash were verified unchanged. + +## Risks and handoff + +- The Image bitmap is the existing repository example asset; design matrices are illustrative and do not replace generated-image runtime behavior. Open in Chat links are visual states; provider URL construction remains in React. +- Standalone examples TypeScript checking reports existing errors in unrelated examples and component files, but none in the corrected Open in Chat example. The complete CI test suites pass. +- Existing upstream PR `vercel/ai-elements#495` and fork PR `igor-im/ai-elements#1` target this feature branch; an update to the branch carries Utilities into those PRs. Upstream merge still requires Vercel review and is not claimed here. +- Release the sole Penpot/manifest writer slot to Workflow after the Utilities branch commit and PR update. diff --git a/.gitignore b/.gitignore index d85bc872..ff75bc0c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ node_modules # Testing coverage **/__screenshots__ +.playwright-cli/ # Turbo .turbo @@ -42,4 +43,4 @@ yarn-error.log* .source # Next.js -next-env.d.ts \ No newline at end of file +next-env.d.ts diff --git a/apps/docs/lib/geistdocs/i18n.ts b/apps/docs/lib/geistdocs/i18n.ts index d215017a..ca020f47 100644 --- a/apps/docs/lib/geistdocs/i18n.ts +++ b/apps/docs/lib/geistdocs/i18n.ts @@ -5,7 +5,7 @@ import { translations } from "@/geistdocs"; export const i18n = defineI18n({ defaultLanguage: "en", - hideLocale: "default-locale", + hideLocale: "never", languages: Object.keys(translations), }); diff --git a/apps/penpot-demo/index.html b/apps/penpot-demo/index.html new file mode 100644 index 00000000..c82395a8 --- /dev/null +++ b/apps/penpot-demo/index.html @@ -0,0 +1,13 @@ + + + + + + + Approval workspace + + +
+ + + diff --git a/apps/penpot-demo/package.json b/apps/penpot-demo/package.json new file mode 100644 index 00000000..9bbb9575 --- /dev/null +++ b/apps/penpot-demo/package.json @@ -0,0 +1,34 @@ +{ + "name": "penpot-demo", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "tsc --noEmit && vite build", + "dev": "vite --host 127.0.0.1", + "test": "vitest run" + }, + "dependencies": { + "@repo/elements": "workspace:*", + "@repo/shadcn-ui": "workspace:*", + "lucide-react": "^0.577.0", + "react": "19.2.3", + "react-dom": "19.2.3" + }, + "devDependencies": { + "@repo/typescript-config": "workspace:*", + "@tailwindcss/postcss": "^4.1.17", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/node": "^25.3.5", + "@types/react": "19.2.8", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.2", + "jsdom": "^26.1.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.17", + "typescript": "^5.9.3", + "vite": "^7.1.2", + "vitest": "^4.0.17" + } +} diff --git a/apps/penpot-demo/postcss.config.mjs b/apps/penpot-demo/postcss.config.mjs new file mode 100644 index 00000000..c2ddf748 --- /dev/null +++ b/apps/penpot-demo/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/apps/penpot-demo/public/favicon.svg b/apps/penpot-demo/public/favicon.svg new file mode 100644 index 00000000..87f03164 --- /dev/null +++ b/apps/penpot-demo/public/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/penpot-demo/public/fonts/LICENSE.md b/apps/penpot-demo/public/fonts/LICENSE.md new file mode 100644 index 00000000..69fa3e45 --- /dev/null +++ b/apps/penpot-demo/public/fonts/LICENSE.md @@ -0,0 +1,93 @@ +Copyright 2010-2024 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/apps/penpot-demo/public/fonts/source-sans-pro-bold.woff2 b/apps/penpot-demo/public/fonts/source-sans-pro-bold.woff2 new file mode 100644 index 00000000..3176912e Binary files /dev/null and b/apps/penpot-demo/public/fonts/source-sans-pro-bold.woff2 differ diff --git a/apps/penpot-demo/public/fonts/source-sans-pro-regular.woff2 b/apps/penpot-demo/public/fonts/source-sans-pro-regular.woff2 new file mode 100644 index 00000000..bfb9ef57 Binary files /dev/null and b/apps/penpot-demo/public/fonts/source-sans-pro-regular.woff2 differ diff --git a/apps/penpot-demo/public/fonts/source-sans-pro-semibold.woff2 b/apps/penpot-demo/public/fonts/source-sans-pro-semibold.woff2 new file mode 100644 index 00000000..51bb81ff Binary files /dev/null and b/apps/penpot-demo/public/fonts/source-sans-pro-semibold.woff2 differ diff --git a/apps/penpot-demo/src/App.test.tsx b/apps/penpot-demo/src/App.test.tsx new file mode 100644 index 00000000..3a3cacba --- /dev/null +++ b/apps/penpot-demo/src/App.test.tsx @@ -0,0 +1,80 @@ +import { cleanup, render, screen } from "@testing-library/react"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +import design from "../../../penpot/exports/approval-workspace.penpot.json"; +import { App } from "./App"; + +const nativeCopy = design.board.children.flatMap((node) => + node.type === "text" && node.text?.characters ? [node.text.characters] : [] +); + +afterEach(cleanup); + +describe("Penpot implementation contract", () => { + it("renders every native text node from the exported board", () => { + render(); + + for (const text of nativeCopy) { + expect(screen.getAllByText(text).length).toBeGreaterThan(0); + } + }); + + it("renders one design-system alert for each linked instance", () => { + render(); + + expect(screen.getAllByRole("alert")).toHaveLength(design.instances.length); + }); + + it("uses accessible page, navigation, and details landmarks", () => { + render(); + + expect( + screen.getByRole("heading", { level: 1, name: "Approval workspace" }) + ).toBeInTheDocument(); + expect( + screen.getByRole("navigation", { name: "Primary" }) + ).toBeInTheDocument(); + expect(screen.getByRole("link", { name: "Workspace" })).toHaveAttribute( + "aria-current", + "page" + ); + expect( + screen.getByRole("region", { name: "Run details" }) + ).toBeInTheDocument(); + }); + + it("renders the resolved request controls and response states", () => { + render(); + + expect(screen.getByRole("button", { name: "Reject" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Approve" })).toBeInTheDocument(); + expect( + screen.getByText("You approved this tool execution") + ).toBeInTheDocument(); + expect( + screen.getByText("You rejected this tool execution") + ).toBeInTheDocument(); + }); + + it("keeps each resolved response icon and message in one content row", () => { + render(); + + for (const message of [ + "You approved this tool execution", + "You rejected this tool execution", + ]) { + const content = screen.getByText(message).closest(".decision-content"); + + expect(content).toBeInTheDocument(); + expect(content?.querySelector("svg")).toBeInTheDocument(); + } + }); + + it("imports the pinned Confirmation implementation instead of redrawing it", () => { + const source = readFileSync(resolve(process.cwd(), "src/App.tsx"), "utf8"); + + expect(source).toContain('from "@repo/elements/confirmation"'); + }); +}); diff --git a/apps/penpot-demo/src/App.tsx b/apps/penpot-demo/src/App.tsx new file mode 100644 index 00000000..250b582f --- /dev/null +++ b/apps/penpot-demo/src/App.tsx @@ -0,0 +1,165 @@ +import { + Confirmation, + ConfirmationAccepted, + ConfirmationAction, + ConfirmationActions, + ConfirmationRejected, + ConfirmationRequest, + ConfirmationTitle, +} from "@repo/elements/confirmation"; +import { CheckIcon, XIcon } from "lucide-react"; + +const Sidebar = () => ( + +); + +const PendingApproval = () => ( +
+
+

Pending approval

+

Requires your decision

+
+ + + + + This tool wants to delete the file /tmp/example.txt. Do + you approve this action? + + + + Reject + Approve + + +
+); + +const RecentDecisions = () => ( +
+
+

Recent decisions

+

Last 15 minutes

+
+ +
+ + + + + + + + + + + + + + + + + +
+
+); + +const RunDetails = () => ( + +); + +export const App = () => ( +
+ + +
+
+

TOOL GOVERNANCE

+

Approval workspace

+

+ Review pending tool calls and recent decisions. +

+ +
+
+
+ +
+
+ + +
+ +
+ +
+

Decisions are logged and attached to the run audit trail.

+

Updated just now

+
+
+
+); diff --git a/apps/penpot-demo/src/index.css b/apps/penpot-demo/src/index.css new file mode 100644 index 00000000..c624937c --- /dev/null +++ b/apps/penpot-demo/src/index.css @@ -0,0 +1,507 @@ +@import "tailwindcss"; + +@source "./**/*.{ts,tsx}"; +@source "../../../packages/elements/src/**/*.{ts,tsx}"; +@source "../../../packages/shadcn-ui/**/*.{ts,tsx}"; + +@font-face { + font-family: "Source Sans Pro"; + src: url("/fonts/source-sans-pro-regular.woff2") format("woff2"); + font-style: normal; + font-weight: 400; + font-display: swap; +} + +@font-face { + font-family: "Source Sans Pro"; + src: url("/fonts/source-sans-pro-semibold.woff2") format("woff2"); + font-style: normal; + font-weight: 600; + font-display: swap; +} + +@font-face { + font-family: "Source Sans Pro"; + src: url("/fonts/source-sans-pro-bold.woff2") format("woff2"); + font-style: normal; + font-weight: 700; + font-display: swap; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-ring: var(--ring); +} + +:root { + --background: #ffffff; + --foreground: #18181b; + --card: #ffffff; + --card-foreground: #18181b; + --primary: #18181b; + --primary-foreground: #fafafa; + --muted: #f4f4f5; + --muted-foreground: #71717a; + --accent: #f4f4f5; + --accent-foreground: #18181b; + --destructive: #dc2626; + --border: #e4e4e7; + --ring: #a1a1aa; + color-scheme: light; + font-family: "Source Sans Pro", "Noto Sans", sans-serif; +} + +@layer base { + * { + @apply border-border outline-ring/50; + } +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + min-height: 100%; + margin: 0; +} + +body { + background: #f4f4f5; + color: #18181b; +} + +button, +body { + font: inherit; +} + +button:not(:disabled), +a { + cursor: pointer; +} + +.approval-workspace { + display: grid; + grid-template-columns: 240px minmax(0, 1fr); + min-height: 800px; + background: #f4f4f5; +} + +.sidebar { + position: relative; + min-height: 800px; + padding: 40px 32px; + background: #18181b; +} + +.brand-name, +.environment-label, +.environment-region, +.environment-value, +.navigation-item, +.page-eyebrow, +.page-header h1, +.page-subtitle, +.pending-badge, +.run-details dd, +.run-details dt, +.run-details h2, +.section-heading h2, +.section-heading p, +.workspace-footer p { + margin: 0; +} + +.brand-name { + color: #a1a1aa; + font-size: 13px; + font-weight: 700; + line-height: 1.2; +} + +.primary-navigation { + margin-top: 40px; +} + +.primary-navigation ul { + display: flex; + flex-direction: column; + gap: 23px; + margin: 0; + padding: 0; + list-style: none; +} + +.navigation-item { + position: relative; + height: 17px; + color: #a1a1aa; + font-size: 14px; + font-weight: 400; + line-height: 1.2; +} + +.navigation-item a { + color: inherit; + text-decoration: none; +} + +.navigation-item-active { + color: #fafafa; + font-weight: 600; +} + +.navigation-item-active::before { + position: absolute; + top: -8px; + left: -16px; + width: 4px; + height: 32px; + border-radius: 2px; + background: #3b82f6; + content: ""; +} + +.environment { + position: absolute; + top: 684px; + left: 32px; +} + +.environment-label { + color: #71717a; + font-size: 11px; + font-weight: 700; + line-height: 1.2; +} + +.environment-value { + margin-top: 15px; + color: #fafafa; + font-size: 14px; + font-weight: 600; + line-height: 1.2; +} + +.environment-region { + margin-top: 11px; + color: #a1a1aa; + font-size: 12px; + font-weight: 400; + line-height: 1.2; +} + +.workspace-main { + min-width: 0; + min-height: 800px; + padding: 50px 48px 0; +} + +.page-header { + position: relative; + height: 90px; +} + +.page-eyebrow { + color: #2563eb; + font-size: 12px; + font-weight: 700; + line-height: 1.2; +} + +.page-header h1 { + margin-top: 11.6px; + color: #18181b; + font-size: 32px; + font-weight: 700; + line-height: 1.2; +} + +.page-subtitle { + margin-top: 7.6px; + color: #71717a; + font-size: 15px; + font-weight: 400; + line-height: 1.2; +} + +.pending-badge { + position: absolute; + top: 10px; + right: 0; + display: flex; + width: 142px; + height: 34px; + align-items: center; + gap: 10px; + padding: 0 13px; + border: 1px solid #e4e4e7; + border-radius: 17px; + background: #ffffff; + color: #3f3f46; + font-size: 13px; + font-weight: 600; + line-height: 1.2; +} + +.pending-dot { + width: 8px; + height: 8px; + flex: 0 0 8px; + border-radius: 50%; + background: #f59e0b; +} + +.workspace-grid { + display: grid; + grid-template-columns: minmax(0, 640px) 274px; + gap: 30px; + margin-top: 38px; +} + +.section-heading { + display: flex; + align-items: flex-start; + justify-content: space-between; +} + +.pending-heading { + width: min(604px, 100%); +} + +.recent-heading { + width: min(587px, 100%); +} + +.section-heading h2, +.run-details h2 { + color: #27272a; + font-size: 15px; + font-weight: 600; + line-height: 1.2; +} + +.section-heading p { + margin-top: 2px; + color: #a1a1aa; + font-size: 12px; + font-weight: 400; + line-height: 1.2; +} + +.confirmation-card { + width: 100%; +} + +.confirmation-card [data-slot="alert-description"] { + color: #18181b; +} + +.request-card { + height: 96px; + margin-top: 20px; +} + +.confirmation-card code { + display: inline; + border-radius: 3px; + background: #f4f4f5; + padding: 2px 6px; + color: #3f3f46; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 12px; +} + +.recent-decisions { + margin-top: 62px; +} + +.recent-list { + display: flex; + flex-direction: column; + gap: 44px; + margin-top: 20px; +} + +.response-card { + height: 64px; + justify-content: center; +} + +.decision-content { + display: flex; + align-items: center; + gap: 8px; +} + +.decision-icon { + width: 16px; + height: 16px; + flex: 0 0 16px; +} + +.run-details { + height: 252px; + margin-top: 38px; + padding: 23px; + border: 1px solid #e4e4e7; + border-radius: 12px; + background: #ffffff; +} + +.run-details h2 { + color: #18181b; +} + +.run-details dl { + display: flex; + flex-direction: column; + gap: 21px; + margin: 24px 0 0; +} + +.run-details dt { + color: #a1a1aa; + font-size: 10px; + font-weight: 700; + line-height: 1.2; +} + +.run-details dd { + margin-top: 7px; + color: #3f3f46; + font-size: 13px; + font-weight: 400; + line-height: 1.2; +} + +.workspace-footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 124px; + padding-right: 15px; + color: #71717a; + font-size: 12px; + font-weight: 400; + line-height: 1.2; +} + +@media (max-width: 1180px) { + .workspace-grid { + grid-template-columns: minmax(0, 640px); + } + + .run-details { + width: min(100%, 640px); + margin-top: 0; + } + + .workspace-footer { + width: min(100%, 640px); + margin-top: 48px; + } +} + +@media (max-width: 760px) { + .approval-workspace { + grid-template-columns: 1fr; + } + + .sidebar { + min-height: auto; + padding: 24px; + } + + .primary-navigation { + margin-top: 24px; + } + + .primary-navigation ul { + flex-flow: row wrap; + gap: 24px; + } + + .navigation-item-active::before { + top: auto; + bottom: -8px; + left: 0; + width: 100%; + height: 2px; + } + + .environment { + position: static; + display: grid; + grid-template-columns: auto auto; + gap: 4px 16px; + margin-top: 32px; + } + + .environment-label { + grid-column: 1 / -1; + } + + .environment-value, + .environment-region { + margin-top: 0; + } + + .workspace-main { + min-height: auto; + padding: 32px 24px; + } + + .page-header { + height: auto; + } + + .pending-badge { + position: static; + margin-top: 20px; + } + + .workspace-grid { + grid-template-columns: minmax(0, 1fr); + margin-top: 40px; + } + + .recent-decisions { + margin-top: 48px; + } + + .recent-list { + gap: 24px; + } + + .request-card { + height: auto; + min-height: 96px; + } + + .response-card { + height: auto; + min-height: 64px; + } + + .workspace-footer { + flex-direction: column; + align-items: flex-start; + gap: 8px; + padding-right: 0; + } +} diff --git a/apps/penpot-demo/src/main.tsx b/apps/penpot-demo/src/main.tsx new file mode 100644 index 00000000..fddff2a2 --- /dev/null +++ b/apps/penpot-demo/src/main.tsx @@ -0,0 +1,17 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import { App } from "./App"; +import "./index.css"; + +const root = document.querySelector("#root"); + +if (!root) { + throw new Error("Missing #root mount element"); +} + +createRoot(root).render( + + + +); diff --git a/apps/penpot-demo/src/test-setup.ts b/apps/penpot-demo/src/test-setup.ts new file mode 100644 index 00000000..f149f27a --- /dev/null +++ b/apps/penpot-demo/src/test-setup.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/apps/penpot-demo/tsconfig.json b/apps/penpot-demo/tsconfig.json new file mode 100644 index 00000000..6012128d --- /dev/null +++ b/apps/penpot-demo/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "@repo/typescript-config/react-library.json", + "compilerOptions": { + "baseUrl": ".", + "declaration": false, + "declarationMap": false, + "jsx": "react-jsx", + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "paths": { + "@repo/shadcn-ui/*": ["../../packages/shadcn-ui/*"] + }, + "target": "ES2022", + "types": ["vite/client", "vitest/globals"] + }, + "include": ["src", "vite.config.ts"] +} diff --git a/apps/penpot-demo/vite.config.ts b/apps/penpot-demo/vite.config.ts new file mode 100644 index 00000000..904013a3 --- /dev/null +++ b/apps/penpot-demo/vite.config.ts @@ -0,0 +1,18 @@ +import react from "@vitejs/plugin-react"; +import { fileURLToPath, URL } from "node:url"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@repo/shadcn-ui": fileURLToPath( + new URL("../../packages/shadcn-ui", import.meta.url) + ), + }, + }, + test: { + environment: "jsdom", + setupFiles: "./src/test-setup.ts", + }, +}); diff --git a/package.json b/package.json index 27a32a6b..69a6fc72 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,9 @@ "dev": "turbo run dev", "test": "turbo run test", "test:coverage": "turbo run test:coverage", + "penpot:bindings:test": "vitest run scripts/penpot-bindings", + "penpot:bindings:validate": "node scripts/penpot-bindings/validate.mjs", + "penpot:bindings:resolve": "node scripts/penpot-bindings/resolve.mjs", "check": "ultracite check", "fix": "ultracite fix", "bump-deps": "npx npm-check-updates --deep -u -x recharts,react-resizable-panels && pnpm install", diff --git a/packages/elements/__tests__/attachments.test.tsx b/packages/elements/__tests__/attachments.test.tsx new file mode 100644 index 00000000..9ae56719 --- /dev/null +++ b/packages/elements/__tests__/attachments.test.tsx @@ -0,0 +1,77 @@ +import { render, screen } from "@testing-library/react"; +import { userEvent } from "@testing-library/user-event"; + +import { + Attachment, + AttachmentEmpty, + AttachmentInfo, + AttachmentPreview, + AttachmentRemove, + Attachments, + getAttachmentLabel, + getMediaCategory, +} from "../src/attachments"; + +const imageAttachment = { + filename: "diagram.png", + id: "image-1", + mediaType: "image/png", + type: "file" as const, + url: "data:image/png;base64,AA==", +}; + +describe("attachments", () => { + it("classifies and labels pinned attachment data", () => { + expect(getMediaCategory(imageAttachment)).toBe("image"); + expect(getAttachmentLabel(imageAttachment)).toBe("diagram.png"); + expect( + getAttachmentLabel({ + id: "source-1", + mediaType: "text/html", + sourceId: "source", + title: "AI Elements", + type: "source-document", + }) + ).toBe("AI Elements"); + }); + + it.each(["grid", "inline", "list"] as const)( + "renders the %s visual variant", + (variant) => { + render( + + + + + + + ); + + expect(screen.getByAltText("diagram.png")).toBeInTheDocument(); + expect(screen.queryByText("diagram.png") !== null).toBe( + variant !== "grid" + ); + } + ); + + it("invokes the item removal callback", async () => { + const onRemove = vi.fn(); + const user = userEvent.setup(); + render( + + + + + + + ); + + await user.click(screen.getByRole("button", { name: "Remove" })); + expect(onRemove).toHaveBeenCalledOnce(); + }); + + it("renders the explicit empty state", () => { + render(); + expect(screen.getByText("No attachments")).toBeInTheDocument(); + }); +}); diff --git a/packages/elements/__tests__/audio-player.test.tsx b/packages/elements/__tests__/audio-player.test.tsx index fbbf8ff6..afa84936 100644 --- a/packages/elements/__tests__/audio-player.test.tsx +++ b/packages/elements/__tests__/audio-player.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from "@testing-library/react"; +import { renderToString } from "react-dom/server"; import { AudioPlayer, @@ -67,6 +68,14 @@ describe("audioPlayer", () => { }); describe("audioPlayerElement", () => { + it("renders the media controller tab stop consistently during SSR", () => { + const markup = renderToString( + + ); + + expect(markup).toContain('tabindex="-1"'); + }); + it("renders audio element with remote src", () => { const { container } = render( diff --git a/packages/elements/__tests__/checkpoint.test.tsx b/packages/elements/__tests__/checkpoint.test.tsx new file mode 100644 index 00000000..98895e15 --- /dev/null +++ b/packages/elements/__tests__/checkpoint.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from "@testing-library/react"; + +import { + Checkpoint, + CheckpointIcon, + CheckpointTrigger, +} from "../src/checkpoint"; + +describe("checkpoint", () => { + it("renders the checkpoint label and default icon", () => { + const { container } = render( + + + Restore checkpoint + + ); + + expect(screen.getByTestId("checkpoint-icon")).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Restore checkpoint" }) + ).toHaveAttribute("type", "button"); + expect(container.firstChild).toHaveClass("text-muted-foreground"); + }); + + it("preserves explicit trigger styling", () => { + render( + + + Restore + + + ); + + expect(screen.getByRole("button", { name: "Restore" })).toHaveClass( + "checkpoint-action" + ); + }); +}); diff --git a/packages/elements/__tests__/controls.test.tsx b/packages/elements/__tests__/controls.test.tsx index 42b50a81..e9efe9d9 100644 --- a/packages/elements/__tests__/controls.test.tsx +++ b/packages/elements/__tests__/controls.test.tsx @@ -6,9 +6,11 @@ import { Controls } from "../src/controls"; describe("controls", () => { it("renders within Canvas", () => { const { container } = render( - - - +
+ + + +
); expect( container.querySelector(".react-flow__controls") @@ -17,9 +19,11 @@ describe("controls", () => { it("applies custom className", () => { const { container } = render( - - - +
+ + + +
); const controls = container.querySelector(".custom-controls"); expect(controls).toBeInTheDocument(); @@ -28,9 +32,11 @@ describe("controls", () => { it("renders with default styles", () => { const { container } = render( - - - +
+ + + +
); const controls = container.querySelector(".react-flow__controls"); expect(controls).toBeInTheDocument(); @@ -38,9 +44,11 @@ describe("controls", () => { it("accepts additional props", () => { const { container } = render( - - - +
+ + + +
); // Just verify it renders without error expect(container).toBeTruthy(); diff --git a/packages/elements/__tests__/open-in-chat.test.tsx b/packages/elements/__tests__/open-in-chat.test.tsx index d28d51d6..fbe1b0f6 100644 --- a/packages/elements/__tests__/open-in-chat.test.tsx +++ b/packages/elements/__tests__/open-in-chat.test.tsx @@ -1,5 +1,6 @@ -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; +import OpenInExample from "../../examples/src/open-in-chat"; import { OpenIn, OpenInChatGPT, @@ -23,6 +24,24 @@ const V0_REGEX = /v0/i; const CURSOR_REGEX = /Cursor/i; describe("openIn", () => { + it("passes the example query through the parent context to provider links", async () => { + render(); + + fireEvent.pointerDown( + screen.getByRole("button", { name: "Open in chat" }), + { + button: 0, + ctrlKey: false, + pointerType: "mouse", + } + ); + + const link = await screen.findByRole("menuitem", { name: CHATGPT_REGEX }); + expect( + new URL(link.getAttribute("href") ?? "").searchParams.get("prompt") + ).toBe("How can I implement authentication in Next.js?"); + }); + it("renders children", () => { render( diff --git a/packages/elements/__tests__/panel.test.tsx b/packages/elements/__tests__/panel.test.tsx index 6d1e90b7..04213006 100644 --- a/packages/elements/__tests__/panel.test.tsx +++ b/packages/elements/__tests__/panel.test.tsx @@ -6,18 +6,22 @@ import { Panel } from "../src/panel"; describe("panel", () => { it("renders children within Canvas", () => { render( - - Panel content - +
+ + Panel content + +
); expect(screen.getByText("Panel content")).toBeInTheDocument(); }); it("applies custom className", () => { const { container } = render( - - Content - +
+ + Content + +
); const panel = container.querySelector(".custom-panel"); expect(panel).toBeInTheDocument(); @@ -26,18 +30,22 @@ describe("panel", () => { it("renders with default styles", () => { const { container } = render( - - Content - +
+ + Content + +
); expect(container.querySelector(".react-flow__panel")).toBeInTheDocument(); }); it("passes through additional props", () => { const { container } = render( - - Content - +
+ + Content + +
); const panel = container.querySelector("[data-testid='test-panel']"); expect(panel).toBeInTheDocument(); diff --git a/packages/elements/src/audio-player.tsx b/packages/elements/src/audio-player.tsx index a340e76b..ef7a21f7 100644 --- a/packages/elements/src/audio-player.tsx +++ b/packages/elements/src/audio-player.tsx @@ -80,6 +80,7 @@ export const AudioPlayerElement = ({ ...props }: AudioPlayerElementProps) => (