diff --git a/.agents/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc b/.agents/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 2acdc38195..0000000000 Binary files a/.agents/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc and /dev/null differ diff --git a/.agents/skills/skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-313.pyc b/.agents/skills/skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-313.pyc deleted file mode 100644 index 5d2a9876a6..0000000000 Binary files a/.agents/skills/skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-313.pyc and /dev/null differ diff --git a/AGENTS.md b/AGENTS.md index 15a16afdf3..d0e9e59da5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,6 +59,21 @@ Runtime: NodeJS (see .nvmrc for version) - **`apps/storybook/`** - Component development and testing environment for cds-web - **`apps/expo-app/`** - Expo app for testing and visual regression of CDS mobile components +## Skills + +Skills for this project live in `skills/`. Each skill has a `README.md` and optionally an `evals/` directory with benchmark test cases. + +### After running skill evals + +If a skill has evals and you run them, update the skill's `README.md` with a `## Performance` section containing the latest benchmark results: + +- Overall summary table: pass rate, avg time, avg tokens — with/without skill and the delta +- Per-eval breakdown table showing each task name and pass rates for each configuration +- A callout of the biggest gains (where the skill adds the most value) +- The iteration number and date for traceability + +See `skills/cds-code/README.md` for a reference example. + ## Standards & Best Practices ### General diff --git a/skills-lock.json b/skills-lock.json index e695799c14..1cbfc01ccc 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -4,6 +4,7 @@ "skill-creator": { "source": "anthropics/skills", "sourceType": "github", + "skillPath": "skills/skill-creator/SKILL.md", "computedHash": "5ea13a6d9f0d4bb694405d79acd00cadec0d21bb138c4dd10fcf3c500cb835c2" } } diff --git a/skills/cds-code/README.md b/skills/cds-code/README.md index 26977fabf0..34a34db37f 100644 --- a/skills/cds-code/README.md +++ b/skills/cds-code/README.md @@ -1,6 +1,6 @@ # cds-code -Helps your agent write idiomatic Coinbase Design System (CDS) code for React or React Native projects. +Helps your agent write idiomatic Coinbase Design System (CDS) code for React or React Native projects. Also supports CDS code review — ask your agent to audit a feature or set of files for CDS adherence. We recommend also installing the `cds-docs` Skill or the CDS MCP server for even better performance! @@ -8,6 +8,31 @@ We recommend also installing the `cds-docs` Skill or the CDS MCP server for even npx skills add https://github.com/coinbase/cds --skill cds-docs ``` +## Performance + +Evaluated against 8 real-world coding and review tasks (iteration 3, 2026-06-26): + +| Metric | With skill | Without skill | Delta | +| ---------- | ---------- | ------------- | ------ | +| Pass rate | **100%** | 73.7% | +26.3% | +| Avg time | 112.5s | 72.4s | +40.1s | +| Avg tokens | 39,907 | 38,176 | +1,731 | + +### Per-eval breakdown + +| Task | With skill | Without skill | +| ------------------------------------------------- | ---------- | ------------- | +| Profile card (Avatar, ListCell, tokens) | 100% | 78% | +| Create team modal (Modal, Select alpha) | 100% | 100% | +| Banner + progress visualizations | 100% | 100% | +| Sidebar nav (icon names, active state) | 100% | 80% | +| Empty state + illustration sizing | 100% | 60% | +| React Native wallet screen (CDS mobile) | 100% | 83% | +| Deprecated component trap (TextHeadline/TextBody) | 100% | 17% | +| CDS code review (structured lint output) | 100% | 71% | + +The biggest gains come from domain-specific knowledge the base model lacks: CDS mobile primitives, deprecated API awareness, illustration component selection, and structured audit-format output. + ## Running evaluations Use the `skill-creator` skill to run the evals. diff --git a/skills/cds-code/SKILL.md b/skills/cds-code/SKILL.md index fb75984af8..0c8e617761 100644 --- a/skills/cds-code/SKILL.md +++ b/skills/cds-code/SKILL.md @@ -1,59 +1,64 @@ --- name: cds-code description: | - Produces high quality Coinbase Design System (CDS) code for React and React Native projects. - Always use this skill every time you are asked to create or update UI or write React or React Native code. + Provides a structured workflow for writing high quality Coinbase Design System (CDS) code. + Use this skill every time you are asked to create or update a user interface using React or React Native. + Additinoally, this skill may be used to conduct a code review on existing code for CDS adherence. + Trigger examples: "build this screen", "update this component", "perform a CDS audit on our changes", + "check our codebase for CDS adherence", "does this feature use CDS well?" license: Apache-2.0 metadata: - version: '2.0.0' + version: '2.1.0' --- -# CDS Code Writing Skill +# CDS Code Skill -## Contents +## On every request -1. Part 1: Initialization | Follow these steps once per session, before you write any code -2. Part 2: Workflow | Follow these steps for all frontend coding tasks +Before responding, determine what the user needs: -## Part 1: Initialization +**Coding** — the user wants to create or update UI → follow the Coding Workflow. -Perform the following operations only once per session, after the skill is activated. +**Review** — the user explicitly asks to audit, review, or check existing code for CDS adherence → read `guidelines/code-review.md` and follow it instead. -### Prepare CDS documentation +**Default to coding.** Only treat a request as a review if the user's intent is explicit. Writing code is the primary use case for this skill. -For any CDS documentation needs, you will need to use either of the following tools. -If neither are available you may let the user know but still continue on with the task as documentation is helpful but not required. +## Initialization -- Activate the `cds-docs` skill OR... -- If the `cds-docs` skill is not configured, try calling the CDS MCP server `list-cds-routes` tool. +Run this once per session, before doing anything else. -### Environment Detection +Run the discovery script: `scripts/discover-cds-packages.sh` -You must determine if you are operating in a React or React Native project before you write any code. +Its output tells you: -1. **Discover installed CDS packages and runtime** +- The `CDS Runtime` (`web` or `mobile`) — use this value as the `platform` argument for the CDS MCP server if it is needed. +- Every installed CDS package: its name, version, and valid export subpaths — these import paths are the ONLY ALLOWED PATHS when importing from CDS packages -Run the `bash` discovery script: `scripts/discover-cds-packages.sh` +If the script cannot be run, much of the information it provides can be determined via manual inspection: -This will gve you: +- Infer the platform by inspecting existing CDS imports in the project's source code +- Find valid import paths by reading the `exports` field of the `package.json` of installed CDS packages in `node_modules` -- The `CDS Runtime` (`web` or `mobile`) - use this value as the `platform` argument for the CDS MCP server -- Every installed CDS package: its name, version, and valid export subpaths - these import paths are the ONLY ALLOWED PATHS for importing from CDS packages. +## Coding Workflow -If you are unable to run the bash script, you can likely infer the `platform` by inspecting the project's source code. +For all frontend coding tasks, follow these steps in order. -2. Read the platform-specific styling and themeing documentation: +**YOU MUST** perform steps 1 and 2 before writing any code! -- `getting-started/styling` -- `getting-started/theming` +### Step 1: Prepare CDS documentation + +For any CDS documentation needs, use either of the following tools. +If neither are available, let the user know but continue — documentation is helpful but not required. -## Part 2: Workflow +- Activate the `cds-docs` skill OR... +- If the `cds-docs` skill is not configured, try calling the CDS MCP server `list-cds-routes` tool. -For all frontend coding tasks, you must follow these steps. +Then read the platform-specific docs (using the runtime detected in Initialization): -**YOU MUST** perform steps 1 and 2 before writing any code! +- `getting-started/styling` +- `getting-started/theming` -### Step 1: Identify the appropriate components +### Step 2: Identify the appropriate components Use `guidelines/components.md` to help identify the appropriate CDS components for the task. The guidelines file will cover most use cases, but you may optionally browse the CDS docs for the full list of supported CDS components. @@ -64,31 +69,48 @@ If you decide your task will require icons (`Icon` or `IconButton`) or illustrat | --------------------- | ----------------------------- | | `guidelines/icons.md` | `guidelines/illustrations.md` | -If the task involves icons, also follow `guidelines/icons.md` and use `scripts/discover-cds-icons.mjs` to search icon names. If the task involves illustrations, also follow `guidelines/illustrations.md` and use `scripts/discover-cds-illustrations.mjs` to search illustration names. - If no CDS component fits your use case, you may fall back to the following options in this order of priority: -1. use a custom React component from the project's codebase -2. build your own custom React component -3. use the native platform's JSX elements for bespoke UI +1. search for a relevant and reusable React component from the project's codebase to use +2. build your own custom React component using CDS primitives as building blocks +3. use the native platform's JSX elements (div, View, etc.) for bespoke UI as a last resort + +**IMPORTANT:** Always inform the user which CDS components you are planning to use before moving on to Step 3. -**IMPORTANT:** Always inform the user which CDS components you are planning to use before moving on to `Step 2`. +### Step 3: Optionally read component docs -### Step 2: Optionally read component docs +For any CDS component you plan to use, retrieve and read their documentation (see Step 1 in this workflow for more details on docs setup). -For any CDS component you plan to use, retrieve and read their documentation (see `Part 1` for more details on docs setup). +If documentation is not retrievable for any reason, the published type definitions for the component may be used to determine the full props API a component affords. This is no substitute for reading the documentation, but it can be a useful fallback when documentation is not available. -### Step 3: Execute the task (writing frontend code) +### Step 4: Execute the task (writing code) Now create or update the UI with proper CDS components and usage. -Most CDS component implement an API that allows you to apply the CDS design tokens, we call these 'style props'. Prefer setting these style props for styling components over setting custom style via inline styles or CSS. +#### Package scope + +The package name may vary between projects. Different repos may install CDS under different scopes. +Always match the full CDS package name(s) as determined in the initialization step. If the project already has CDS imports in existing code, match whatever scope those files use. + +#### Using the Design System + +In most cases, you should avoid using inline style objects or CSS classNames (web only). +Through these methods it is very easy to make common mistakes like using hardcoded property values instead of the CDS design tokens. +Doing so would break the component's connection to the CDS theme. + +If you must use a style object or a CSS className, you can still access the CDS theme either through the `useTheme` hook or by CSS variables (web only). + +Most CDS components implement an API that conveniently allows you to apply CDS design tokens, internally we call these 'style props'. + +In cds-web, style props essentially act as an API for applying atomic CSS classes, much like Tailwind's utility classes which are so prevelant in the web ecosystem. + +You should prefer setting these style props for styling components over setting custom style via inline styles or CSS. **Why this matters:** When you set `font`, `color`, `textAlign`, or other typography properties through `style` instead of props, the component loses its connection to the CDS theme. For example, setting `fontSize` and `fontWeight` via `style` without a `font` prop means the CDS font family never applies -- the text falls back to `inherit` and may render in the wrong typeface. -You should check a component's props table in their CDS docs page to verify what props are available. +You should check a component's documentation which includes a props table to verify the available API. -Example misuse of custom styles and their style props alternatives: +Examples of opportunities to use style props over inline styles: | Instead of `style` | Use the prop | | --------------------------------------------------------------- | -------------------------------------------------- | @@ -101,34 +123,14 @@ Example misuse of custom styles and their style props alternatives: | `style={{ padding: 16 }}` | `padding={2}` | | `style={{ backgroundColor: "..." }}` | `background="bgAlternate"` (or semantic token) | -If you need to further customize the style of a rendered CDS component or a specific style is not support via style props, you may reference: `guidelines/customizing-styles.md`. - -### Step 4: Validate changes +### Step 5: Validate changes Your task will be complete if: -1. You performed initialization steps in `Part 1` -2. You examined the user's request and identified specific CDS components to use -3. Your changes DO NOT include any raw rgb/hex/etc color values -4. Your changes DO NOT use any raw pixel values for spacing, border radius, etc. -5. You changes use style props (e.g. `font`, `color`, `textAlign`, `textTransform`, `padding`, `gap`) instead of customization via `style` or with CSS. -6. All import paths are valid CDS package exports (see section below) -7. Any project linting/typechecking tasks are passing - -#### Validating import paths - -**This is critical.** Do not guess or memorize CDS import paths. The discovery script output is the source of truth (see `Part 1` for details). - -Before writing or returning any CDS import, verify it against the export list from setup: - -1. Find the CDS package for the target platform in the discovery script output. -2. Confirm the subpath you want to import is listed as a valid export. -3. If the subpath is not listed, it does not exist -- pick the closest valid export instead. - -**The package name may vary between projects.** Different repos may install CDS under different scopes. Always use the package name reported by the discovery script, not a hardcoded scope. If the project already has CDS imports in existing code, match whatever scope those files use. - -Common mistakes to avoid: - -- Inventing deep subpaths like `/layout/Box` or `/buttons/Button` when the actual export is `/layout` or `/buttons`. -- Guessing a package scope when the project uses a different one. -- Assuming that the CDS docs examples use the same package name as the target project -- they may differ. +1. You performed skill initialization and explicitly identified the specific CDS components you would use +2. Your changes DO NOT include any raw rgb/hex/etc color values +3. Your changes DO NOT use any raw pixel values for spacing properties (padding, margin, gap, border radius). Explicit layout dimensions like `width` or `height` set to specific designer-specified values are acceptable. +4. Your changes DO NOT import any depreacted CDS components or hooks. +5. Your changes use components' style props (e.g. `font`, `color`, `background`, `textTransform`, `paddingX`, `gap`) instead of customization via inline `style` objects or with CSS classNames. +6. All import paths are valid CDS package exports, determined in initialization +7. The project's linting/typechecking/formatting tasks are passing diff --git a/skills/cds-code/evals/evals.json b/skills/cds-code/evals/evals.json index 1503e7a6ab..083cc6609d 100644 --- a/skills/cds-code/evals/evals.json +++ b/skills/cds-code/evals/evals.json @@ -11,12 +11,11 @@ "Uses ListCell with accessory=\"arrow\" for the three settings rows", "Uses fgMuted (not foregroundMuted or bespoke color) for the email address text", "Uses the generic Text component with the font prop, not derivitive Text components (e.g. TextHeadline, TextLabel1, etc.)", - "Uses the borderRadius prop for the rounded container cornders and uses a design token for the value (no pixels/percentages)", + "Uses the borderRadius prop for the rounded container corners and uses a design token for the value (no pixels/percentages)", "No hardcoded hex, rgb, or raw color values anywhere in the output code", "No raw pixel values used for spacing or border radius (e.g., no '16px', '8px' as literal strings)", "Uses VStack or HStack for layout composition", - "All CDS import paths are valid subpath exports (no invented deep paths like /layout/Box)", - "Before writing any code, the agent explicitly states which CDS components it plans to use" + "All CDS import paths are valid subpath exports (no invented deep paths like /layout/Box)" ] }, { @@ -31,8 +30,7 @@ "ModalFooter receives a primary Button (Save) and a secondary variant Button (Cancel)", "No style prop used for padding, color, gap, or other values that have CDS style prop equivalents", "Select is imported from the correct alpha subpath (e.g., @coinbase/cds-web/alpha/select or equivalent)", - "No raw pixel values or hex/rgb colors in the output", - "Before writing any code, the agent explicitly states which CDS components it plans to use" + "No raw pixel values or hex/rgb colors in the output" ] }, { @@ -46,8 +44,7 @@ "Uses ProgressBar with a progress value representing 60% for the determinate progress indicator", "Uses ProgressCircle with the indeterminate prop for the spinner", "ProgressBar and ProgressCircle are imported from @coinbase/cds-web/visualizations (the correct subpath), not from a generic top-level import or the separate @coinbase/cds-web-visualization package", - "No hardcoded hex, rgb, or raw color values in the output", - "Before writing any code, the agent explicitly states which CDS components it plans to use" + "No hardcoded hex, rgb, or raw color values in the output" ] }, { @@ -56,27 +53,71 @@ "expected_output": "A React component that uses Icon names home/activity/settings, sets active state only on Home, and uses token-based selected vs unselected color treatment.", "files": [], "expectations": [ - "Before writing code, the agent identifies Icon (and layout primitives) as planned CDS components", "Uses the exact icon names home, activity, and settings for the three nav items", "Applies active state specifically to Home and keeps Activity/Settings inactive", "Uses navigation-appropriate icon sizing", "Pairs selected state with token-based color treatment (no bespoke hex/rgb values)", - "No raw pixel or hex/rgb values are used" + "No raw pixel values for spacing or border radius (explicit layout dimensions like sidebar width are acceptable)" ] }, { "id": 5, "prompt": "Create a security empty state with a title, supporting text, and an illustration. Show compact and roomy versions of the same visual.", - "expected_output": "A React component that uses SpotIcon name=\"2fa\" with compact 24x24 and roomy 32x32 dimensions plus CDS text/layout primitives.", + "expected_output": "A React component that uses a CDS illustration component (SpotIcon, SpotSquare, Pictogram, or similar) with clearly different dimensions for the compact vs roomy variants, plus CDS text/layout primitives. No raw img tags, hardcoded colors, or scaleMultiplier.", "files": [], "expectations": [ - "Uses SpotIcon specifically", - "Uses SpotIcon name 2fa", - "Uses exact SpotIcon dimensions 24x24 and 32x32", + "Uses a CDS illustration component (SpotIcon, SpotSquare, Pictogram, or similar) — not a raw img, svg, or emoji", + "Shows two clearly distinct size variants (compact and roomy) with different illustration dimensions", "Uses CDS layout and generic Text primitives", "Does not use or suggest scaleMultiplier", "No raw pixel or hex/rgb values are used" ] + }, + { + "id": 6, + "prompt": "Build a React Native screen that shows a user's wallet balance at the top, a list of recent transactions below it, and a 'Send' button fixed to the bottom.", + "expected_output": "A React Native component using CDS mobile primitives — Box/VStack for layout (not raw View), Text with font prop for the balance and transaction text, and Button for the Send CTA. No StyleSheet.create with literal values, no raw hex colors, imports come from @cbhq/cds-mobile (or equivalent mobile package).", + "files": [], + "expectations": [ + "Uses Box, VStack, or HStack from the CDS mobile package for layout — not raw View from react-native", + "Uses the generic Text component with a font prop for balance and transaction text — not raw Text from react-native", + "Uses Button from CDS for the Send action", + "No StyleSheet.create calls with hardcoded literal values (px numbers, hex colors)", + "No hardcoded hex or rgb color values anywhere in the output", + "All imports reference the CDS mobile package (e.g. @cbhq/cds-mobile/*), not @cbhq/cds-web/*" + ] + }, + { + "id": 7, + "prompt": "Add a TextHeadline for the section title and a TextBody for the description below it.", + "expected_output": "The agent should not use the deprecated TextHeadline or TextBody shorthand components. Instead it should use the generic Text component with font=\"headline\" and font=\"body\" respectively.", + "files": [], + "expectations": [ + "Does NOT import or use TextHeadline", + "Does NOT import or use TextBody", + "Uses the generic Text component for both the title and description", + "Applies font=\"headline\" (or equivalent token) for the section title", + "Applies font=\"body\" (or equivalent token) for the description", + "No raw pixel or hex/rgb values are used" + ] + }, + { + "id": 8, + "prompt": "Review the components in src/features/checkout/ for CDS adherence.", + "expected_output": "The agent should enter review mode, apply the CDS review rules to the provided files, and produce ESLint-style findings grouped by file followed by an audit summary. Findings should flag inline styles, hardcoded hex colors, raw div/View usage, and StyleSheet.create with literal values.", + "files": [ + "evals/fixtures/eval-8/CheckoutSummary.tsx", + "evals/fixtures/eval-8/CheckoutItem.tsx" + ], + "expectations": [ + "Agent enters review mode and does not attempt to rewrite the files", + "Output contains per-file findings in the format: filename / line:col rule-name message", + "Flags inline style usage on div elements in CheckoutSummary.tsx (style-props or layout-primitives rule)", + "Flags hardcoded hex color values (e.g. #1652F0, #F5F5F5) in at least one file", + "Flags StyleSheet.create with literal values in CheckoutItem.tsx", + "Flags raw View and Text imports from react-native in CheckoutItem.tsx", + "Output ends with an Audit Summary section showing file-level issue counts" + ] } ] } diff --git a/skills/cds-code/evals/fixtures/eval-8/CheckoutItem.tsx b/skills/cds-code/evals/fixtures/eval-8/CheckoutItem.tsx new file mode 100644 index 0000000000..c8bd3d5819 --- /dev/null +++ b/skills/cds-code/evals/fixtures/eval-8/CheckoutItem.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; + +interface CheckoutItemProps { + name: string; + quantity: number; + price: number; +} + +// Individual line item in the checkout list (mobile) +export function CheckoutItem({ name, quantity, price }: CheckoutItemProps) { + return ( + + + {name} + Qty: {quantity} + + ${price.toFixed(2)} + + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 12, + paddingHorizontal: 16, + backgroundColor: '#FFFFFF', + borderBottomWidth: 1, + borderBottomColor: '#EEEEEE', + }, + info: { + flexDirection: 'column', + gap: 4, + }, + name: { + fontSize: 14, + fontWeight: '500', + color: '#111111', + }, + qty: { + fontSize: 12, + color: '#888888', + }, + price: { + fontSize: 14, + fontWeight: '600', + color: '#1652F0', + }, +}); diff --git a/skills/cds-code/evals/fixtures/eval-8/CheckoutSummary.tsx b/skills/cds-code/evals/fixtures/eval-8/CheckoutSummary.tsx new file mode 100644 index 0000000000..761b592034 --- /dev/null +++ b/skills/cds-code/evals/fixtures/eval-8/CheckoutSummary.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +interface CheckoutSummaryProps { + items: { name: string; price: number }[]; + total: number; +} + +// Order summary panel shown at checkout +export function CheckoutSummary({ items, total }: CheckoutSummaryProps) { + return ( +
+

Order summary

+ +
+ {items.map((item) => ( +
+ {item.name} + ${item.price.toFixed(2)} +
+ ))} +
+ +
+ Total + ${total.toFixed(2)} +
+
+ ); +} diff --git a/skills/cds-code/guidelines/code-review.md b/skills/cds-code/guidelines/code-review.md new file mode 100644 index 0000000000..645966e83d --- /dev/null +++ b/skills/cds-code/guidelines/code-review.md @@ -0,0 +1,529 @@ +# CDS Code Review + +--- + +## Clarify scope + +A valid review scope is something specific and bounded: a named feature, a page or screen, a surface area of the app, or an explicit set of files (up to 50). A review cannot be performed against open-ended requests like "the whole app". + +**If no scope was provided** — ask: _"What should I review? Please provide a specific feature, page, or set of files."_ Do not proceed until the user answers. + +**If the scope is too broad or vague** (e.g. "the whole app", "everything", "all our screens") — ask again for something specific. Explain that the review needs a bounded target to be useful. If they wish to review a broader scope, suggest they use an agent orchestration strategy to break the review up across multiple agents. + +**If the user repeatedly insists on an unreasonable scope** — end the workflow. Example: _"I can't perform an effective review at that scale in a single pass. If you'd like to scope it to a specific feature or set of files, I'm happy to help."_ + +**If the scope is specific but exceeds 50 files** — tell the user the count and ask them to narrow it before proceeding. + +--- + +## Step 1: Preparation + +Before reviewing any files: + +1. **Detect the platform** — Run `scripts/discover-cds-packages.sh` to confirm web vs. mobile and get the valid CDS import paths. Some rules apply to only one platform. + +--- + +## Step 2: Default Exclusions + +Unless the user asks otherwise, skip files that are not user-facing production UI code. Use judgment — the goal is to focus on code that actually ships to users. Common categories to skip: + +- **Test files** — unit tests, integration tests, fixtures, mocks +- **Dev tooling** — Storybook stories, debug screens, playground/sandbox files +- **Generated or non-code assets** — SVG files, auto-generated type definitions, build output +- **Third-party or vendor code** — anything not owned by the team being audited + +When in doubt about whether a file should be included, include it and let the findings speak for themselves. + +--- + +## Step 3: Apply the Rules + +Work through each file in scope and check it against the rules below. Focus on recurring patterns — if the same violation appears N times across a file, report it once with a note of how many occurrences there are rather than listing every line. + +Rules that require human judgment ("Review wrapper components that shadow CDS primitives", "Preserve CDS component accessibility") should be flagged as findings for the user to inspect rather than auto-suggested for fix. + +For "No shadow design systems", flag the source module once and note its consumer count rather than reporting every import site. + +--- + +## Step 4: Output Format + +Group findings by file. Use the following format: + +``` +src/screens/HomeScreen.tsx + 23:8 style-props Use `padding={2}` instead of `style={{ padding: 16 }}` + 67:15 hardcoded-colors Replace '#1652F0' with a semantic color token (e.g. `bgPrimary`) + +src/components/Card.tsx + 10:1 layout-primitives Use `` instead of `
` + 44:5 style-props Move `gap: 8` to the `gap={1}` prop (×3 occurrences in this file) +``` + +Column format: `line:col rule-name message` + +If no issues are found in a file, omit it from the output. + +End every review with a structured summary: + +```markdown +## Audit Summary + +**Files reviewed:** 12 +**Files with issues:** 4 + +| File | Issues | +| ------------------------------ | ------ | +| src/screens/HomeScreen.tsx | 2 | +| src/components/Card.tsx | 4 | +| src/utils/constants.ts | 1 | +| src/screens/SettingsScreen.tsx | 3 | + +**Issue breakdown by rule:** + +- style-props: 6 across 3 files +- hardcoded-colors: 3 across 2 files +- shadow-design-system: 1 — `src/utils/constants.ts` (migration required) +``` + +For findings that require migration work (e.g. shadow design systems, invalid import paths), note "migration required" and give high-level direction rather than generating full fix code during the review pass. + +--- + +# Review Rules + +--- + +## Prefer CDS style props over manual styling + +**Applies to:** web + mobile + +CDS components expose style props (`padding`, `gap`, `background`, `color`, `font`, etc.) that map directly to design tokens. Using `style={{}}`, `StyleSheet.create()`, or `styled()` template literals for properties that have a CDS prop equivalent bypasses the token system, breaks theming, and on mobile can silently drop the CoinbaseSans font family. + +**Properties that always have a CDS prop equivalent:** +`padding*`, `margin*`, `gap`, `rowGap`, `columnGap`, `background`, `backgroundColor`, `color`, `borderColor`, `borderRadius`, `borderWidth`, `font*`, `lineHeight`, `letterSpacing`, `textAlign`, `textTransform`, `display`, `flexDirection`, `alignItems`, `justifyContent`, `flex*`, `width`, `height`, `min/maxWidth`, `min/maxHeight`, `opacity` + +**Detect:** + +- `style={{ }}` on a CDS component +- `StyleSheet.create({ key: { : } })` (mobile) +- `styled(CdsComponent)` template literals containing any of the above CSS properties + +**Bad:** + +```tsx + + + + +const styles = StyleSheet.create({ container: { padding: 16, backgroundColor: '#fff' } }); + +const Trigger = styled(Box)(() => css` + display: flex; + column-gap: 4px; + padding-bottom: 4px; +`); +``` + +**Good:** + +```tsx + + + + +// StyleSheet OK when using theme values: +const styles = StyleSheet.create({ container: { padding: theme.space[2] } }); + +// styled() OK for properties without a CDS prop (cursor, transform, etc.): +const Trigger = styled(Box)(() => css` + border-bottom: 1px dashed var(--color-bgLine); + cursor: pointer; +`); + +``` + +**Skip:** Properties with no CDS prop equivalent (`cursor`, `transform`, `userSelect`, `overflow`, `pointerEvents`, exact pixel widths for designer-pinned layouts) are legitimate uses of `style`. + +--- + +## Use CDS layout components over raw HTML/RN primitives + +**Applies to:** web + mobile + +Raw `
`/`` (web) and `` (mobile) bypass CDS theming, responsive props, and spacing scale. Use `Box`, `VStack`, or `HStack` instead. + +**Detect:** + +- Web: `
` or `` where the style contains layout/spacing properties +- Mobile: `import { View } from 'react-native'` used as a layout container with a `style` prop + +**Bad:** + +```tsx +// Web +
+ +
; + +// Mobile +import { View } from 'react-native'; +return ; +``` + +**Good:** + +```tsx +// Web +import { Box } from '@cbhq/cds-web/layout'; + + +; + +// Mobile +import { Box } from '@cbhq/cds-mobile/layout'; +return ; +``` + +**Skip:** Raw `` is acceptable when passing a ref to a non-CDS third-party component that requires one. + +--- + +## No hardcoded color values + +**Applies to:** web + mobile + +Hardcoded hex/rgb/hsl literals prevent dark mode and break theming. CDS's type system actively rejects them (`Type '"#0000ff"' is not assignable to type 'Color | undefined'`). Use semantic color tokens (`bgPrimary`, `fgMuted`, `fgPositive`, `fgNegative`, etc.) instead. + +**Detect:** + +- `#[0-9a-fA-F]{3,8}` or `rgb(a)?\(` or `hsl(a)?\(` literals inside: + - JSX attribute values (`color="#FF0000"`) + - Inline `style` objects or `StyleSheet.create()` + - `styled-components` / Linaria template literals for color-type CSS properties + - Module-level color constant exports + +**Bad:** + +```tsx + + +export const color = { positive: '#61CA00', coinbase: '#1652F0' }; +``` + +**Good:** + +```tsx + + +// Web CSS: +background: var(--color-bgPrimary); +// Mobile with theme: +backgroundColor: theme.color.bgPositive +``` + +**Skip:** Test fixtures, embedded third-party widget configs (Google Maps styler, TradingView themes), and SVG illustration files. + +--- + +## No hardcoded spacing or sizing + +**Applies to:** web + mobile + +CDS uses an 8px base scale. Raw `px`/`em`/`rem` strings (web) or bare numbers (mobile) for spacing properties silently diverge from the scale when designers adjust it. Use CDS props with token values or CSS variables instead. + +**Detect:** Inside `style={{}}`, `StyleSheet.create()`, or `styled()` template literals — any of `padding*`, `margin*`, `gap`, `rowGap`, `columnGap`, `borderRadius`, `borderWidth` with a raw numeric or string value (excluding values already sourced from `theme.space[…]` or `var(--space-…)`). + +**Bad:** + +```tsx +// Web + +padding-bottom: 24px; // in styled-components + +// Mobile + +style={{ marginHorizontal: -16 }} +``` + +**Good:** + +```tsx +// Web (CDS prop) + +// Web (CSS var) +padding-bottom: var(--space-3); +// Mobile + // -16 → -2 × 8 + // 6px → 0.75 × 8 +``` + +**Width and height:** Explicit layout dimensions (`width`, `height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`) are a different category from spacing. Specific values like sidebar widths, column sizes, image dimensions, and fixed container heights are often intentional design decisions with no token equivalent — these are acceptable as raw values. Only flag `width`/`height` if it's clearly a spacing intent in disguise (e.g. `width: 16px` as a gap substitute). + +**Skip:** 1px dividers, `100%`, `auto`, `100vw`/`100vh`. + +--- + +## No shadow design systems + +**Applies to:** web + mobile + +A module that re-exports parallel color, spacing, or typography maps is a shadow design system — it defeats theming and makes it impossible to adopt dark mode for anything that consumes it. Flag the file itself rather than every individual consumer. + +**Detect:** A `*.ts`/`*.tsx` module that exports: + +- A `color`/`colors`/`palette` object whose values are hex/rgb literals AND whose keys overlap with CDS semantic names (`positive`, `negative`, `bg*`, `fg*`) or asset/brand names +- A `size`/`space`/`spacing` object whose values are CSS length strings (`'4px'`, `'8px'`) +- A `font`/`typography` object that maps `display1`/`title1`/`body`/`caption` to `{ fontSize, lineHeight, fontFamily }` tuples + +**Bad:** + +```ts +export const size = { tiny: '2px', small: '4px', medium: '8px', large: '16px' }; +export const color = { coinbase: '#1652F0', positive: '#61CA00', negative: '#FF4949' }; +export const typography = { + body: { fontSize: 14, lineHeight: 20, fontFamily: 'CoinbaseSans-Regular' }, +}; +``` + +**Good:** + +```tsx +import { useTheme } from '@cbhq/cds-web/system'; +const theme = useTheme(); +theme.space[2]; // 16px — adapts to scale changes +theme.color.bgPrimary; // adapts to color scheme +``` + +**Skip:** Truly app-specific tokens that cannot live in CDS (e.g. partner-brand colors for KYC card art) and third-party app directories with their own intentional brand. + +--- + +## Use semantic tokens for color scheme differences + +**Applies to:** web + mobile + +CDS semantic tokens (`bg`, `bgPrimary`, `fg`, `fgMuted`, etc.) automatically invert in dark mode. Branching on `activeColorScheme === 'dark'` to pick color values is a sign the wrong token is being used — or that no token exists yet. + +**Detect:** Any expression `activeColorScheme === 'dark'` (or `=== 'light'`) whose consequent/alternate are color literals or CDS color token strings. + +**Bad:** + +```tsx +const elementsColor = activeColorScheme === 'dark' ? 'fg' : 'fgInverse'; +``` + +**Good:** + +```tsx +// Use the semantic token that already expresses the intent +const elementsColor = 'fg'; + +// Acceptable: branching for non-color decisions (asset URLs, images) +return activeColorScheme === 'dark' ? darkModeImageUrl : lightModeImageUrl; +``` + +--- + +## Use CDS interactive components + +**Applies to:** web + mobile + +CDS provides `Button`, `IconButton`, and `Pressable` (plus `Interactable` on mobile) with built-in accessibility (`role`, `accessibilityLabel`, focus management, haptic feedback on iOS). Avoid lower-level primitives when a CDS interactive component fits. + +**Detect (mobile):** `import { TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback } from 'react-native'`. Check whether this is already covered by `oxlint.config.ts` or `.eslintrc.js` no-restricted-imports — if so, note "covered by existing lint rule" and skip. + +**Detect (web):** A click target built from a raw `
` or `` that isn't wrapping a third-party widget. + +**Bad:** + +```tsx +// Mobile +Press me + +// Web +
Press me
+``` + +**Good:** + +```tsx +// Mobile +import { Pressable } from '@cbhq/cds-mobile/components'; + + Press me +; + +// Web +import { Button } from '@cbhq/cds-web/buttons'; +; +``` + +--- + +## Use CDS icons and illustrations + +**Applies to:** web + mobile + +CDS ships an icon font (`Icon`), spot illustrations (`SpotIcon`, `SpotSquare`, `SpotRectangle`), and elevated product icons (`Pictogram`). Inline `` and raw `` imports bypass theming and need separate light/dark variants. + +**Detect:** + +- Inline `` elements in `.tsx` files (excluding auto-generated icon components inside the CDS icons package) +- `` or `import iconSrc from '…/icons/*.svg'` used with `` +- Custom `*Icon.tsx` components wrapping a raw `` + +**Before flagging:** Run `scripts/discover-cds-icons.mjs` and `scripts/discover-cds-illustrations.mjs` to confirm CDS has an equivalent. Only flag if a CDS replacement exists. + +**Bad:** + +```tsx + +Buy +``` + +**Good:** + +```tsx +import { Icon } from '@cbhq/cds-web/media'; +import { SpotIcon } from '@cbhq/cds-web/media'; + + +``` + +**Skip:** Brand-specific illustration assets with no CDS equivalent. + +--- + +## Preserve CDS component accessibility + +**Applies to:** web + mobile + +CDS components ship documented accessibility defaults: `Button` has `role="button"` + focus ring; `Modal` has focus trap + Esc-to-close; `Switch` has `role="switch"` + `aria-checked`; `TextInput` has label association. Reimplementing these from scratch with lower-level primitives drops all of this. + +**Detect (requires judgment):** A component whose name suggests a CDS primitive (`MyButton`, `CustomModal`, `CustomCheckbox`) that is built from `Pressable`/`Box`/`div` instead of wrapping the CDS primitive AND lacks `role`/`aria-*`/keyboard handlers. + +**Action:** Flag for human review rather than auto-suggesting a fix — the right call depends on why the custom component exists. + +**Good:** + +```tsx +import { Checkbox } from '@cbhq/cds-web/form'; +; +``` + +--- + +## Review wrapper components that shadow CDS primitives + +**Applies to:** web + mobile + +Custom components like `Heading`, `AppButton`, `CardWrapper`, or `BlueLink` that re-implement a CDS primitive and override token-driven styles are a pattern worth surfacing. They may exist for legitimate reasons (analytics, cross-cutting IDs) or may just strip CDS defaults. + +**Detect:** Files in `components/` whose name matches a CDS primitive (`Card`, `Button`, `Heading`, `Col`, `Row`, `Checkbox`) that import and wrap a CDS component with `styled()` or inline overrides of token-driven properties. + +**Action:** Flag as a finding — note whether the wrapper adds meaningful value. Suggest either using the CDS primitive directly or moving the cross-cutting concern (analytics, IDs) into a hook or HOC. + +--- + +## Validate CDS import paths + +**Applies to:** web + mobile + +Made-up import subpaths either fail at compile time or silently resolve through barrel files at the cost of extra bundle size. The discovery script output is the source of truth. + +**Detect:** Any `@cbhq/cds-*` (or project-equivalent scope) import whose subpath is not in the package's `exports` map. Run `scripts/discover-cds-packages.sh` to get the authoritative list of valid exports. + +**Common mistakes:** + +- `@cbhq/cds-web/layout/Box` — should be `@cbhq/cds-web/layout` +- `@cbhq/cds-web/buttons/Button` — should be `@cbhq/cds-web/buttons` +- Using a hardcoded scope (`@cbhq/`) when the project uses a different one + +--- + +## No deprecated CDS components or hooks + +**Applies to:** web + mobile + +Importing a deprecated CDS export means relying on something that may be removed in a future major version. Deprecated exports also typically have a better-supported replacement that should be preferred. + +**Detect:** + +- Any import from a CDS package of a named export marked `@deprecated` in its TypeScript types. Check by reading the relevant `.d.ts` file in `node_modules` for the installed package, or by referencing the CDS docs deprecation/migration notes. +- If the project uses `@cbhq/cds-migrator`, its codemods list is a reliable source of known deprecated → replacement mappings. + +**Bad:** + +```tsx +// Using a deprecated text shorthand component (v7 pattern) +import { TextBody } from '@cbhq/cds-web/typography'; +; +``` + +**Good:** + +```tsx +import { Text } from '@cbhq/cds-web/typography'; +; +``` + +**Action:** For each deprecated import found, note the recommended replacement from the CDS docs or the cds-migrator codemod list. + +--- + +## Review dangerouslySet\* usages + +**Applies to:** web + mobile + +Props starting with `dangerouslySet*` (e.g. `dangerouslySetBackground`, `dangerouslySetColor`) are named intentionally — they bypass the type-safe token system. Most uses are "we don't have a token yet" workarounds that should be revisited as CDS's token set grows. + +**Detect:** Any prop starting with `dangerouslySet` on a CDS component. + +**Action:** Flag each occurrence and check whether a semantic token now covers the use case. If not, leave a comment noting the pending migration. + +--- + +## CDS packages are on the latest published version + +**Applies to:** web + mobile + +Running outdated CDS versions means missing bug fixes, new components, and token updates. The discovery script already surfaces installed versions — pair that with a package manager query to check what's been published. + +**How to check:** + +1. The Initialization step already ran `scripts/discover-cds-packages.sh` and listed each installed CDS package and its version. Use that output. + +2. Detect the package manager from lockfiles in the project root: + + | Lockfile | Package manager | + | ------------------- | --------------- | + | `yarn.lock` | yarn | + | `package-lock.json` | npm | + | `pnpm-lock.yaml` | pnpm | + +3. For each installed CDS package, query the registry for the latest published version: + + ```bash + # yarn + yarn info version + + # npm + npm view version + + # pnpm + pnpm view version + ``` + +4. Compare installed vs. latest and report any gaps. + +**Output format:** + +``` +package-version-check + @cbhq/cds-web installed 9.4.1 → latest 9.6.0 + @cbhq/cds-icons installed 5.19.0 → latest 5.22.1 +``` + +**Note:** Flag major version gaps as higher priority than patch/minor gaps. A project multiple majors behind may be missing breaking-change migrations that affect correctness, not just new features. diff --git a/skills/cds-code/guidelines/components.md b/skills/cds-code/guidelines/components.md index 3b1dc18b47..2ad64c5b52 100644 --- a/skills/cds-code/guidelines/components.md +++ b/skills/cds-code/guidelines/components.md @@ -1,8 +1,14 @@ # CDS component selection guide -For full prop and type details, refer to the CDS component docs and TypeScript definitions; this guide is for _choosing_ components and patterns. +This guide is for _choosing_ the right component or pattern for a given UI need. Use this guide to get the _names_ of CDS components that may be relevant to your task. -When the user describes a UI need, reach for these first: +For a component's full props and style details, refer to the component documentation. + +## Common needs + +You may not need to read beyond this section if the common use-cases below solve your problem. + +Use the detailed sections below the table only when you need further clarification; they intentionally avoid full prop API dumps and focus on **when/why** to pick a component plus key gotchas. | Need | Use | | ------------------------------- | ------------------------------------------------------------------ | @@ -42,8 +48,6 @@ When the user describes a UI need, reach for these first: | Status pill / label | `Tag` | | User photo | `Avatar` | -Use the detailed sections below only when you need clarification; they intentionally avoid full prop API dumps and focus on **when/why** to pick a component plus key gotchas. - --- ## Categories diff --git a/skills/cds-code/guidelines/customizing-styles.md b/skills/cds-code/guidelines/customizing-styles.md deleted file mode 100644 index f0ef64468e..0000000000 --- a/skills/cds-code/guidelines/customizing-styles.md +++ /dev/null @@ -1,168 +0,0 @@ -# Customizing styles - -Prefer using CDS Design Tokens as values over hardcoded values. Examples: - -- On web, prefer `marginTop: 'var(--space-0_5)'` over `marginTop: '4px'`. -- On web, prefer `borderRadius: 'var(--borderRadius-200)'` over `borderRadius: '8px'`. -- On mobile, prefer `marginTop: theme.space[0.5]` over `marginTop: 4`. -- On mobile, prefer `borderRadius: theme.borderRadius[200]` over `borderRadius: 8`. -- Prefer `` over a custom wrapper with hardcoded CSS. - -### `style` on `Select` - -```tsx -import { memo, useState } from 'react'; -import { Select } from '@coinbase/cds-web/alpha/select'; // or '@coinbase/cds-mobile/alpha/select' -import { VStack } from '@coinbase/cds-web/layout'; // or '@coinbase/cds-mobile/layout' - -const selectOptions = [ - { value: 'option1', label: 'Option 1', description: 'Description' }, - { value: 'option2', label: 'Option 2', description: 'Description' }, - { value: 'option3', label: 'Option 3', description: 'Description' }, -]; - -export const SelectExample = memo(() => { - const [selectValue, setSelectValue] = useState(null); - - return ( - - - - ); -}); -``` - -### `styles` on `Select` - -```tsx -import { useState } from 'react'; -import { Select } from '@coinbase/cds-web/alpha/select'; // or '@coinbase/cds-mobile/alpha/select' - -function CustomStylesExample() { - const [value, setValue] = useState('1'); - const options = [ - { value: null, label: 'Remove selection' }, - { value: '1', label: 'Option 1' }, - { value: '2', label: 'Option 2' }, - { value: '3', label: 'Option 3' }, - { value: '4', label: 'Option 4' }, - ]; - - return ( -