Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/hooks/enforce-component-create.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ async function main() {
// edit-via-Write — let it pass (validate-tokens.mjs still applies).
if (existsSync(filePath) && !fileIsRecent(filePath)) process.exit(0)

if (skillReferencedInTranscript(input.transcript_path)) process.exit(0)
if (pipelineReferencedInTranscript(input.transcript_path)) process.exit(0)

const msg = [
`Component-create gate: blocked Write to ${relPath}`,
Expand Down
23 changes: 23 additions & 0 deletions .claude/hooks/validate-spec-compliance.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,29 @@ async function main() {
if (slotsDiff.missing.length) violations.push(`Slots missing in .vue: ${slotsDiff.missing.join(', ')}`)
if (slotsDiff.extra.length) violations.push(`Slots in .vue not in spec: ${slotsDiff.extra.join(', ')}`)

// ---- Naming conventions (B1): things the spec tables can't catch ----
// Visual-variant prop must be `kind` (never variant/appearance/intent). `color` is
// left out on purpose — some components legitimately take a color value.
const FORBIDDEN_PROP = { variant: 'kind', appearance: 'kind', intent: 'kind' }
for (const p of sfc.props) {
const alt = FORBIDDEN_PROP[p.name]
if (alt) {
violations.push(`Prop "${p.name}" is not allowed — use "${alt}" (.claude/rules/naming.md).`)
}
if (/^(is|has)[A-Z]/.test(p.name)) {
const suggested = p.name.replace(/^(is|has)/, '').replace(/^./, (c) => c.toLowerCase())
violations.push(`Boolean prop "${p.name}" must drop the is/has prefix (use "${suggested}").`)
}
}
// Events are kebab-case (update:value, before-close). Flag a camelCase event name,
// but never a v-model `update:*` event (its payload segment can be camelCase).
for (const e of sfc.emits) {
if (!e.name.includes(':') && /^[a-z]+[A-Z]/.test(e.name)) {
const suggested = e.name.replace(/([A-Z])/g, '-$1').toLowerCase()
violations.push(`Event "${e.name}" must be kebab-case (use "${suggested}").`)
}
}

// ---- defineOptions.name ----
const expectedName = toPascal(info.name)
if (sfc.defineOptionsName !== expectedName) {
Expand Down
30 changes: 30 additions & 0 deletions .claude/hooks/validate-tokens.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,36 @@ const VIOLATIONS = [
id: 'ts-ignore',
regex: /\/\/\s*@ts-(?:ignore|nocheck|expect-error)\b/g,
message: '`@ts-ignore`/`@ts-nocheck`/`@ts-expect-error`. Fix the underlying type issue.'
},
{
id: 'js-class-preset',
regex: /\bconst\s+\w*[Cc]lasses\s*=\s*(?:[{[]|computed\b)/g,
message:
'JS class preset (const *Classes = {…}/[…]/computed). Put utilities inline on the root class + switch variants with data-* (.claude/rules/styling.md).'
},
{
id: 'style-block',
regex: /<style[\s>]/g,
message:
'Component-local <style> block. Styles live inline on the root class as Tailwind utilities; no <style>/scoped CSS (.claude/rules/styling.md).'
},
{
id: 'keyframes-local',
regex: /@keyframes\b/g,
message:
'Component-local @keyframes. Add the animation to packages/theme/src/tokens/semantic/animations.js (run /add-animation) and use the animate-* utility.'
},
{
id: 'animate-arbitrary',
regex: /\banimate-\[/g,
message:
'Arbitrary animate-[…] value. Use a catalogued animate-* utility, or add one via /add-animation (semantic/animations.js).'
},
{
id: 'motion-hardcoded',
regex: /\b(?:duration|delay|ease)-\[/g,
message:
'Hardcoded duration/ease/delay. Use the duration/curve/ease tokens from primitives/animations (DESIGN.md § Animations).'
}
]

Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Use Vue `<Teleport to="body">` when the overlay must escape an `overflow: hidden
</Transition>
```

The catalog is in [`.claude/docs/DESIGN.md`](./tokens.md#animations--semanticanimationsjs).
The catalog is in [`.claude/docs/DESIGN.md`](../docs/DESIGN.md#animations--semanticanimationsjs).

## Hook coverage

Expand Down
4 changes: 2 additions & 2 deletions .claude/rules/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ When importing a component, pattern, or any artifact from **outside** this codeb
| Source | events may be camelCase (`onValueChange`) | Always kebab-case (`update:value`, `value-change`). |
| Source | boolean props may have `is`/`has` prefixes | No prefix (`disabled`, `loading`, `open`). |
| Source | `class` declared in `defineProps` | Never. Use `useAttrs()` + `rootClasses` merging `attrs.class`. |
| Source | Inline `@keyframes` or animation library | Only the semantic utilities catalogued in [`tokens.md`](./tokens.md#animations--semanticanimationsjs). No animation lib (see [`dependencies.md`](./dependencies.md)). |
| Source | Inline `@keyframes` or animation library | Only the semantic utilities catalogued in [`DESIGN.md`](../docs/DESIGN.md#animations--semanticanimationsjs). No animation lib (see [`dependencies.md`](./dependencies.md)). |
| Source | `@floating-ui/vue` for positioning | CSS only (anchor positioning, Popover API, `<Teleport>` + `position: fixed`). |
| Source | Their tokens (`#fff`, `--brand-50`, `text-blue-600`) | Our tokens (`var(--bg-canvas)`, `var(--primary)`, `text-button-lg`). Catalog: [`tokens.md`](./tokens.md). |
| Source | Their tokens (`#fff`, `--brand-50`, `text-blue-600`) | Our tokens (`var(--bg-canvas)`, `var(--primary)`, `text-button-lg`). Catalog: [`DESIGN.md`](../docs/DESIGN.md). |

### When migrating from Figma

Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/no-invention.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ You are a transcriber, not an author. The spec at `.specs/<name>.md` is the cont
7. **Do not edit files outside the paths your task names.**
8. **Do not bring artifacts from outside this codebase as-is.** Migrating from another design system, Figma, Base UI/Reka UI/Radix/shadcn/PrimeVue examples, a previous repo, or any inherited `CONTRACT.md`/`README.md` requires **rewriting** to our conventions. See [`migration.md`](./migration.md).
9. **Do not introduce external libs for positioning or animation.** No `floating-ui`, `popper.js`, `tippy`, `gsap`, `framer-motion`, `motion`, `@vueuse/motion`, `@formkit/auto-animate`, drag-drop runtimes, scroll virtualization. Use CSS + tokens + Vue primitives. Catalog: [`dependencies.md`](./dependencies.md).
10. **Do not improvise animations.** Every `animate-*` / `transition-*` class comes from the catalog. Every motion-bearing class pairs with `motion-reduce:*`. No component-local `@keyframes`. See [`tokens.md`](./tokens.md#animations--semanticanimationsjs).
10. **Do not improvise animations.** Every `animate-*` / `transition-*` class comes from the catalog. Every motion-bearing class pairs with `motion-reduce:*`. No component-local `@keyframes`. See [`DESIGN.md`](../docs/DESIGN.md#animations--semanticanimationsjs).
11. **Do not add Figma references to Storybook stories.** No `parameters.design`, no `parameters.figma`, no Figma URLs in `docs.description.component`, no addon-designs links. The story documents the **component API and behavior**; the Figma file documents the design. They stay separate. See [`.claude/docs/COMPONENT_REQUIREMENTS.md`](../docs/COMPONENT_REQUIREMENTS.md) § 13.x.
12. **Do not create CSS class presets in JavaScript** (`const kindClasses = {...}`, `const sharedClasses = [...]`, `const sizeClasses = {...}`, `const rootClasses = computed(...)` that only composes classes). Variants live on `data-*` attributes; styles live as Tailwind utilities on the root element's `class` attribute. No `<style>` block, no component-local `.css`/`.scss`. See [`styling.md`](./styling.md).
13. **NEVER create anything outside the standard.** If the only way to satisfy a request is to deviate from our patterns, that's a spec problem — emit `BLOCKED:` and stop.
Expand Down
Loading