diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 741d9826..b4d146ea 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -35,6 +35,7 @@ None. ## Checklist - [ ] Component(s) follow the conventions in [AGENTS.md](../AGENTS.md). +- [ ] UI changes follow [DESIGN.md](../DESIGN.md). Deviations: - [ ] New exports added to `packages/ui/src/index.ts`. - [ ] Tests added (unit + visual, as applicable). - [ ] CHANGELOG note added under `[Unreleased]` for user-facing changes. diff --git a/AGENTS.md b/AGENTS.md index 9ce51f90..2ddc0d1f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,8 +64,9 @@ Repository-wide docs (audience = contributors, not just agents): ## Quick reference -The two most-violated rules in PR review history: +The most-violated rules in PR review history: +0. **Design rules are mandatory for UI work.** Any UI suggestion or component/site change must follow [`DESIGN.md`](./DESIGN.md) and the machine-readable tokens in [`packages/design/tokens.json`](./packages/design/tokens.json). List any intentional deviation in the PR body. 1. **Workspace gates green at HEAD** ([R6](./docs/agents/RULES.md#r6--workspace-gates-green-at-head)). Touched-file passes alone are not ship-OK. Run `pnpm -F @vllnt/ui lint && pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json && pnpm build && pnpm test:once` — all must be green. 2. **PR body matches HEAD** ([R3](./docs/agents/RULES.md#r3--pr-body-matches-head)). After every push, rewrite the body to match the current head. Stale claims block ship. 3. **Linked issue required** ([R5](./docs/agents/RULES.md#r5--linked-issue-required)). Every PR must reference a GitHub issue. Accepted keywords: `close` / `closes` / `closed`, `fix` / `fixes` / `fixed`, `resolve` / `resolves` / `resolved`, or repo phrases `Part of` / `Related to` — all case-insensitive, optional colon — followed by `#123` or `owner/repo#123`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0535cb88..46792c69 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,6 +7,7 @@ Thanks for wanting to contribute. This repo welcomes issues, bug reports, and PR - Be respectful. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). - Security-sensitive reports go through [SECURITY.md](SECURITY.md), **not** public issues. - Every change keeps the repo shippable: lint, typecheck, tests, and build pass on `main`. +- UI changes must follow [DESIGN.md](DESIGN.md) and the token contract in [packages/design/tokens.json](packages/design/tokens.json). Document intentional deviations in the PR body. ## Development setup diff --git a/DESIGN.md b/DESIGN.md index fbe9457b..548a0ea8 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -4,9 +4,9 @@ > Humans read this file. Agents read it too — every UI suggestion must follow it. This is the **canonical** brand & design guideline for the library. The full -machine-readable token set lives in `packages/design/tokens.json` (shipping in a -follow-up to keep this PR scoped). Site renderings at `/design` will mirror this -file once that route lands. +machine-readable token set lives in `packages/design/tokens.json`, is documented +in `packages/design/README.md`, and is mirrored publicly at `/r/design.json`. +The site rendering at `/design` reads this file directly. --- @@ -51,6 +51,10 @@ Tokens live as CSS variables in `packages/ui/themes/default.css`. Components **always** consume tokens — never raw hex. The token set is versioned with the library. +Machine clients use `packages/design/tokens.json`. The JSON schema is +documented in `packages/design/tokens.schema.json` and groups tokens by color, +typography, spacing, radius, elevation, motion, and iconography. + ### Semantic tokens (consumer-facing) | Token | Light role | Dark role | diff --git a/apps/registry/app/design.txt/route.ts b/apps/registry/app/design.txt/route.ts new file mode 100644 index 00000000..6d6cd852 --- /dev/null +++ b/apps/registry/app/design.txt/route.ts @@ -0,0 +1,20 @@ +import { getDesignGuideMarkdown } from "@/lib/design-guide"; + +const TEXT_HEADERS = new Headers([ + [ + "Cache-Control", + "public, max-age=0, s-maxage=86400, stale-while-revalidate=604800", + ], + ["Content-Type", "text/plain; charset=utf-8"], +]); + +export const dynamic = "force-static"; +export const revalidate = 86_400; + +async function getDesignText(): Promise { + const body = await getDesignGuideMarkdown(); + + return new Response(body, { headers: TEXT_HEADERS }); +} + +export { getDesignText as GET }; diff --git a/apps/registry/app/design/page.tsx b/apps/registry/app/design/page.tsx new file mode 100644 index 00000000..def120fb --- /dev/null +++ b/apps/registry/app/design/page.tsx @@ -0,0 +1,180 @@ +import React, { type ComponentProps, type ReactNode } from "react"; + +import { MDXContent, Sidebar } from "@vllnt/ui"; +import type { Metadata } from "next"; + +import { + designTokens, + extractDesignGuideSections, + getDesignGuideMarkdown, + slugifyHeading, +} from "@/lib/design-guide"; +import { jsonLdScript } from "@/lib/jsonld"; +import { generateOGMetadata, generateTwitterMetadata } from "@/lib/og"; +import { canonical } from "@/lib/seo"; +import { getSidebarSections } from "@/lib/sidebar-sections"; + +const DESCRIPTION = + "Canonical VLLNT UI design rules, tokens, component patterns, accessibility expectations, and agent-facing guidance."; + +export const dynamic = "force-static"; +export const revalidate = 86_400; + +export const metadata: Metadata = { + alternates: { canonical: canonical("/design") }, + description: DESCRIPTION, + openGraph: generateOGMetadata({ + description: DESCRIPTION, + title: "VLLNT UI Design Guide", + type: "docs", + }), + title: "Design Guide", + twitter: generateTwitterMetadata({ + description: DESCRIPTION, + title: "VLLNT UI Design Guide", + type: "docs", + }), +}; + +function getNodeText(children: ReactNode): string { + return React.Children.toArray(children) + .map((child) => { + if (typeof child === "string" || typeof child === "number") { + return String(child); + } + return ""; + }) + .join(" ") + .trim(); +} + +function DesignHeadingTwo({ children, ...props }: ComponentProps<"h2">) { + const id = slugifyHeading(getNodeText(children)); + + return ( +

+ {children} +

+ ); +} + +function DesignHeadingThree({ children, ...props }: ComponentProps<"h3">) { + const id = slugifyHeading(getNodeText(children)); + + return ( +

+ {children} +

+ ); +} + +export default async function DesignPage() { + const markdown = await getDesignGuideMarkdown(); + const sections = extractDesignGuideSections(markdown); + const semanticColorCount = Object.keys(designTokens.color.semantic).length; + const spacingCount = Object.keys(designTokens.spacing.scale).length; + + const techArticleLd = { + "@context": "https://schema.org", + "@type": "TechArticle", + about: ["React component library", "design system", "accessibility"], + author: { + "@type": "Organization", + name: "VLLNT", + }, + description: DESCRIPTION, + headline: "VLLNT UI Design Guide", + publisher: { + "@type": "Organization", + name: "VLLNT", + }, + url: canonical("/design"), + }; + + return ( + <> + +
+