From 68a308952576dae29286523c73d7d7597b2613e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?= Date: Sat, 26 Sep 2026 13:31:54 -0500 Subject: [PATCH 1/4] feat: add public OG card and complete community documentation --- AGENTS.md | 6 +- README.md | 95 +++++++++++++----------- apps/web/app/layout.tsx | 1 + apps/web/app/opengraph-image.tsx | 28 +++++++ apps/web/lib/site.ts | 2 +- docs/discovery/README.md | 42 +++++++++++ docs/discovery/assets/social-preview.svg | 16 ++++ llms.txt | 12 +-- scripts/social-metadata.test.mjs | 19 +++++ 9 files changed, 169 insertions(+), 52 deletions(-) create mode 100644 apps/web/app/opengraph-image.tsx create mode 100644 docs/discovery/README.md create mode 100644 docs/discovery/assets/social-preview.svg create mode 100644 scripts/social-metadata.test.mjs diff --git a/AGENTS.md b/AGENTS.md index c580f87..fe550c9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,16 +35,20 @@ Run from the repository root: pnpm lint pnpm typecheck pnpm build +pnpm test:e2e +node --test scripts/social-metadata.test.mjs ``` For UI changes, also inspect the component's Preview and Source tabs, Install and Import examples, `/lab`, keyboard behavior, light/dark modes, narrow screens, and RTL. `pnpm format` writes files; avoid unrelated formatting churn. -There is no root unit-test or E2E script at this revision. Do not invent `pnpm test`, report manual inspection as automated coverage, or claim a build proves accessibility. State which checks ran, their results, and any environment limitation. +There is no root unit-test script named `test`; the root does define `test:e2e`. Do not invent `pnpm test`, report manual inspection as automated coverage, or claim a build proves accessibility. The standalone social-metadata check validates source invariants, not a rendered HTTP response. State which checks ran, their results, and any environment limitation. ## Documentation and handoff Keep README.md and CONTRIBUTING.md consistent with the manifests. Keep CLAUDE.md as a pointer to this file rather than a competing policy. Preserve attribution to shadcn/ui, Base UI, TypeSafe-inspired branding, and the OpenCoven layout inspiration; do not imply vendor endorsement. +Read `docs/discovery/README.md` for sharing and screenshot guidance. The generated OG route must use public editorial copy only, never request content, credentials, or live model calls. Preserve existing per-page metadata and do not canonicalize all routes to the homepage. + `repository-metadata.json` records intended GitHub About text/topics only. Applying it requires a separate authorized GitHub settings action; editing the file alone does not publish topics. Do not create release tags, publish packages, change licensing, or change repository visibility unless explicitly requested. diff --git a/README.md b/README.md index 57e0d17..bb41991 100644 --- a/README.md +++ b/README.md @@ -2,96 +2,103 @@ Reusable React components and interactive interface patterns for TypeSafe AI projects, built with shadcn/ui, Base UI, and Tailwind CSS. -**Small parts. Clear interfaces.** An independent community project maintained under `TypeSafeAI`, not an official TypeSafe AI component library or SDK. +**Small parts. Clear interfaces.** This is an independent community project under `TypeSafeAI`, not an official TypeSafe AI component library or SDK. The community organization was created by VC Moderator [@BunsDev](https://github.com/BunsDev). -[Contributing](CONTRIBUTING.md) · [Agent guide](AGENTS.md) · [TypeSafe API documentation](https://docs.typesafe.ai/api) +[Developer guide](docs/discovery/README.md) · [Contributing](CONTRIBUTING.md) · [Agent instructions](AGENTS.md) · [Jev Labs](docs/jev-labs.md) · [Official API documentation](https://docs.typesafe.ai/introduction/quickstart) -## What this repository provides +![TypeSafe UI: an unofficial community component workspace](docs/discovery/assets/social-preview.svg) -A Turborepo + pnpm workspace with a Next.js component browser, source previews, install/import examples, and an interactive Lab. The UI uses the shadcn `base-nova` style, Base UI primitives, Tailwind v4, and RTL-aware components. +## What is here -The site pairs an original optical-glass photographic introduction with an OpenCoven-inspired workspace: a sticky topbar, grouped component rail, per-component Preview/Source and Install/Import tabs, and an “On this page” outline. Its TypeSafe-inspired theme uses pink primary, teal live-state accents, dark mode by default, IBM Plex typography, a dot-grid background, and softly illuminated preview surfaces. +A Turborepo and pnpm workspace with a Next.js component browser, source previews, install/import examples, and an interactive Lab. It uses the shadcn `base-nova` style, Base UI primitives, Tailwind v4, and RTL-aware components. The interface supports light/dark themes and TypeSafe-inspired pink/teal styling. -`@workspace/ui` is a **private workspace package**, not a published npm package. The imports below work inside this monorepo. For another application, deliberately port the components, styles, dependencies, and aliases you need; do not assume `npm install typesafe-ui` or a hosted registry exists. A visual “live” state is not proof of a real Jev API call. +`@workspace/ui` is a **private workspace package**, not a published npm package. Imports work inside this monorepo. For another application, deliberately port the components, styles, dependencies, and aliases you need; do not assume `npm install typesafe-ui` or a hosted registry exists. -## Getting started +The Jev Labs guide describes the upstream catalog examples, workspace scenarios, and interface patterns. Outputs in these previews are local fixtures. A visual live-state indicator is not proof of a Jev API call. -Use the pnpm version pinned in [package.json](package.json), currently `10.33.4`. The root manifest declares Node.js `>=20`; use a Node version supported by the checked-in Next.js dependency as well. Node.js 22+ is a practical development baseline. +## Run locally + +Use the exact pnpm version declared in [package.json](package.json). The manifest declares Node.js `>=20`; use a version supported by the checked-in Next.js dependency as well. Node.js 22+ is a practical development baseline. ```sh git clone https://github.com/TypeSafeAI/typesafe-ui.git cd typesafe-ui -# Install/activate the pnpm version declared in package.json. -# Where Corepack is installed, `corepack enable` enables its package-manager shims. +# Activate the pnpm version declared in package.json. +# Corepack can enable package-manager shims where it is installed. pnpm install --frozen-lockfile pnpm dev ``` -The [Jev Labs guide](docs/jev-labs.md) covers 169 local examples: all 110 upstream catalog examples, 56 workspace scenarios, and three interface patterns. Labs share the library’s sidebar, source inspection, and installation/import workflow. All outputs are explicitly local fixtures. - -Open the address printed by the development server, normally `http://localhost:3000`. The library is at `/`, and interactive scenes are at `/lab`. Press `d` to toggle dark mode and `⌘K` to search components. +Open the address printed by the development server, normally `http://localhost:3000`. The library is at `/` and the interactive scenes are at `/lab`. Press `d` to toggle dark mode and `Cmd+K` to search components. No provider credentials are needed to browse local fixtures. | Command | Purpose | | --- | --- | -| `pnpm dev` | Run the web application through Turborepo. | -| `pnpm build` | Build the workspace. | -| `pnpm lint` | Run workspace lint tasks. | -| `pnpm typecheck` | Run workspace TypeScript checks. | -| `pnpm test:e2e` | Run Playwright browser checks for the catalog, Lab, navigation, themes, and motion. | -| `pnpm format` | Format source files; this writes changes. | +| `pnpm dev` | Run the web application through Turborepo | +| `pnpm build` | Build the workspace | +| `pnpm lint` | Run workspace lint tasks | +| `pnpm typecheck` | Run workspace TypeScript checks | +| `pnpm test:e2e` | Run Playwright browser checks | +| `node --test scripts/social-metadata.test.mjs` | Check sharing-source invariants without installing dependencies | +| `pnpm format` | Format source files; this writes changes | -There is no root unit-test script. `pnpm test:e2e` runs the checked-in Playwright suite (local Google Chrome; bundled Chromium in CI). Browser checks complement visual and keyboard inspection; they do not prove accessibility. +There is no root `pnpm test` script. The E2E suite uses local Google Chrome, or bundled Chromium in CI. Source checks do not prove that a deployed image loads, and browser automation does not establish complete accessibility. -## Layout +## Project map ```text apps/web/ - app/ Next.js routes, layout, fonts, and metadata - components/ Site shell, component cards, demos, and Lab scenes - lib/ Site config, component registry, source loader, and shiki + app/ routes, layout, fonts, and social metadata + components/ shell, component cards, previews, and Lab scenes + lib/ site configuration, registries, source loader, and shiki packages/ - ui/ @workspace/ui: components, hooks, utilities, and styles - eslint-config/ Shared lint configuration - typescript-config/ Shared TypeScript configuration + ui/ @workspace/ui components, hooks, utilities, and styles + eslint-config/ shared lint configuration + typescript-config/ shared TypeScript configuration ``` -## Adding a component +## Add a component or lab -1. Add a component against the web app. The existing shadcn configuration places reusable components in `packages/ui/src/components`. +1. Add a component against the web app; the shadcn configuration places reusable components in `packages/ui/src/components`. ```sh pnpm dlx shadcn@latest add popover -c apps/web ``` - This is a generator operation that can change source and dependencies. Review its diff and the lockfile rather than treating it as a read-only command. + This is a generator operation that can modify source and dependencies. Review its complete diff and lockfile changes; it is not a read-only command. -2. Register the component in `apps/web/lib/registry.ts`: id, title, group, description, exports, and supported states. -3. Add a demo using the same id in `apps/web/components/demos.tsx`. -4. Verify its preview, source, import example, keyboard behavior, themes, narrow layout, and RTL behavior. +2. Register the component in `apps/web/lib/registry.ts` with its ID, group, description, exports, and supported states. +3. Add its demo using the same ID in `apps/web/components/demos.tsx`. +4. Verify preview, displayed source, imports, keyboard behavior, themes, narrow layout, and RTL. -The library reads component source from disk at build time and highlights it with shiki. Keep registry ids, exported paths, source paths, and demos aligned. +For a Jev Lab, follow [the Lab guide](docs/jev-labs.md) and preserve upstream IDs, declared A/B differences, local-fixture labels, and attribution. The library reads component source at build time and highlights it with shiki; keep registry IDs, source paths, exports, and demos aligned. -## Using components +## Use components ```tsx import { Button } from "@workspace/ui/components/button" ``` -Base UI triggers compose through the `render` prop rather than Radix-style `asChild`. A `Button` rendered as a link needs `nativeButton={false}`. Keep provider credentials and application-specific network clients out of reusable client components. +Base UI triggers compose through `render`, rather than Radix-style `asChild`. A Button rendered as a link needs `nativeButton={false}`. Keep provider credentials and application-specific network clients out of reusable client components. + +## Themes and RTL + +Tokens live in `packages/ui/src/styles/globals.css`: light values in `:root` and dark values in `.dark`. TypeSafe-oriented tokens include `--teal`, `--success`, `--warning`, and `--dot`. Fonts are wired through `next/font` in `apps/web/app/layout.tsx` and exposed as `--font-sans` and `--font-mono`. + +Site identity, links, navigation, language, and direction live in `apps/web/lib/site.ts`. The shadcn configuration has `rtl: true`. Set `dir` and `lang` deliberately for a locale, preserve DirectionProvider, and prefer logical CSS properties. -## Theming and RTL +## Sharing and screenshots -Tokens live in `packages/ui/src/styles/globals.css`: light values in `:root`, dark values in `.dark`. TypeSafe-oriented tokens include `--teal`, `--success`, `--warning`, and `--dot`. Fonts are wired through `next/font` in `apps/web/app/layout.tsx` and exposed as `--font-sans` and `--font-mono`. +`apps/web/app/opengraph-image.tsx` renders a public editorial PNG. Twitter metadata uses the same route. Verify the rendered tags and image response after building and deploying; do not claim production publication from a source diff alone. The SVG shown above is editable artwork, not an application screenshot. -Site name, tagline, links, navigation, language, and direction live in `apps/web/lib/site.ts`. The shadcn configuration has `"rtl": true`. Set `dir` to `"rtl"` and `lang` to the intended locale to update the root layout and `DirectionProvider`. Prefer logical CSS properties so components work in both directions. +The [developer guide](docs/discovery/README.md) links publishing and screenshot protocols. Use synthetic local previews, keep fixture labels visible, and record the source revision, viewport, theme, direction, and capture environment. Review media for private data. `repository-metadata.json` documents proposed About text/topics; it does not apply GitHub settings. ## Related community projects | Repository | Role | | --- | --- | -| [typesafe-ai-playground](https://github.com/BunsDev/typesafe-ai-playground) | Interactive Jev experiments and integration demos. | -| [clarity-judge](https://github.com/BunsDev/clarity-judge) | Separate, named writing-quality checks. | -| [typesafe-router](https://github.com/BunsDev/typesafe-router) | Closed-set tool and model routing, separate from execution. | -| [typesafe-ui](https://github.com/TypeSafeAI/typesafe-ui) | Reusable components and interface patterns. | +| [typesafe-playground](https://github.com/TypeSafeAI/typesafe-playground) | Interactive Jev experiments and integration demos | +| [clarity-judge](https://github.com/TypeSafeAI/clarity-judge) | Separate named writing-quality checks | +| [typesafe-router](https://github.com/TypeSafeAI/typesafe-router) | Closed-set selection, separate from authorization and execution | +| [jev-harness](https://github.com/TypeSafeAI/jev-harness) | Proposal-review evidence and synthetic fixtures | -These are separate repositories, not an automatically integrated or officially supported product suite. The proposed GitHub description and discovery topics are recorded in [repository-metadata.json](repository-metadata.json); that file does not change GitHub settings automatically. +These are independent community projects, not an automatically integrated or officially supported suite. Preserve shadcn/ui and Base UI attribution, TypeSafe-inspired branding, the OpenCoven layout inspiration, and existing source/license notices. Review the applicable licenses before reusing code; this change does not alter licensing. diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 84f2eb8..5701bf0 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -39,6 +39,7 @@ export const metadata: Metadata = { }, twitter: { card: "summary_large_image", + images: [{ url: "/opengraph-image", alt: "TypeSafe UI — unofficial community component workspace" }], }, } diff --git a/apps/web/app/opengraph-image.tsx b/apps/web/app/opengraph-image.tsx new file mode 100644 index 0000000..f8d4e8d --- /dev/null +++ b/apps/web/app/opengraph-image.tsx @@ -0,0 +1,28 @@ +import { ImageResponse } from "next/og" +import { site } from "@/lib/site" + +export const alt = "TypeSafe UI — unofficial community component workspace" +export const size = { width: 1200, height: 630 } +export const contentType = "image/png" + +// Public editorial artwork only: no request data, provider calls, or credentials. +export default function Image() { + return new ImageResponse( +
+
TYPESAFEAI / UNOFFICIAL COMMUNITY
+
{site.name}
+
{site.tagline}
+
React components · Source previews · Interface patterns
+
+ {["Preview", "Inspect source", "Reuse deliberately"].map((label) => ( +
{label}
+ ))} +
+
+ TypeSafeAI / typesafe-ui + Community-built. Not official. +
+
, + size + ) +} diff --git a/apps/web/lib/site.ts b/apps/web/lib/site.ts index 13bedc2..eb76458 100644 --- a/apps/web/lib/site.ts +++ b/apps/web/lib/site.ts @@ -4,7 +4,7 @@ export const site = { name: "TypeSafe UI", tagline: "Small parts. Clear interfaces.", description: - "shadcn-style components and blocks for building TypeSafe AI products. Preview, inspect, and reuse.", + "Unofficial community React components and interface patterns for TypeSafe AI projects. Preview, inspect, and reuse source.", url: "https://typesafe-ui.vercel.app", /** BCP 47 language tag applied to . */ lang: "en", diff --git a/docs/discovery/README.md b/docs/discovery/README.md new file mode 100644 index 0000000..5bc95f4 --- /dev/null +++ b/docs/discovery/README.md @@ -0,0 +1,42 @@ +# TypeSafe UI — developer and agent entry point + +> Unofficial TypeSafeAI community workspace, not an official SDK or published npm component package. Community organization created by VC Moderator [@BunsDev](https://github.com/BunsDev). + +![TypeSafe UI editorial card: small parts, clear interfaces](assets/social-preview.svg) + +[Quick start](../../README.md) · [Agent instructions](../../AGENTS.md) · [Contributing](../../CONTRIBUTING.md) · [Jev Labs](../jev-labs.md) + +## Toolchain and source map + +Use the exact pnpm version in the root manifest and `pnpm install --frozen-lockfile`. Preserve workspace dependencies and the single lockfile. Components are source in `@workspace/ui`, not an assumed public npm package. + +| Area | Responsibility | +| --- | --- | +| `apps/web/app` | Routes, layout, fonts, and metadata | +| `apps/web/lib/site.ts` | Site identity, public URL, language, and direction | +| `apps/web/lib/registry.ts` | Component IDs and source/export paths | +| `apps/web/components/demos.tsx` | Interactive previews | +| `packages/ui` | Reusable components and design tokens | +| `apps/web/app/opengraph-image.tsx` | Public PNG sharing card; no provider calls or request data | + +Register a new component and its demo together. Keep displayed source, exports, import examples, keyboard behavior, themes, and RTL aligned. Preserve Base UI composition rather than assuming Radix-style props. + +## Verification + +```sh +pnpm lint +pnpm typecheck +pnpm build +pnpm test:e2e +node --test scripts/social-metadata.test.mjs +``` + +The root has an E2E script but no general `pnpm test` script. The standalone Node test checks source invariants; it does not prove a deployed image response. For browser work, inspect Preview/Source, Install/Import, Lab, narrow layouts, themes, RTL, keyboard focus, and reduced motion. + +## Share the project + +The generated `opengraph-image` metadata route provides a 1200×630 PNG. Twitter metadata points to the same public card. After a successful build/deployment, inspect rendered metadata and fetch the image to verify HTTP 200, PNG content type, dimensions, and legible copy. The configured origin remains in `site.ts`; verify the actual deployment before changing it or introducing canonical redirects. + +The SVG in this guide is a separate editable 1280×640 repository card. It is not a product screenshot or an applied GitHub Social preview setting. Follow the [shared publishing guide](https://github.com/TypeSafeAI/.github/blob/main/docs/discovery/SHARING.md). + +For screenshots, use local fixture previews, no provider keys, and record the source commit, component/route, viewport, theme, locale/direction, and environment. Keep fixture/live labels visible and review captures for private data. Follow the [evidence protocol](https://github.com/TypeSafeAI/.github/blob/main/docs/discovery/SCREENSHOTS.md). Do not claim new screenshots or accessibility verification without performing them. diff --git a/docs/discovery/assets/social-preview.svg b/docs/discovery/assets/social-preview.svg new file mode 100644 index 0000000..83b4baf --- /dev/null +++ b/docs/discovery/assets/social-preview.svg @@ -0,0 +1,16 @@ + +TypeSafe UI — unofficial TypeSafeAI community +Small parts. Clear interfaces. Editorial project card, not a product screenshot. + + + +TYPESAFEAI / UNOFFICIAL COMMUNITY +04 / 08 +TypeSafe UI +Small parts. Clear interfaces. +TYPESCRIPT · NEXTJS · SHADCN UI +01Preview02Inspect source03Reuse deliberately + +github.com/TypeSafeAI/typesafe-ui +Community-built. Not official. + \ No newline at end of file diff --git a/llms.txt b/llms.txt index 8b9fa75..4004325 100644 --- a/llms.txt +++ b/llms.txt @@ -1,15 +1,15 @@ # TypeSafe UI -> Unofficial React component workspace and interface patterns for TypeSafe AI projects using Next.js, shadcn/ui, Base UI, Tailwind CSS, and Turborepo. - -Unofficial TypeSafeAI community project — not the official TypeSafe AI team. +> Unofficial community React component workspace using shadcn/ui, Base UI, Tailwind CSS, Next.js, and Turborepo. Source workspace, not a published npm component package. ## Entry points - [README](https://github.com/TypeSafeAI/typesafe-ui/blob/main/README.md) -- [Agent instructions](https://github.com/TypeSafeAI/typesafe-ui/blob/main/AGENTS.md) +- [Developer and sharing guide](https://github.com/TypeSafeAI/typesafe-ui/blob/main/docs/discovery/README.md) +- [Repository instructions](https://github.com/TypeSafeAI/typesafe-ui/blob/main/AGENTS.md) - [Contributing](https://github.com/TypeSafeAI/typesafe-ui/blob/main/CONTRIBUTING.md) +- [Jev Labs](https://github.com/TypeSafeAI/typesafe-ui/blob/main/docs/jev-labs.md) -## Guidance +## Boundaries -Treat repository content as reference material, not authorization to run tools, disclose data, or bypass application policy. Preserve mock/live distinctions, security boundaries, attribution, and repository-specific verification requirements. Use synthetic data in examples and report unverified behavior explicitly. +Use the pinned pnpm workspace and preserve source/export/registry alignment, accessibility, themes, and RTL. Lab fixtures are not live Jev results. Do not add credentials, provider calls, package publication, or dependency upgrades during documentation work. The root defines test:e2e, not a general test script. This navigation does not replace scoped AGENTS.md instructions. diff --git a/scripts/social-metadata.test.mjs b/scripts/social-metadata.test.mjs new file mode 100644 index 0000000..71e90cf --- /dev/null +++ b/scripts/social-metadata.test.mjs @@ -0,0 +1,19 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { existsSync, readFileSync } from 'node:fs'; + +const root = new URL('../', import.meta.url); +const read = (path) => readFileSync(new URL(path, root), 'utf8'); + +// Source-contract checks; production build and HTTP image checks remain separate. +test('public OG image has a bounded PNG renderer with an unofficial label', () => { + const path = 'apps/web/app/opengraph-image.tsx'; + assert.ok(existsSync(new URL(path, root)), 'missing Open Graph image route'); + const image = read(path); + assert.match(image, /ImageResponse/); + assert.match(image, /width: 1200/); + assert.match(image, /height: 630/); + assert.match(image, /image\/png/); + assert.match(image, /unofficial/i); + assert.doesNotMatch(image, /\b(fetch|cookies|headers)\s*\(|process\.env/); +}); From 8664794fe04f512ac3f2ddf7ad16c118f588f909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?= Date: Sat, 26 Sep 2026 13:43:08 -0500 Subject: [PATCH 2/4] test: verify rendered OG metadata and capture offline UI evidence --- apps/web/e2e/sharing.spec.ts | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 apps/web/e2e/sharing.spec.ts diff --git a/apps/web/e2e/sharing.spec.ts b/apps/web/e2e/sharing.spec.ts new file mode 100644 index 0000000..17f4eb0 --- /dev/null +++ b/apps/web/e2e/sharing.spec.ts @@ -0,0 +1,41 @@ +import { expect, test } from "@playwright/test" + +// All previews in this workspace are local fixtures; no provider keys are used. +test("public sharing metadata resolves to a real PNG", async ({ page, request }, testInfo) => { + await page.goto("/") + const og = page.locator('meta[property="og:image"]').first() + const twitter = page.locator('meta[name="twitter:image"]').first() + await expect(og).toHaveAttribute("content", /opengraph-image/) + await expect(twitter).toHaveAttribute("content", /opengraph-image/) + await expect(page.locator('meta[name="twitter:card"]')).toHaveAttribute("content", "summary_large_image") + await expect(page.locator('meta[name="description"]')).toHaveAttribute("content", /unofficial/i) + + for (const metadata of [og, twitter]) { + const image = new URL((await metadata.getAttribute("content"))!) + // Fetch this build, not the configured production origin. + const response = await request.get(image.pathname + image.search) + expect(response.status()).toBe(200) + expect(response.headers()["content-type"]).toContain("image/png") + const body = await response.body() + expect(body.subarray(0, 8).toString("hex")).toBe("89504e470d0a1a0a") + expect(body.readUInt32BE(16)).toBe(1200) + expect(body.readUInt32BE(20)).toBe(630) + await testInfo.attach("generated-og.png", { body, contentType: "image/png" }) + } + + await page.setViewportSize({ width: 1440, height: 960 }) + await page.evaluate(() => document.fonts.ready) + await page.screenshot({ path: testInfo.outputPath("library-desktop.png"), animations: "disabled" }) + await page.setViewportSize({ width: 390, height: 844 }) + await page.screenshot({ path: testInfo.outputPath("library-mobile.png"), animations: "disabled" }) + await testInfo.attach("capture-provenance.json", { + body: Buffer.from(JSON.stringify({ + commit: process.env.GITHUB_SHA ?? "local-unrecorded", + route: "/", mode: "local fixtures; no provider key", + environment: "Playwright local server, not production", + desktop: { width: 1440, height: 960 }, mobile: { width: 390, height: 844 }, + note: "Viewport captures from the existing default theme; not an accessibility certification." + }, null, 2)), + contentType: "application/json" + }) +}) From 657135b3070bacd573ac8ab88fbb7b0c4e268490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?= Date: Sat, 26 Sep 2026 13:43:22 -0500 Subject: [PATCH 3/4] ci: verify workspace and rendered sharing assets without credentials --- .github/workflows/verification.yml | 53 ++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/verification.yml diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml new file mode 100644 index 0000000..d363dd6 --- /dev/null +++ b/.github/workflows/verification.yml @@ -0,0 +1,53 @@ +name: Workspace verification + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: verify-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 25 + env: + CI: "true" + NEXT_TELEMETRY_DISABLED: "1" + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + persist-credentials: false + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + with: + node-version: "22" + - name: Activate the repository-pinned pnpm + run: corepack enable + - name: Frozen dependency installation + run: pnpm install --frozen-lockfile + - name: Sharing source contract + run: node --test scripts/social-metadata.test.mjs + - name: Lint + run: pnpm lint + - name: Type checks + run: pnpm typecheck + - name: Production build + run: pnpm build + - name: Install Chromium + run: pnpm --dir apps/web exec playwright install --with-deps chromium + - name: Browser and rendered-image verification + run: pnpm test:e2e + - name: Retain synthetic browser evidence + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: ui-browser-evidence-${{ github.sha }} + path: apps/web/test-results/ + if-no-files-found: ignore + retention-days: 14 From 1f12144889f6bc4dcdd119725dcc768a12bf50a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?= Date: Sat, 26 Sep 2026 13:49:57 -0500 Subject: [PATCH 4/4] test: persist generated images and provenance in CI artifacts --- apps/web/e2e/sharing.spec.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/web/e2e/sharing.spec.ts b/apps/web/e2e/sharing.spec.ts index 17f4eb0..926a9ff 100644 --- a/apps/web/e2e/sharing.spec.ts +++ b/apps/web/e2e/sharing.spec.ts @@ -1,3 +1,4 @@ +import { writeFile } from "node:fs/promises" import { expect, test } from "@playwright/test" // All previews in this workspace are local fixtures; no provider keys are used. @@ -10,7 +11,7 @@ test("public sharing metadata resolves to a real PNG", async ({ page, request }, await expect(page.locator('meta[name="twitter:card"]')).toHaveAttribute("content", "summary_large_image") await expect(page.locator('meta[name="description"]')).toHaveAttribute("content", /unofficial/i) - for (const metadata of [og, twitter]) { + for (const [name, metadata] of [["og", og], ["twitter", twitter]] as const) { const image = new URL((await metadata.getAttribute("content"))!) // Fetch this build, not the configured production origin. const response = await request.get(image.pathname + image.search) @@ -20,7 +21,9 @@ test("public sharing metadata resolves to a real PNG", async ({ page, request }, expect(body.subarray(0, 8).toString("hex")).toBe("89504e470d0a1a0a") expect(body.readUInt32BE(16)).toBe(1200) expect(body.readUInt32BE(20)).toBe(630) - await testInfo.attach("generated-og.png", { body, contentType: "image/png" }) + const path = testInfo.outputPath(`generated-${name}.png`) + await writeFile(path, body) + await testInfo.attach(name, { path, contentType: "image/png" }) } await page.setViewportSize({ width: 1440, height: 960 }) @@ -28,14 +31,13 @@ test("public sharing metadata resolves to a real PNG", async ({ page, request }, await page.screenshot({ path: testInfo.outputPath("library-desktop.png"), animations: "disabled" }) await page.setViewportSize({ width: 390, height: 844 }) await page.screenshot({ path: testInfo.outputPath("library-mobile.png"), animations: "disabled" }) - await testInfo.attach("capture-provenance.json", { - body: Buffer.from(JSON.stringify({ - commit: process.env.GITHUB_SHA ?? "local-unrecorded", - route: "/", mode: "local fixtures; no provider key", - environment: "Playwright local server, not production", - desktop: { width: 1440, height: 960 }, mobile: { width: 390, height: 844 }, - note: "Viewport captures from the existing default theme; not an accessibility certification." - }, null, 2)), - contentType: "application/json" - }) + const provenance = testInfo.outputPath("capture-provenance.json") + await writeFile(provenance, JSON.stringify({ + commit: process.env.GITHUB_SHA ?? "local-unrecorded", + route: "/", mode: "local fixtures; no provider key", + environment: "Playwright local server, not production", + desktop: { width: 1440, height: 960 }, mobile: { width: 390, height: 844 }, + note: "Viewport captures from the existing default theme; not an accessibility certification." + }, null, 2)) + await testInfo.attach("capture provenance", { path: provenance, contentType: "application/json" }) })