[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6BXQHTKPDAXD7A7BKHSGK08":3,"skill-same:01M6BXQHTKPDAXD7A7BKHSGK08":79},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":31,"publishedAt":52,"linkOnly":53,"markdownContent":54,"markdownBody":55,"frontMatter":56,"refName":57,"commitSha":58,"skillDirectory":59,"forksCount":60,"repositoryHtmlUrl":61,"repositoryHomepageUrl":62,"ownerHtmlUrl":63,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":64,"install":65,"resources":78},"01M6BXQHTKPDAXD7A7BKHSGK08","optiaxiom-review",null,"Code review conventions and standards for the optiaxiom (@optiaxiom\u002Freact) design-system repo. Consult whenever reviewing a PR, reviewing changes, or self-reviewing a branch in this repo, and when writing or changing a component, story, recipe, or token so the change matches the repo's conventions before review.","optimizely-axiom\u002Foptiaxiom","optimizely-axiom","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F165137028?v=4","01M6BXQDPHYKTZ2BNKSB5WN7SG","Optimizely Axiom","01M6BXQDTQNF1XPVC9WAS6W0EQ",25,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Foptimizely-axiom\u002Foptiaxiom\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Foptiaxiom-review\u002FSKILL.md",[18,23,26],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000007","代码审查","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],"review","optiaxiom review","system","when","changes","for","recipe","self","standards","repo","branch","code review","consult","code","pr","component","change","react","writing","design","2026-11-10 10:12:30",false,"---\nname: optiaxiom-review\ndescription: Code review conventions and standards for the optiaxiom (@optiaxiom\u002Freact) design-system repo. Consult whenever reviewing a PR, reviewing changes, or self-reviewing a branch in this repo, and when writing or changing a component, story, recipe, or token so the change matches the repo's conventions before review.\n---\n\n# Optiaxiom Code Review\n\nYou are reviewing code for **@optiaxiom\u002Freact**, a React design system built on Radix primitives (plus `downshift` for comboboxes\u002Fselect, `react-day-picker` for calendar, `@tanstack` for virtual\u002Ftable), vanilla-extract (`recipe`\u002F`style`\u002F`sprinkles`), and a `Box`\u002F`Flex`\u002F`Text` styling layer. This skill encodes the repo's review priorities and conventions.\n\n> **Before citing an exact prop name, token, sprinkle value, or import path in a review, verify it against the current codebase** — `grep` the sprinkle defs in `packages\u002Freact\u002Fsrc\u002Fsprinkles\u002Fproperties.css.ts`, the component source, and `package.json`. The principles here are stable, but specific names and values change.\n\n> The condensed, enforceable subset of these rules lives in the repo root [CLAUDE.md](..\u002F..\u002F..\u002FCLAUDE.md) — that's what loads every session and what the generic `\u002Fcode-review` engine checks diffs against. This skill is the full reference: reasoning, examples, and long-tail conventions. Keep the two in sync when conventions change.\n\n## How to run a review\n\n1. Get the diff: `gh pr diff \u003Cnumber>` (or review the working tree).\n2. Walk every changed file against the **checklists** below, in roughly this priority order: API design → composition\u002Fforwarding → styling system rules → naming → stories → a11y → housekeeping.\n3. Write findings as **inline, specific, actionable** comments. Prefer a `suggestion` block with the exact replacement code over prose. One concern per comment.\n4. Lead with the _why_ when it's non-obvious — explain the reasoning, not just the rule.\n5. Post with `--comment` only if asked; otherwise present findings grouped by file.\n\n## How to write the comments\n\n- Short, direct, friendly. Suggest collaboratively rather than command (\"let's …\", \"can we …?\"), question rather than assert (\"why do we need this?\", \"this doesn't look right\"), and flag anything unnecessary (\"we don't need this\").\n- Always suggest the fix, don't just flag the problem. Use ` ```suggestion ` blocks heavily.\n- Explain the reasoning for anything architectural (\"the reason being…\", \"that way…\", \"otherwise…\").\n- It's fine to be tentative when genuinely unsure (\"i'm wondering if…\", \"not sure we need…\", \"is it possible to…\").\n- Defer to design\u002FFigma for visual values you can't verify (\"where did you see this in figma?\", \"this should be design feedback\").\n- Flag follow-ups explicitly as out of scope (\"let's address this in a separate PR\", \"todo: …\").\n\n---\n\n## 1. API & prop design (highest priority)\n\n- **Native HTML attribute names, not invented ones.** `disabled` not `isDisabled`; `error` not `isInvalid` (no `error` attr exists, but stay consistent with `disabled`\u002F`readonly`). Follow the platform; don't surprise users.\n- **Dash-case for prop string values**: `\"basic-link\"`, `\"danger-outline\"` — never camelCase values.\n- **Don't initialize falsy defaults**: `({ hideCloseIcon, ... })` not `hideCloseIcon = false`.\n- **Prefer opt-in booleans, inverted to the safe default**: `withCloseButton=false` rather than `hideCloseIcon`.\n- **Pick specific props from primitives instead of hard-coding unions**: `ComponentPropsWithRef\u003Ctypeof Button>[\"appearance\"]` instead of re-typing `\"danger\" | \"primary\" | …`. Use `Pick\u003C…, \"open\" | \"modal\">` rather than spreading a whole Root's props.\n- **Don't re-declare props already supplied by the base** (`children` from `Flex`, etc.). Use `BoxProps\u003Ctypeof X, {…}>` and let it flow.\n- **Reuse upstream types**: e.g. `TableOptions\u003CTData>` from tanstack rather than re-inventing `{columns, data}`. Don't re-export third-party types — consumers import them directly. Peer deps (react-table, etc.) are intentional.\n- **Generics over `unknown` casts**: combobox\u002Fselect item types should be a generic `Item`, threaded through props, context, and callbacks. Pass `selectedItem`\u002Fvalues through as-is without `String()`\u002F`itemToString` coercion.\n- **Controlled\u002Funcontrolled**: expose `defaultX` \u002F `x` \u002F `onXChange`; derive controlled-ness from `typeof valueProp === \"undefined\"`, or use `useControllableState`. Don't half-implement it. Remember controlled state is \"late by one render\" — pass your own state through context rather than reading `downshift.selectedItem` directly.\n- **Don't over-engineer.** Drop props that aren't needed yet (\"skip this for now \u002F later iteration\"). Don't add a prop for something CSS\u002Fmedia-query can do automatically (e.g. equal-width tabs on mobile). Don't expose internal toggles consumers shouldn't touch (e.g. don't let consumers remove a built-in search icon via a prop — override internally with `addonBefore={null}`).\n- **Naming = the primitive \u002F the ecosystem.** `Progress` not `ProgressBar`; `RadioGroup`, `Menu`, `FileUpload`, `DropdownMenuSub` — match the underlying Radix primitive and what most libraries call it. Drop redundant prefixes (`Field` not `FormField`).\n- **`Icon...` prefix for icons** so editor autocomplete surfaces them (`IconInfo`, not `InfoIcon`).\n- **`intent` + `variant` are the styling axes** (`intent` for color — `neutral`\u002F`primary`\u002F`danger`\u002F`information` etc.; `variant` for fill — `strong`\u002F`subtle`\u002F`outline`). Keep values consistent across components. `appearance` is a _preset_ shortcut mapping to an `{intent, variant}` combo — only for components with a limited fixed set of combos (Button), not free-form ones (Badge).\n\n## 2. Composition, refs, prop forwarding\n\n- **Spread `...props` onto the right element.** The outer `Box`\u002Fwrapper picks `className`; everything else forwards to the real inner control (`\u003Cinput>`, `\u003Ctextarea>`). Don't forward only `w`\u002Fsingle props — forward all.\n- **`ref` and only truly element-specific props (`disabled`, `id`) go on the inner primitive**; everything else (`{...props}` incl. sprinkles) goes on the outer `Box`\u002F`Text`. Never put `ref`\u002F`{...props}` on a `Title`\u002Finner node when there's an outer wrapper — put them on the outer. Spread `{...props}` straight onto the outer element via `BoxProps`\u002F`TextProps`; don't reintroduce a manual sprinkle-extraction split.\n- **Prop order controls override priority.** Put `data-*` and `{...styles.x()}` _before_ `{...props}` so consumers can override. (But controlled internal state must win where correctness requires.)\n- **Don't wrap virtual elements in `Box`.** Radix `Portal`\u002F`Root` render no DOM — no `asChild` Box around them; `Root` can't take a `ref` (forward it to the overlay\u002Fcontent instead).\n- **Box-composition direction**: `\u003CFlex asChild>\u003CRadixX.Overlay>` — Flex outside, primitive inside (`interface Overlay extends Flex`, the primitive has priority). Be consistent.\n- **Collapse redundant elements.** Don't use both `Flex` and `Text`\u002F`Box` when one element with the right props works. Don't wrap `children`\u002Fdecorators in extra boxes. Use `\u003C>` fragments when a parent already provides the wrapper element.\n- **Use the existing component, not raw styles.** `\u003CHeading level=\"3\">` not `fontSize=\"3xl\"`; `\u003CPaper>` for surfaces (gets light\u002Fdark bg); `\u003CButton>` for the dialog close (keyboard\u002Ffocus); reuse `Field`\u002F`FieldLabel`\u002F`SearchInput`\u002F`forceValueChange` rather than re-implementing.\n- **`asChild` + `Slot` only when consumers will realistically swap the element.** A single fixed empty element (separator) doesn't need it.\n- **Name the forwarded ref `outerRef`** and compose it with internal refs into `ref`: `const ref = useComposedRefs(innerRef, outerRef)`. Not `composedRef`, and don't leave the forwarded ref named `ref` once something composes with it.\n- **Don't clone\u002Fiterate children to inject props** (`Children.map` + `cloneElement`) — children may be fragments\u002Fnested. Pass state via **context** and read it in the child instead.\n- **Write handlers inline** (`onActiveChange={(active) => …}`), not hoisted into a named `useCallback`. Stable identity is a separate concern: when the receiver needs one (an effect dependency, a context value every consumer re-renders on), wrap the inline handler at the call site with `useEvent` (`packages\u002Freact\u002Fsrc\u002Fhooks\u002FuseEvent.ts`): `onActiveChange={useEvent((active) => …)}`.\n- **Don't manufacture synthetic events as `any`.** Re-trigger React's native setter (`forceValueChange`) to clear\u002Fset an input value, and re-focus after; or use `useImperativeHandle` to expose native methods like `showPicker()`.\n\n## 3. Styling system rules (vanilla-extract \u002F sprinkles)\n\n- **Tokens come through `theme` inside `.css.ts`** — never reference a token string directly in `style()` (`theme.colors[\"border.default\"]`, not `\"border.default\"`). Bare sprinkle keys are fine in the sprinkle object, but `style({})` needs `theme.*`.\n- **In `.tsx`, use native sprinkle props, not `sprinkles()` + `className`.** Rule of thumb: `sprinkles()` belongs in `.css.ts`; `.tsx` uses the props.\n- **If styles are just sprinkles, inline them in the `.tsx`** — don't create a `.css.ts` file at all. Conversely, don't hand-roll a one-off `.css.ts` for what `h=\"12\" w=\"auto\"` does.\n- **Don't mix `style()` and sprinkle props in the same recipe entry**; if it's a sprinkle prop, drop the `style()` wrapper. Don't reach for `style()` when sprinkles cover it.\n- **No hard-coded numbers\u002Fsizes\u002Fcolors — use the sprinkle scales.** Spacing (`gap`\u002F`p*`\u002F`m*`) takes **numeric pixel tokens** (`gap=\"8\"`, `pt=\"16\"`, `px=\"24\"`) — t-shirt names like `gap=\"xs\"` are no longer valid for spacing. Dimensions (`size`\u002F`w`\u002F`h`) and `borderRadius` still use t-shirt tokens (`size=\"md\"`, `theme.borderRadius.lg` not `8px`). Use the `size` token instead of separate matching `w`\u002F`h`; square things (icon buttons) use `size` to make the intent obvious. Use `surface` not `white` (light\u002Fdark).\n- **Empty variant branches are `{}`, not `style({})`** — `style({})` creates a needless empty class. Move styles shared by all variants into `base` and _reset_ them in the special branch.\n- **Generate `--` names, don't hand-write them.** `createVar()` for custom properties; `getVarName(createVar())` where a property takes a bare dashed name (`anchor-name`, `anchor-scope`). A literal `\"--name\"` can collide across components or package versions. Only names owned by another library (`--tw-ring-offset-width` in `Box.css.ts`) stay literal.\n- **CSS vars get a `Var` suffix** (`columnWidthVar`). Marker classes for cross-component selectors: export a `className`\u002Fmarker and reference it scoped — never write unscoped `[data-state=\"checked\"]` selectors that an ancestor could accidentally match.\n- **A new sprinkle prop needs its static lists updated.** Add it to `packages\u002Fshared\u002F.eslintplugin\u002Fconsistent-recipe-sprinkles.js` and `no-sprinkles-style-conflict.js`, and give it a docs props page (`apps\u002Fdocs\u002Fprops.config.mjs`, `apps\u002Fdocs\u002Fapp\u002F_meta.global.tsx`).\n- **`z` from the sprinkle\u002Ftoken scale** (`z=\"popover\"`, add `z=\"tooltip\"` = 10000). Don't hard-code `zIndex`.\n- **Don't fiddle with letter-spacing** except maybe headings. Don't set `font-weight: 400` (it's the default — no-op). Don't override default Button padding\u002Fshape\u002Fradius from a parent.\n- **Animations**: prefer the `Transition` component over hand-written keyframes\u002F`willChange`. Watch for flex `gap`\u002Fpadding causing \"jumps\" in height-animated accordions\u002Fdisclosures — apply padding to an _inner_ box, not the animated container.\n- **Elements take container width** — don't give components a fixed `w`. Avatars are `inline-flex` (flow with text). Avoid `inline styles via style={{}}` unless there's a very specific reason.\n\n## 4. Stories (storybook)\n\n- **First\u002Fdefault story is named `Basic`** (not `Default`). No `With…` prefixes — mirror the existing Input\u002FButton story names.\n- **Use `args`, not `render`,** wherever possible so controls integrate. When `render` is needed, forward `(args)` and spread them; build variant stories with `...Basic, args: {…}`.\n- **One concern per story.** Don't pile `info` + `label` + `error` into Basic — split them. Don't sprinkle a secondary feature (e.g. `addonBefore`\u002F`addonAfter`) across every story; give it one dedicated story. Name stories for intent (`IconOnly`, `IconWithText`, `ComplexContent`, `LongContent`).\n- **Don't hand-specify `argTypes`\u002F`component`\u002F`title`\u002F`layout: centered`** that Storybook auto-detects. Do specify the `action`\u002F`onX` arg wiring when needed, and the `design`\u002Ffigma URL.\n- **Story args should be deliberate** — don't pass default-valued args, don't forward parent `args` down to every child in a group story (set them only on the parent).\n- **Add `play` functions that assert real behavior** (open\u002Fclose, scrollbar via `offsetWidth \u003C scrollWidth`), not just render.\n- Components still in development export from **`unstable`** only — stories import from `@optiaxiom\u002Freact\u002Funstable`, and `index.ts` doesn't export them yet.\n\n## 5. Accessibility\n\n- Use a real **`\u003Cbutton>`** for any interactive trigger (keyboard, focus, SR) — don't style a `div`. This is a recurring hard rule.\n- **`fill=\"currentColor\"`** on icon SVGs so color is CSS-controllable; `aria-hidden` decorative icons (or rather, just drop `role=\"img\"`); icon-only buttons need `aria-label` (Capitalized).\n- Prefer accessible **roles\u002Flabels** over `data-testid`; reserve `data-testid` for the edge cases. Meaningful `aria-label`s (\"Main navigation\", not \"side\").\n- `htmlFor`\u002F`id` wiring on labels; generate `id` with `useId` when not provided — never _require_ an `id` prop. Dynamic ids for repeated fields (don't hard-code `id=\"iconInfo\"`).\n- Info\u002Fhelp icons must **not** live inside `\u003Clabel>` (they'd join the accessible name and steal focus to the input) — wrap label + icon in a separate element.\n- `aria-current=\"page\"` on the current breadcrumb. Don't ship empty table headers — add visually-hidden labels.\n- `data-*` attributes are for exposing state to consumers \u002F consuming downstream libs; for state you control yourself, use a **variant**, not a `data-` attr. Keep both `aria-*` and `data-*` but **style only off `data-*`** (aria is the intent, data is the styling contract).\n\n## 6. Housekeeping (still always flagged)\n\n- **No `eslint-disable`** unless truly unavoidable (e.g. add the real `key`, don't disable the rule). Run prettier. Remove commented-out code and stray comments.\n- **Naming conventions**: components\u002Ftypes `PascalCase`; everything else (styles, vars, files) `camelCase`, ideally a single word (`item`, `option`, `indicator`, `wrapper`\u002Fcomponent-name for the root). Directories `kebab-case`. Story files have no spaces.\n- **Blank lines group related blocks** like paragraphs — keep them; add them between import block and code, between JSX siblings for readability.\n- **Drop out-of-scope commits\u002Fchanges** (\"this exists in main now\", \"drop this commit\", \"split this PR by component\", \"squash into one commit\"). A PR should do one thing.\n- **Keep the diff minimal** — revert unrelated formatting\u002Fimport-order churn; rebase against `main` when asked.\n- **Changesets**: required for user-facing changes (often both `react` and `web-components`). Use normal semver — `patch` for fixes, `minor` for backwards-compatible features, `major` for breaking changes. Reword changeset prose to be consumer-facing and accurate (\"added X to Y via `prop`\"). Follow-ups to an unreleased feature may not need their own changeset.\n- **Dependencies** go in the owning workspace's `package.json`, not the root. Watch for accidental deps (`framer-motion`, build plugins) — \"don't need this\". pnpm quirk: a transitive sub-package occasionally has to be added as an explicit direct dep for a peer\u002Fimport to resolve.\n- New components must be wired everywhere: `index.ts`\u002F`unstable.ts` export **and** `packages\u002Fweb-components\u002Fpackage.json`.\n- Pin\u002Fhold-back dep bumps that break the toolchain, and **leave a comment explaining why** (e.g. TS held at 5.5 for eslint-plugin compat). Same for non-obvious CSS (16px input font to stop iOS zoom; line-height tweaks for borders) — keep them and add an explanatory comment.\n\n---\n\n## Reasoning patterns to apply (not just rules)\n\nWhen you find an issue, reach for the underlying principles:\n\n- **\"Who owns this?\"** Consumers own content\u002Fstyling of decorators and children; the lib owns structure and behavior. Don't dictate capitalization, don't style injected content, don't lock consumers into `lineClamp`.\n- **\"Is this the primitive's job?\"** Reuse Radix\u002Fdownshift\u002Ftanstack state and behavior; don't reimplement focus traps, selection, pagination state.\n- **\"Will this break `asChild`\u002FSlot or composition?\"** Single-child constraints, virtual elements, prop priority.\n- **\"Does this scale to N \u002F dark mode \u002F mobile \u002F multi-line \u002F RTL?\"** `start`\u002F`end` not `left`\u002F`right`; responsive sprinkle arrays; `surface` token; `flex=\"none\"` + `align-items: start` for multi-line.\n- **\"Is this in scope?\"** Push unrelated fixes, speculative props, and design-value debates out — into a separate PR, a `todo`, or back to the designer\u002FFigma.\n- **\"Minimal, consistent, native.\"** The smallest diff, matching existing conventions, using the platform's own names and attributes.\n","# Optiaxiom Code Review\n\nYou are reviewing code for **@optiaxiom\u002Freact**, a React design system built on Radix primitives (plus `downshift` for comboboxes\u002Fselect, `react-day-picker` for calendar, `@tanstack` for virtual\u002Ftable), vanilla-extract (`recipe`\u002F`style`\u002F`sprinkles`), and a `Box`\u002F`Flex`\u002F`Text` styling layer. This skill encodes the repo's review priorities and conventions.\n\n> **Before citing an exact prop name, token, sprinkle value, or import path in a review, verify it against the current codebase** — `grep` the sprinkle defs in `packages\u002Freact\u002Fsrc\u002Fsprinkles\u002Fproperties.css.ts`, the component source, and `package.json`. The principles here are stable, but specific names and values change.\n\n> The condensed, enforceable subset of these rules lives in the repo root [CLAUDE.md](..\u002F..\u002F..\u002FCLAUDE.md) — that's what loads every session and what the generic `\u002Fcode-review` engine checks diffs against. This skill is the full reference: reasoning, examples, and long-tail conventions. Keep the two in sync when conventions change.\n\n## How to run a review\n\n1. Get the diff: `gh pr diff \u003Cnumber>` (or review the working tree).\n2. Walk every changed file against the **checklists** below, in roughly this priority order: API design → composition\u002Fforwarding → styling system rules → naming → stories → a11y → housekeeping.\n3. Write findings as **inline, specific, actionable** comments. Prefer a `suggestion` block with the exact replacement code over prose. One concern per comment.\n4. Lead with the _why_ when it's non-obvious — explain the reasoning, not just the rule.\n5. Post with `--comment` only if asked; otherwise present findings grouped by file.\n\n## How to write the comments\n\n- Short, direct, friendly. Suggest collaboratively rather than command (\"let's …\", \"can we …?\"), question rather than assert (\"why do we need this?\", \"this doesn't look right\"), and flag anything unnecessary (\"we don't need this\").\n- Always suggest the fix, don't just flag the problem. Use ` ```suggestion ` blocks heavily.\n- Explain the reasoning for anything architectural (\"the reason being…\", \"that way…\", \"otherwise…\").\n- It's fine to be tentative when genuinely unsure (\"i'm wondering if…\", \"not sure we need…\", \"is it possible to…\").\n- Defer to design\u002FFigma for visual values you can't verify (\"where did you see this in figma?\", \"this should be design feedback\").\n- Flag follow-ups explicitly as out of scope (\"let's address this in a separate PR\", \"todo: …\").\n\n---\n\n## 1. API & prop design (highest priority)\n\n- **Native HTML attribute names, not invented ones.** `disabled` not `isDisabled`; `error` not `isInvalid` (no `error` attr exists, but stay consistent with `disabled`\u002F`readonly`). Follow the platform; don't surprise users.\n- **Dash-case for prop string values**: `\"basic-link\"`, `\"danger-outline\"` — never camelCase values.\n- **Don't initialize falsy defaults**: `({ hideCloseIcon, ... })` not `hideCloseIcon = false`.\n- **Prefer opt-in booleans, inverted to the safe default**: `withCloseButton=false` rather than `hideCloseIcon`.\n- **Pick specific props from primitives instead of hard-coding unions**: `ComponentPropsWithRef\u003Ctypeof Button>[\"appearance\"]` instead of re-typing `\"danger\" | \"primary\" | …`. Use `Pick\u003C…, \"open\" | \"modal\">` rather than spreading a whole Root's props.\n- **Don't re-declare props already supplied by the base** (`children` from `Flex`, etc.). Use `BoxProps\u003Ctypeof X, {…}>` and let it flow.\n- **Reuse upstream types**: e.g. `TableOptions\u003CTData>` from tanstack rather than re-inventing `{columns, data}`. Don't re-export third-party types — consumers import them directly. Peer deps (react-table, etc.) are intentional.\n- **Generics over `unknown` casts**: combobox\u002Fselect item types should be a generic `Item`, threaded through props, context, and callbacks. Pass `selectedItem`\u002Fvalues through as-is without `String()`\u002F`itemToString` coercion.\n- **Controlled\u002Funcontrolled**: expose `defaultX` \u002F `x` \u002F `onXChange`; derive controlled-ness from `typeof valueProp === \"undefined\"`, or use `useControllableState`. Don't half-implement it. Remember controlled state is \"late by one render\" — pass your own state through context rather than reading `downshift.selectedItem` directly.\n- **Don't over-engineer.** Drop props that aren't needed yet (\"skip this for now \u002F later iteration\"). Don't add a prop for something CSS\u002Fmedia-query can do automatically (e.g. equal-width tabs on mobile). Don't expose internal toggles consumers shouldn't touch (e.g. don't let consumers remove a built-in search icon via a prop — override internally with `addonBefore={null}`).\n- **Naming = the primitive \u002F the ecosystem.** `Progress` not `ProgressBar`; `RadioGroup`, `Menu`, `FileUpload`, `DropdownMenuSub` — match the underlying Radix primitive and what most libraries call it. Drop redundant prefixes (`Field` not `FormField`).\n- **`Icon...` prefix for icons** so editor autocomplete surfaces them (`IconInfo`, not `InfoIcon`).\n- **`intent` + `variant` are the styling axes** (`intent` for color — `neutral`\u002F`primary`\u002F`danger`\u002F`information` etc.; `variant` for fill — `strong`\u002F`subtle`\u002F`outline`). Keep values consistent across components. `appearance` is a _preset_ shortcut mapping to an `{intent, variant}` combo — only for components with a limited fixed set of combos (Button), not free-form ones (Badge).\n\n## 2. Composition, refs, prop forwarding\n\n- **Spread `...props` onto the right element.** The outer `Box`\u002Fwrapper picks `className`; everything else forwards to the real inner control (`\u003Cinput>`, `\u003Ctextarea>`). Don't forward only `w`\u002Fsingle props — forward all.\n- **`ref` and only truly element-specific props (`disabled`, `id`) go on the inner primitive**; everything else (`{...props}` incl. sprinkles) goes on the outer `Box`\u002F`Text`. Never put `ref`\u002F`{...props}` on a `Title`\u002Finner node when there's an outer wrapper — put them on the outer. Spread `{...props}` straight onto the outer element via `BoxProps`\u002F`TextProps`; don't reintroduce a manual sprinkle-extraction split.\n- **Prop order controls override priority.** Put `data-*` and `{...styles.x()}` _before_ `{...props}` so consumers can override. (But controlled internal state must win where correctness requires.)\n- **Don't wrap virtual elements in `Box`.** Radix `Portal`\u002F`Root` render no DOM — no `asChild` Box around them; `Root` can't take a `ref` (forward it to the overlay\u002Fcontent instead).\n- **Box-composition direction**: `\u003CFlex asChild>\u003CRadixX.Overlay>` — Flex outside, primitive inside (`interface Overlay extends Flex`, the primitive has priority). Be consistent.\n- **Collapse redundant elements.** Don't use both `Flex` and `Text`\u002F`Box` when one element with the right props works. Don't wrap `children`\u002Fdecorators in extra boxes. Use `\u003C>` fragments when a parent already provides the wrapper element.\n- **Use the existing component, not raw styles.** `\u003CHeading level=\"3\">` not `fontSize=\"3xl\"`; `\u003CPaper>` for surfaces (gets light\u002Fdark bg); `\u003CButton>` for the dialog close (keyboard\u002Ffocus); reuse `Field`\u002F`FieldLabel`\u002F`SearchInput`\u002F`forceValueChange` rather than re-implementing.\n- **`asChild` + `Slot` only when consumers will realistically swap the element.** A single fixed empty element (separator) doesn't need it.\n- **Name the forwarded ref `outerRef`** and compose it with internal refs into `ref`: `const ref = useComposedRefs(innerRef, outerRef)`. Not `composedRef`, and don't leave the forwarded ref named `ref` once something composes with it.\n- **Don't clone\u002Fiterate children to inject props** (`Children.map` + `cloneElement`) — children may be fragments\u002Fnested. Pass state via **context** and read it in the child instead.\n- **Write handlers inline** (`onActiveChange={(active) => …}`), not hoisted into a named `useCallback`. Stable identity is a separate concern: when the receiver needs one (an effect dependency, a context value every consumer re-renders on), wrap the inline handler at the call site with `useEvent` (`packages\u002Freact\u002Fsrc\u002Fhooks\u002FuseEvent.ts`): `onActiveChange={useEvent((active) => …)}`.\n- **Don't manufacture synthetic events as `any`.** Re-trigger React's native setter (`forceValueChange`) to clear\u002Fset an input value, and re-focus after; or use `useImperativeHandle` to expose native methods like `showPicker()`.\n\n## 3. Styling system rules (vanilla-extract \u002F sprinkles)\n\n- **Tokens come through `theme` inside `.css.ts`** — never reference a token string directly in `style()` (`theme.colors[\"border.default\"]`, not `\"border.default\"`). Bare sprinkle keys are fine in the sprinkle object, but `style({})` needs `theme.*`.\n- **In `.tsx`, use native sprinkle props, not `sprinkles()` + `className`.** Rule of thumb: `sprinkles()` belongs in `.css.ts`; `.tsx` uses the props.\n- **If styles are just sprinkles, inline them in the `.tsx`** — don't create a `.css.ts` file at all. Conversely, don't hand-roll a one-off `.css.ts` for what `h=\"12\" w=\"auto\"` does.\n- **Don't mix `style()` and sprinkle props in the same recipe entry**; if it's a sprinkle prop, drop the `style()` wrapper. Don't reach for `style()` when sprinkles cover it.\n- **No hard-coded numbers\u002Fsizes\u002Fcolors — use the sprinkle scales.** Spacing (`gap`\u002F`p*`\u002F`m*`) takes **numeric pixel tokens** (`gap=\"8\"`, `pt=\"16\"`, `px=\"24\"`) — t-shirt names like `gap=\"xs\"` are no longer valid for spacing. Dimensions (`size`\u002F`w`\u002F`h`) and `borderRadius` still use t-shirt tokens (`size=\"md\"`, `theme.borderRadius.lg` not `8px`). Use the `size` token instead of separate matching `w`\u002F`h`; square things (icon buttons) use `size` to make the intent obvious. Use `surface` not `white` (light\u002Fdark).\n- **Empty variant branches are `{}`, not `style({})`** — `style({})` creates a needless empty class. Move styles shared by all variants into `base` and _reset_ them in the special branch.\n- **Generate `--` names, don't hand-write them.** `createVar()` for custom properties; `getVarName(createVar())` where a property takes a bare dashed name (`anchor-name`, `anchor-scope`). A literal `\"--name\"` can collide across components or package versions. Only names owned by another library (`--tw-ring-offset-width` in `Box.css.ts`) stay literal.\n- **CSS vars get a `Var` suffix** (`columnWidthVar`). Marker classes for cross-component selectors: export a `className`\u002Fmarker and reference it scoped — never write unscoped `[data-state=\"checked\"]` selectors that an ancestor could accidentally match.\n- **A new sprinkle prop needs its static lists updated.** Add it to `packages\u002Fshared\u002F.eslintplugin\u002Fconsistent-recipe-sprinkles.js` and `no-sprinkles-style-conflict.js`, and give it a docs props page (`apps\u002Fdocs\u002Fprops.config.mjs`, `apps\u002Fdocs\u002Fapp\u002F_meta.global.tsx`).\n- **`z` from the sprinkle\u002Ftoken scale** (`z=\"popover\"`, add `z=\"tooltip\"` = 10000). Don't hard-code `zIndex`.\n- **Don't fiddle with letter-spacing** except maybe headings. Don't set `font-weight: 400` (it's the default — no-op). Don't override default Button padding\u002Fshape\u002Fradius from a parent.\n- **Animations**: prefer the `Transition` component over hand-written keyframes\u002F`willChange`. Watch for flex `gap`\u002Fpadding causing \"jumps\" in height-animated accordions\u002Fdisclosures — apply padding to an _inner_ box, not the animated container.\n- **Elements take container width** — don't give components a fixed `w`. Avatars are `inline-flex` (flow with text). Avoid `inline styles via style={{}}` unless there's a very specific reason.\n\n## 4. Stories (storybook)\n\n- **First\u002Fdefault story is named `Basic`** (not `Default`). No `With…` prefixes — mirror the existing Input\u002FButton story names.\n- **Use `args`, not `render`,** wherever possible so controls integrate. When `render` is needed, forward `(args)` and spread them; build variant stories with `...Basic, args: {…}`.\n- **One concern per story.** Don't pile `info` + `label` + `error` into Basic — split them. Don't sprinkle a secondary feature (e.g. `addonBefore`\u002F`addonAfter`) across every story; give it one dedicated story. Name stories for intent (`IconOnly`, `IconWithText`, `ComplexContent`, `LongContent`).\n- **Don't hand-specify `argTypes`\u002F`component`\u002F`title`\u002F`layout: centered`** that Storybook auto-detects. Do specify the `action`\u002F`onX` arg wiring when needed, and the `design`\u002Ffigma URL.\n- **Story args should be deliberate** — don't pass default-valued args, don't forward parent `args` down to every child in a group story (set them only on the parent).\n- **Add `play` functions that assert real behavior** (open\u002Fclose, scrollbar via `offsetWidth \u003C scrollWidth`), not just render.\n- Components still in development export from **`unstable`** only — stories import from `@optiaxiom\u002Freact\u002Funstable`, and `index.ts` doesn't export them yet.\n\n## 5. Accessibility\n\n- Use a real **`\u003Cbutton>`** for any interactive trigger (keyboard, focus, SR) — don't style a `div`. This is a recurring hard rule.\n- **`fill=\"currentColor\"`** on icon SVGs so color is CSS-controllable; `aria-hidden` decorative icons (or rather, just drop `role=\"img\"`); icon-only buttons need `aria-label` (Capitalized).\n- Prefer accessible **roles\u002Flabels** over `data-testid`; reserve `data-testid` for the edge cases. Meaningful `aria-label`s (\"Main navigation\", not \"side\").\n- `htmlFor`\u002F`id` wiring on labels; generate `id` with `useId` when not provided — never _require_ an `id` prop. Dynamic ids for repeated fields (don't hard-code `id=\"iconInfo\"`).\n- Info\u002Fhelp icons must **not** live inside `\u003Clabel>` (they'd join the accessible name and steal focus to the input) — wrap label + icon in a separate element.\n- `aria-current=\"page\"` on the current breadcrumb. Don't ship empty table headers — add visually-hidden labels.\n- `data-*` attributes are for exposing state to consumers \u002F consuming downstream libs; for state you control yourself, use a **variant**, not a `data-` attr. Keep both `aria-*` and `data-*` but **style only off `data-*`** (aria is the intent, data is the styling contract).\n\n## 6. Housekeeping (still always flagged)\n\n- **No `eslint-disable`** unless truly unavoidable (e.g. add the real `key`, don't disable the rule). Run prettier. Remove commented-out code and stray comments.\n- **Naming conventions**: components\u002Ftypes `PascalCase`; everything else (styles, vars, files) `camelCase`, ideally a single word (`item`, `option`, `indicator`, `wrapper`\u002Fcomponent-name for the root). Directories `kebab-case`. Story files have no spaces.\n- **Blank lines group related blocks** like paragraphs — keep them; add them between import block and code, between JSX siblings for readability.\n- **Drop out-of-scope commits\u002Fchanges** (\"this exists in main now\", \"drop this commit\", \"split this PR by component\", \"squash into one commit\"). A PR should do one thing.\n- **Keep the diff minimal** — revert unrelated formatting\u002Fimport-order churn; rebase against `main` when asked.\n- **Changesets**: required for user-facing changes (often both `react` and `web-components`). Use normal semver — `patch` for fixes, `minor` for backwards-compatible features, `major` for breaking changes. Reword changeset prose to be consumer-facing and accurate (\"added X to Y via `prop`\"). Follow-ups to an unreleased feature may not need their own changeset.\n- **Dependencies** go in the owning workspace's `package.json`, not the root. Watch for accidental deps (`framer-motion`, build plugins) — \"don't need this\". pnpm quirk: a transitive sub-package occasionally has to be added as an explicit direct dep for a peer\u002Fimport to resolve.\n- New components must be wired everywhere: `index.ts`\u002F`unstable.ts` export **and** `packages\u002Fweb-components\u002Fpackage.json`.\n- Pin\u002Fhold-back dep bumps that break the toolchain, and **leave a comment explaining why** (e.g. TS held at 5.5 for eslint-plugin compat). Same for non-obvious CSS (16px input font to stop iOS zoom; line-height tweaks for borders) — keep them and add an explanatory comment.\n\n---\n\n## Reasoning patterns to apply (not just rules)\n\nWhen you find an issue, reach for the underlying principles:\n\n- **\"Who owns this?\"** Consumers own content\u002Fstyling of decorators and children; the lib owns structure and behavior. Don't dictate capitalization, don't style injected content, don't lock consumers into `lineClamp`.\n- **\"Is this the primitive's job?\"** Reuse Radix\u002Fdownshift\u002Ftanstack state and behavior; don't reimplement focus traps, selection, pagination state.\n- **\"Will this break `asChild`\u002FSlot or composition?\"** Single-child constraints, virtual elements, prop priority.\n- **\"Does this scale to N \u002F dark mode \u002F mobile \u002F multi-line \u002F RTL?\"** `start`\u002F`end` not `left`\u002F`right`; responsive sprinkle arrays; `surface` token; `flex=\"none\"` + `align-items: start` for multi-line.\n- **\"Is this in scope?\"** Push unrelated fixes, speculative props, and design-value debates out — into a separate PR, a `todo`, or back to the designer\u002FFigma.\n- **\"Minimal, consistent, native.\"** The smallest diff, matching existing conventions, using the platform's own names and attributes.\n",{"name":5,"description":7},"main","158e8d4d87464676e67a1d9cb4fef53214a13f7f",".claude\u002Fskills\u002Foptiaxiom-review",31,"https:\u002F\u002Fgithub.com\u002Foptimizely-axiom\u002Foptiaxiom","https:\u002F\u002Foptimizely-axiom.github.io\u002F","https:\u002F\u002Fgithub.com\u002Foptimizely-axiom","2026-11-14 03:21:58",{"source":8,"skillName":5,"commands":66,"prompt":76,"zipAvailable":77},[67,70,73],{"tool":68,"command":69},"npx","npx skills add optimizely-axiom\u002Foptiaxiom --skill optiaxiom-review",{"tool":71,"command":72},"bunx","bunx skills add optimizely-axiom\u002Foptiaxiom --skill optiaxiom-review",{"tool":74,"command":75},"pnpm","pnpm dlx skills add optimizely-axiom\u002Foptiaxiom --skill optiaxiom-review","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Foptimizely-axiom\u002Foptiaxiom\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Foptiaxiom-review\u002FSKILL.md",true,[],[80],{"skillNo":81,"name":82,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":86,"publishedAt":52},"01M6BXQMWKR9Q41PP29RFFD7ZE","code-review","https:\u002F\u002Fgithub.com\u002Foptimizely-axiom\u002Foptiaxiom\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fcode-review\u002FSKILL.md",[85],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[43,32,45]]