pasika 0.1.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +99 -78
  2. package/dist/cli/index.d.ts +2 -0
  3. package/dist/cli/index.js +132 -0
  4. package/dist/enforcement/coverage.d.ts +51 -0
  5. package/dist/enforcement/coverage.js +210 -0
  6. package/dist/enforcement/docs-check.d.ts +17 -0
  7. package/dist/enforcement/docs-check.js +159 -0
  8. package/dist/enforcement/normalize.d.ts +11 -0
  9. package/dist/enforcement/normalize.js +21 -0
  10. package/dist/enforcement/parse-docs.d.ts +58 -0
  11. package/dist/enforcement/parse-docs.js +94 -0
  12. package/dist/enforcement/types.d.ts +57 -0
  13. package/dist/enforcement/types.js +59 -0
  14. package/dist/eslint/pasika/index.d.ts +13 -1
  15. package/dist/eslint/pasika/index.js +24 -11
  16. package/dist/eslint/pasika/project/ccf.d.ts +48 -0
  17. package/dist/eslint/pasika/project/ccf.js +119 -0
  18. package/dist/eslint/pasika/project/index.d.ts +21 -0
  19. package/dist/eslint/pasika/project/index.js +139 -0
  20. package/dist/eslint/pasika/project/parse-module.d.ts +27 -0
  21. package/dist/eslint/pasika/project/parse-module.js +128 -0
  22. package/dist/eslint/pasika/rules/component-placement.d.ts +11 -0
  23. package/dist/eslint/pasika/rules/component-placement.js +75 -0
  24. package/dist/eslint/pasika/rules/enforce-barrel-exports.d.ts +9 -0
  25. package/dist/eslint/pasika/rules/enforce-barrel-exports.js +78 -0
  26. package/dist/eslint/pasika/rules/enforce-cn-merge.d.ts +9 -0
  27. package/dist/eslint/pasika/rules/enforce-cn-merge.js +84 -0
  28. package/dist/eslint/pasika/rules/enforce-cva-variant-props.d.ts +9 -0
  29. package/dist/eslint/pasika/rules/enforce-cva-variant-props.js +81 -0
  30. package/dist/eslint/pasika/rules/filename-case.js +10 -0
  31. package/dist/eslint/pasika/rules/import-boundaries.d.ts +2 -0
  32. package/dist/eslint/pasika/rules/{organization-imports.js → import-boundaries.js} +62 -19
  33. package/dist/eslint/pasika/rules/no-arbitrary-tailwind.d.ts +9 -0
  34. package/dist/eslint/pasika/rules/no-arbitrary-tailwind.js +97 -0
  35. package/dist/eslint/pasika/rules/no-mixed-concerns.d.ts +9 -0
  36. package/dist/eslint/pasika/rules/no-mixed-concerns.js +84 -0
  37. package/dist/eslint/pasika/rules/support-file-placement.d.ts +15 -0
  38. package/dist/eslint/pasika/rules/support-file-placement.js +70 -0
  39. package/enforcement/registry.json +1139 -0
  40. package/package.json +21 -15
  41. package/claude/hooks/.vulyk +0 -3
  42. package/claude/hooks/AGENTS.md +0 -3
  43. package/claude/hooks/CLAUDE.md +0 -1
  44. package/claude/hooks/claude-hooks.md +0 -30
  45. package/claude/hooks/notification.sh +0 -38
  46. package/claude/hooks/protect-files.sh +0 -21
  47. package/claude/hooks/status-line/index.js +0 -57
  48. package/claude/scripts/render-settings.ts +0 -223
  49. package/claude/settings.base.json +0 -38
  50. package/dist/claude/scripts/render-settings.js +0 -145
  51. package/dist/eslint/pasika/rules/organization-imports.d.ts +0 -2
  52. package/dist/eslint.config.js +0 -7
  53. package/dist/scripts/pasika.js +0 -59
  54. package/docs/agent-conventions.md +0 -27
  55. package/docs/claude/hooks.md +0 -30
  56. package/docs/code-organization-guide/code-organization-guide.md +0 -69
  57. package/docs/code-organization-guide/references/application-architecture-reference.md +0 -149
  58. package/docs/code-organization-guide/rules/component-placement-rule.md +0 -119
  59. package/docs/code-organization-guide/rules/configuration-rule.md +0 -42
  60. package/docs/code-organization-guide/rules/constants-rule.md +0 -74
  61. package/docs/code-organization-guide/rules/exports-and-imports-rule.md +0 -84
  62. package/docs/code-organization-guide/rules/folder-nesting-rule.md +0 -82
  63. package/docs/code-organization-guide/rules/hook-extraction-rule.md +0 -141
  64. package/docs/code-organization-guide/rules/interactive-component-rule.md +0 -97
  65. package/docs/code-organization-guide/rules/jsx-hygiene-rule.md +0 -67
  66. package/docs/code-organization-guide/rules/locales-rule.md +0 -53
  67. package/docs/code-organization-guide/rules/nameable-visual-concept-rule.md +0 -66
  68. package/docs/code-organization-guide/rules/no-mixed-concerns-rule.md +0 -63
  69. package/docs/code-organization-guide/rules/repeated-structure-rule.md +0 -93
  70. package/docs/code-organization-guide/rules/smart-vs-dumb-component-rule.md +0 -112
  71. package/docs/code-organization-guide/rules/sole-state-owner-rule.md +0 -101
  72. package/docs/code-organization-guide/rules/types-and-schemas-rule.md +0 -139
  73. package/docs/code-organization-guide/rules/utilities-rule.md +0 -86
  74. package/docs/documentation-guide/_templates/grouped-reference.md +0 -11
  75. package/docs/documentation-guide/_templates/guide.md +0 -19
  76. package/docs/documentation-guide/_templates/rule.md +0 -21
  77. package/docs/documentation-guide/_templates/single-lookup-reference.md +0 -5
  78. package/docs/documentation-guide/documentation-guide.md +0 -13
  79. package/docs/documentation-guide/references/documentation-types-reference.md +0 -9
  80. package/docs/documentation-guide/rules/guide-creation-rule.md +0 -113
  81. package/docs/documentation-guide/rules/reference-creation-rule.md +0 -132
  82. package/docs/documentation-guide/rules/rule-creation-rule.md +0 -81
  83. package/docs/documentation-guide/rules/template-usage-rule.md +0 -49
  84. package/docs/shadcn-theme.md +0 -121
  85. package/docs/styling-guide/rules/arbitrary-value-rule.md +0 -31
  86. package/docs/styling-guide/rules/class-composition-rule.md +0 -52
  87. package/docs/styling-guide/rules/component-ui-state-rule.md +0 -53
  88. package/docs/styling-guide/rules/component-variant-rule.md +0 -125
  89. package/docs/styling-guide/rules/global-stylesheet-rule.md +0 -67
  90. package/docs/styling-guide/rules/theme-and-utility-definition-rule.md +0 -86
  91. package/docs/styling-guide/styling-guide.md +0 -14
@@ -1,132 +0,0 @@
1
- # Reference Creation Rule
2
-
3
- References without a consistent creation process scatter lookup material. This rule defines how to create a Reference document.
4
-
5
- - Reference file names MUST match the document title in kebab-case and use the `-reference` suffix (e.g., `foo-reference.md`).
6
- - A Reference MUST NOT link to Rules, Guides, or other References.
7
- - A Reference MUST NOT use RFC 2119 vocabulary (`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, `MAY`), because lookup material describes what exists rather than imposing requirements.
8
- - A Reference overview and the overview of each headed lookup block MUST contain one or two short sentences.
9
- - A Reference with a single lookup block MUST NOT add a section heading for it.
10
- - A Reference with two or more lookup blocks MUST add a section heading for every block, including the first.
11
-
12
- ## Incorrect — Reference Links to Another Document
13
-
14
- ```markdown
15
- - See the [Naming Rule](../rules/naming-rule.md) for the file naming conventions.
16
- ```
17
-
18
- Why: reference body contains a reference to another Rule.
19
-
20
- ## Correct — Reference States the Lookup Fact Directly
21
-
22
- ```markdown
23
- - File names are kebab-case, match the document title, and have a `-reference` suffix.
24
- ```
25
-
26
- Why: reference states lookup content directly without linking to its source.
27
-
28
- ## Incorrect — Reference Uses Requirement Vocabulary
29
-
30
- ```markdown
31
- | Layer | Contents |
32
- | ----- | ------------------------------------------------- |
33
- | `app` | Routing files only — a route MUST NOT hold a util |
34
- ```
35
-
36
- Why: the cell carries RFC 2119 vocabulary, so a Reference asserts a requirement that only a Rule can own, and the same constraint ends up stated in two documents.
37
-
38
- ## Correct — Reference Describes Existing Content
39
-
40
- ```markdown
41
- | Layer | Contents |
42
- | ----- | ------------------ |
43
- | `app` | Routing files only |
44
- ```
45
-
46
- Why: the cell describes what the layer holds, leaving the requirement to the Rule that owns it.
47
-
48
- ## Incorrect — Single Lookup Block Has a Heading
49
-
50
- ```markdown
51
- # Option Reference
52
-
53
- Use this reference to choose between the two supported project sizes when starting a new initiative.
54
-
55
- ## Options
56
-
57
- Pick the size that matches the team's headcount and timeline.
58
-
59
- | Option | Use Case |
60
- | ------ | -------------- |
61
- | A | Small projects |
62
- | B | Large projects |
63
- ```
64
-
65
- Why: section heading is unnecessary and forces a duplicated intro covering what the reference is for and what the group contains.
66
-
67
- ## Correct — Single Lookup Block Has No Heading
68
-
69
- ```markdown
70
- # Option Reference
71
-
72
- Use this reference to choose between the two supported project sizes when starting a new initiative.
73
-
74
- | Option | Use Case |
75
- | ------ | -------------- |
76
- | A | Small projects |
77
- | B | Large projects |
78
- ```
79
-
80
- Why: single lookup block sits directly under the intro without a heading.
81
-
82
- ## Incorrect — First of Multiple Lookup Blocks Has No Heading
83
-
84
- ```markdown
85
- # Option Reference
86
-
87
- Use this reference to look up the supported project sizes and the review gates each one requires.
88
-
89
- | Option | Use Case |
90
- | ------ | -------------- |
91
- | A | Small projects |
92
- | B | Large projects |
93
-
94
- ## Review Gates
95
-
96
- Each size carries a different approval bar.
97
-
98
- | Option | Approvals |
99
- | ------ | --------- |
100
- | A | 1 |
101
- | B | 2 |
102
- ```
103
-
104
- Why: the reference has two lookup blocks but only the second is headed, so the first table reads as part of the intro and cannot be scanned or linked as a group of its own.
105
-
106
- ## Correct — Every Lookup Block Has a Heading
107
-
108
- ```markdown
109
- # Option Reference
110
-
111
- Use this reference to look up the supported project sizes and the review gates each one requires.
112
-
113
- ## Project Sizes
114
-
115
- Pick the size that matches the team's headcount and timeline.
116
-
117
- | Option | Use Case |
118
- | ------ | -------------- |
119
- | A | Small projects |
120
- | B | Large projects |
121
-
122
- ## Review Gates
123
-
124
- Each size carries a different approval bar.
125
-
126
- | Option | Approvals |
127
- | ------ | --------- |
128
- | A | 1 |
129
- | B | 2 |
130
- ```
131
-
132
- Why: both lookup blocks are headed, so they read as peers and each is independently scannable and linkable.
@@ -1,81 +0,0 @@
1
- # Rule Creation Rule
2
-
3
- Rules without a consistent creation process invent their own structure and enforcement language. This rule defines how to create a Rule document.
4
-
5
- - Rule file names MUST match the document title in kebab-case and use the `-rule` suffix (e.g., `foo-rule.md`).
6
- - A Rule MUST contain at least one bullet point that uses [RFC 2119 vocabulary](https://datatracker.ietf.org/doc/html/rfc2119) (`MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, `MAY`).
7
- - RFC 2119 vocabulary MUST appear only in bullet points, so the bullet list is the single place a requirement is stated.
8
- - Prose outside bullet points — intros, `Why:` explanations, table cells, and code comments — MUST restate a requirement in plain language instead of repeating RFC 2119 vocabulary.
9
- - `MUST` MUST mean required.
10
- - `MUST NOT` MUST mean forbidden.
11
- - `SHOULD` MUST mean recommended, with exceptions possible.
12
- - `SHOULD NOT` MUST mean discouraged, with exceptions possible.
13
- - `MAY` MUST mean optional.
14
- - A Rule MUST contain at least one Incorrect/Correct pair.
15
- - A Rule MAY contain multiple Incorrect/Correct pairs.
16
- - An Incorrect/Correct pair MUST add a concise description after an em dash in both headings, so readers can scan the examples by decision.
17
- - A Rule MUST NOT link to References, Guides, or other Rules.
18
-
19
- ## Incorrect — Lowercase Requirement Words
20
-
21
- ```markdown
22
- - Authors must remove unused sections.
23
- - New docs should use the -rule suffix.
24
- ```
25
-
26
- Why: lower-case "must" and "should" do not carry RFC 2119 meaning.
27
-
28
- ## Correct — RFC 2119 Requirement Words
29
-
30
- ```markdown
31
- - Authors MUST remove unused sections.
32
- - New docs SHOULD use the -rule suffix.
33
- ```
34
-
35
- Why: uppercase keywords carry their canonical RFC 2119 meaning.
36
-
37
- ## Incorrect — Rule Links to a Reference
38
-
39
- ```markdown
40
- - Rule bodies MUST follow the guidance in the [Vocabulary Reference](../references/vocabulary-reference.md).
41
- ```
42
-
43
- Why: rule contains a reference to another Reference.
44
-
45
- ## Correct — Rule States Its Vocabulary Directly
46
-
47
- ```markdown
48
- - Rule bodies MUST use RFC 2119 vocabulary: MUST, MUST NOT, SHOULD, SHOULD NOT, or MAY.
49
- ```
50
-
51
- Why: rule states requirements directly without referencing other docs.
52
-
53
- ## Incorrect — Requirement Repeated in an Explanation
54
-
55
- ```markdown
56
- - A helper file MUST use named exports.
57
-
58
- ## Incorrect — Default Export
59
-
60
- // helper.ts
61
- export default function sum(a, b) {}
62
-
63
- Why: a helper file MUST use named exports, so `export default` is not allowed here.
64
- ```
65
-
66
- Why: the explanation repeats the bullet's requirement word, so the requirement now lives in two places and a later edit can leave the bullet and the explanation disagreeing about what is required.
67
-
68
- ## Correct — Requirement Explained in Plain Language
69
-
70
- ```markdown
71
- - A helper file MUST use named exports.
72
-
73
- ## Incorrect — Default Export
74
-
75
- // helper.ts
76
- export default function sum(a, b) {}
77
-
78
- Why: helper files use named exports only, so `export default` does not belong here.
79
- ```
80
-
81
- Why: the bullet is the only place the requirement is stated, and the explanation describes the example in plain language.
@@ -1,49 +0,0 @@
1
- # Template Usage Rule
2
-
3
- Authoring without a template makes every doc invent its own title, orientation, and section shape. This rule keeps new docs predictable. Templates for each document kind live in `../_templates/`.
4
-
5
- - Authors MUST start from the template that matches the chosen document kind and structure.
6
- - Authors MUST replace each bracketed prompt with the final title, explanation, step, or lookup content it asks for.
7
- - Authors MUST delete template branches that do not apply to the document being written.
8
- - Documentation support assets MAY live in folders whose names start with an underscore.
9
-
10
- ## Incorrect — Template Prompts and Unused Branches Kept
11
-
12
- ```markdown
13
- # Example Guide
14
-
15
- [1-2 short sentences explaining the underlying idea behind this guide and why it matters.]
16
-
17
- ## How To Do Something
18
-
19
- [What does this accomplish? When should it run?]
20
-
21
- 1. [Step 1]
22
- 2. [Step 2]
23
-
24
- ## How To Another Workflow
25
-
26
- [What does this accomplish? When should it run?]
27
-
28
- 1. [Step 1]
29
- 2. [Step 2]
30
- ```
31
-
32
- Why: committed doc still contains bracketed prompts, and the unused "Another Workflow" section was not deleted.
33
-
34
- ## Correct — Template Prompts Replaced and Unused Branches Removed
35
-
36
- ```markdown
37
- # Example Guide
38
-
39
- This guide shows how to do something useful. It matters because doing it wrong breaks the build.
40
-
41
- ## How To Do Something
42
-
43
- Run this before every release.
44
-
45
- 1. Check the config.
46
- 2. Run the deploy script.
47
- ```
48
-
49
- Why: all bracketed prompts are replaced, unused template branches are deleted, and no leftover brackets remain.
@@ -1,121 +0,0 @@
1
- # shadcn Theme
2
-
3
- Use this profile when a project uses shadcn UI tokens as its theme baseline.
4
-
5
- ## File structure
6
-
7
- Read the theme file by section:
8
-
9
- 1. `@theme inline` exposes CSS variables to Tailwind utilities.
10
- 2. `:root` defines light-mode token values.
11
- 3. `.dark` defines dark-mode token values.
12
- 4. `@layer base` applies default page, border, and outline styling.
13
-
14
- Component authors write utilities such as `bg-primary`, `text-primary-foreground`, and `rounded-lg`. Tailwind resolves those utilities through the tokens registered in `@theme inline`. Do not write generated CSS by hand.
15
-
16
- ## Registered Tailwind tokens
17
-
18
- `@theme inline` maps project CSS variables to Tailwind token names. The categories below mirror the groups in a standard shadcn `globals.css`.
19
-
20
- | Category | CSS variables | Utility examples |
21
- |---|---|---|
22
- | Page defaults | `--color-background`, `--color-foreground` | `bg-background`, `text-foreground` |
23
- | Content surfaces | `--color-card`, `--color-card-foreground`, `--color-popover`, `--color-popover-foreground` | `bg-card text-card-foreground`, `bg-popover text-popover-foreground` |
24
- | Action and state surfaces | `--color-primary`, `--color-primary-foreground`, `--color-secondary`, `--color-secondary-foreground`, `--color-muted`, `--color-muted-foreground`, `--color-accent`, `--color-accent-foreground`, `--color-destructive` | `bg-primary text-primary-foreground`, `bg-muted text-muted-foreground`, `bg-destructive text-white` |
25
- | Borders, form fields, focus | `--color-border`, `--color-input`, `--color-ring` | `border`, `border-input`, `ring-ring`, `outline-ring/50` |
26
- | Charts | `--color-chart-1` through `--color-chart-5` | `bg-chart-1`, `text-chart-2` |
27
- | Sidebar | `--color-sidebar`, `--color-sidebar-foreground`, `--color-sidebar-primary`, `--color-sidebar-primary-foreground`, `--color-sidebar-accent`, `--color-sidebar-accent-foreground`, `--color-sidebar-border`, `--color-sidebar-ring` | `bg-sidebar`, `text-sidebar-foreground`, `border-sidebar-border` |
28
- | Radius | `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`, `--radius-2xl`, `--radius-3xl`, `--radius-4xl` | `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`, `rounded-3xl`, `rounded-4xl` |
29
-
30
- ## Value tokens
31
-
32
- `:root` and `.dark` define the actual values for the same semantic tokens. The token names stay stable; only the values change between theme modes.
33
-
34
- ### Page defaults
35
-
36
- | Token | Use |
37
- |---|---|
38
- | `background` | App/page background |
39
- | `foreground` | Default app text |
40
-
41
- Use together as `bg-background text-foreground`.
42
-
43
- ### Content surfaces
44
-
45
- | Token pair | Use |
46
- |---|---|
47
- | `card` / `card-foreground` | Card or elevated surface content |
48
- | `popover` / `popover-foreground` | Floating surfaces like menus, tooltips, dialogs |
49
-
50
- Use as `bg-card text-card-foreground` and `bg-popover text-popover-foreground`.
51
-
52
- ### Action and state surfaces
53
-
54
- | Token pair | Use |
55
- |---|---|
56
- | `primary` / `primary-foreground` | Main action or selected/high-emphasis filled state |
57
- | `secondary` / `secondary-foreground` | Supporting action or lower-emphasis filled state |
58
- | `muted` / `muted-foreground` | Subtle surface and de-emphasized content |
59
- | `accent` / `accent-foreground` | Transient hover, active, or current-row surface |
60
- | `destructive` | Destructive action or error surface |
61
-
62
- Fresh shadcn components use fixed white foreground on destructive filled surfaces:
63
-
64
- ```tsx
65
- <Button className="bg-destructive text-white" />
66
- ```
67
-
68
- ### Borders, form fields, and focus
69
-
70
- | Token | shadcn use | Utility examples |
71
- |---|---|---|
72
- | `border` | Default borders and separators | `border`, `border-b` |
73
- | `input` | Form-control border color and input-like surface treatment | `border-input`, `dark:bg-input/30`, `dark:border-input`, `dark:hover:bg-input/50` |
74
- | `ring` | Focus border, ring, and outline color | `focus-visible:border-ring`, `focus-visible:ring-ring/50`, `focus-visible:outline-ring`, `outline-ring/50` |
75
-
76
- The base layer already applies `border-border` and `outline-ring/50`; component code usually needs only the structural border utility (`border`, `border-b`, etc.) unless it is using another border role or overriding a competing border-color class.
77
-
78
- ### Charts
79
-
80
- `chart-1` through `chart-5` are palette slots for charts and data visualizations. They are not app surface tokens and do not imply foreground pairs.
81
-
82
- ### Sidebar
83
-
84
- Sidebar tokens are scoped versions of the same shadcn roles for sidebar UI:
85
-
86
- - `sidebar` / `sidebar-foreground`
87
- - `sidebar-primary` / `sidebar-primary-foreground`
88
- - `sidebar-accent` / `sidebar-accent-foreground`
89
- - `sidebar-border`
90
- - `sidebar-ring`
91
-
92
- ### Radius
93
-
94
- `radius` is the base radius value. `@theme inline` derives Tailwind radius utilities from it.
95
-
96
- ## Base layer
97
-
98
- shadcn applies default app chrome in `@layer base`:
99
-
100
- ```css
101
- @layer base {
102
- * {
103
- @apply border-border outline-ring/50;
104
- }
105
- body {
106
- @apply bg-background text-foreground;
107
- }
108
- }
109
- ```
110
-
111
- The base layer applies `border-border` and `outline-ring/50` globally. Plain `border`, `border-b`, etc. inherit the default border color when no competing border-color class is present. Add an explicit border color for another role, such as `border-input`, `border-primary`, `border-destructive`, or `focus-visible:border-ring`, or when overriding a component variant such as `border-transparent`.
112
-
113
- ## Adding tokens
114
-
115
- Treat fresh shadcn tokens as the baseline. Add project-specific tokens only when the project explicitly adopts an extension beyond shadcn defaults, and document that extension outside this baseline profile.
116
-
117
- ## Update trigger
118
-
119
- Re-check this document when the [shadcn changelog](https://ui.shadcn.com/docs/changelog) changes the theme token contract, default theme CSS, or generated component styling conventions.
120
-
121
- When re-checking, verify the affected claims against current shadcn docs and generated registry components before editing this document.
@@ -1,31 +0,0 @@
1
- # Arbitrary Value Rule
2
-
3
- Literal arbitrary-value classes hide values used for the project's styling in component code. This rule requires every such value to have a named utility or token.
4
-
5
- - Components MUST NOT use arbitrary-value classes for project styling. They MUST use an existing Tailwind or project utility, or define a project token or custom utility first.
6
-
7
- ## Incorrect — Literal Arbitrary Radius
8
-
9
- ```tsx
10
- <button className="rounded-[13px]">Save</button>
11
- ```
12
-
13
- Why: the design value is hidden in markup and cannot be tracked as part of the project's explicit token set.
14
-
15
- ## Correct — Named Radius Token
16
-
17
- ```css
18
- :root {
19
- --radius-md: 0.375rem;
20
- }
21
-
22
- @theme inline {
23
- --radius-md: var(--radius-md);
24
- }
25
- ```
26
-
27
- ```tsx
28
- <button className="rounded-md">Save</button>
29
- ```
30
-
31
- Why: the named value makes the radius searchable and exposes the matching Tailwind radius utility.
@@ -1,52 +0,0 @@
1
- # Class Composition Rule
2
-
3
- `cn` is the project's helper for combining conditional classes and resolving conflicting Tailwind utilities. It gives conditional classes and a passed `className` one predictable merge point.
4
-
5
- - Components MUST use `cn` or the project's equivalent class-merging helper for conditional classes and a `className` passed to the component.
6
- - Components MUST NOT concatenate class strings with template literals or `+` when any part is conditional.
7
- - A `className` passed to a component MUST contain only outer-layout utilities: margins, sizing, flex or grid item placement, and `z-index`.
8
- - A component MUST expose its supported appearance and size variants through typed props, not through a passed `className` or separate class-name props for internal elements.
9
- - A static class list with more than five class names MUST use `cn` with multiple string literals, each grouped by styling concern and containing no more than five class names.
10
-
11
- ## Incorrect — Conditional Classes Concatenated Manually
12
-
13
- ```tsx
14
- <button className={"rounded px-3 " + (active ? "bg-primary" : "bg-muted")} />
15
- ```
16
-
17
- Why: the condition and merge order are embedded in a string expression, which becomes difficult to extend with consumer overrides.
18
-
19
- ## Correct — Conditional Classes Merged with `cn`
20
-
21
- ```tsx
22
- export function Card({ className, ...props }: CardProps): React.JSX.Element {
23
- return <article className={cn("card-surface rounded-lg p-4", className)} {...props} />;
24
- }
25
-
26
- <Card className="w-full max-w-lg self-center" />;
27
- ```
28
-
29
- Why: every class source is explicit, and a consumer can add outer layout such as `w-full max-w-lg self-center` without reconstructing the card's internal treatment.
30
-
31
- ## Incorrect — Long Static Class Literal
32
-
33
- ```tsx
34
- <article className="rounded-lg border border-border bg-card px-6 py-4 shadow-sm transition-shadow hover:shadow-md" />
35
- ```
36
-
37
- Why: one static literal contains more than five class names, so its styling concerns are difficult to scan.
38
-
39
- ## Correct — Static Classes Grouped in `cn`
40
-
41
- ```tsx
42
- <article
43
- className={cn(
44
- "rounded-lg border border-border",
45
- "bg-card shadow-sm",
46
- "px-6 py-4",
47
- "transition-shadow hover:shadow-md",
48
- )}
49
- />
50
- ```
51
-
52
- Why: each literal contains at most five class names and groups one styling concern.
@@ -1,53 +0,0 @@
1
- # Component UI State Rule
2
-
3
- Visual state communicates whether a component can be used and what will happen when it is used. This rule keeps that feedback owned by the component rather than reconstructed by every caller.
4
-
5
- - A component MUST own a distinguishable appearance for every UI state it supports.
6
- - A component MUST use Tailwind "state variants" when they can express a supported UI state.
7
- - Components MUST use native semantic elements and attributes and MUST NOT use custom equivalents.
8
-
9
- ## Incorrect — State Styled Only with Classes
10
-
11
- ```tsx
12
- <Button className={isSaving ? "pointer-events-none opacity-50" : ""}>Save</Button>
13
- ```
14
-
15
- Why: every caller has to reconstruct the saving or disabled treatment, and the button does not receive a semantic disabled state.
16
-
17
- ## Correct — State Exposed Through Component Props
18
-
19
- ```tsx
20
- import { cn } from "@/utils/cn";
21
-
22
- type ButtonProps = {
23
- children: React.ReactNode;
24
- disabled?: boolean;
25
- loading?: boolean;
26
- };
27
-
28
- export function Button({ children, disabled = false, loading = false }: ButtonProps): React.JSX.Element {
29
- const isUnavailable = disabled || loading;
30
-
31
- return (
32
- <button
33
- aria-busy={loading}
34
- className={cn(
35
- "rounded px-3",
36
- "hover:opacity-90 focus-visible:outline-2",
37
- "disabled:cursor-not-allowed disabled:opacity-50",
38
- "aria-busy:animate-pulse",
39
- )}
40
- disabled={isUnavailable}
41
- type="button"
42
- >
43
- {loading ? "Saving…" : children}
44
- </button>
45
- );
46
- }
47
-
48
- <Button loading={isSaving}>
49
- Save
50
- </Button>
51
- ```
52
-
53
- Why: `Button` uses native disabled and ARIA busy attributes with Tailwind state variants, while callers express only loading through its typed API.
@@ -1,125 +0,0 @@
1
- # Component Variant Rule
2
-
3
- `cva` is Class Variance Authority, a helper for defining a component's visual options and deriving their TypeScript API. `cn` handles a standalone boolean choice without creating a variant matrix.
4
-
5
- - A component that lets callers choose visual options MUST define them with `cva`.
6
- - A component's variant prop types MUST be derived from its `cva` definition with `VariantProps` rather than manually duplicated unions.
7
- - A boolean prop that changes appearance MUST use conditional `cn()` when it only adds or removes one standalone class treatment.
8
- - A boolean prop that changes appearance MUST use a CVA variant when both boolean values have explicit treatments or when the boolean participates in a compound variant.
9
-
10
- ## Incorrect — Variant Union Duplicated by Hand
11
-
12
- ```tsx
13
- const buttonVariants = cva("inline-flex items-center", {
14
- variants: {
15
- size: {
16
- sm: "h-8 px-3",
17
- lg: "h-11 px-5",
18
- },
19
- },
20
- });
21
-
22
- type ButtonProps = React.ComponentProps<"button"> & {
23
- size?: "sm" | "lg";
24
- };
25
- ```
26
-
27
- Why: the accepted `size` values now live in both CVA and a handwritten prop union, so either side can change without the other.
28
-
29
- ## Correct — Props Derived from CVA
30
-
31
- ```tsx
32
- const buttonVariants = cva("inline-flex items-center", {
33
- variants: {
34
- size: {
35
- sm: "h-8 px-3",
36
- lg: "h-11 px-5",
37
- },
38
- tone: {
39
- primary: "primary-surface",
40
- danger: "danger-surface",
41
- },
42
- },
43
- defaultVariants: {
44
- size: "sm",
45
- tone: "primary",
46
- },
47
- });
48
-
49
- type ButtonProps = React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>;
50
-
51
- export function Button({ size, tone, className, ...props }: ButtonProps): React.JSX.Element {
52
- return <button className={cn(buttonVariants({ size, tone }), className)} {...props} />;
53
- }
54
- ```
55
-
56
- Why: the CVA definition owns the accepted values, defaults, and classes, while `VariantProps` derives the public TypeScript API from the same source.
57
-
58
- ## Incorrect — Standalone Boolean Variant Added to CVA
59
-
60
- ```tsx
61
- const buttonVariants = cva("inline-flex", {
62
- variants: {
63
- elevated: {
64
- true: "shadow-md",
65
- false: "",
66
- },
67
- },
68
- });
69
- ```
70
-
71
- Why: one boolean variant merely adds a standalone treatment and has no compound relationship, so CVA adds configuration without a styling matrix.
72
-
73
- ## Correct — Standalone Boolean Variant Uses `cn`
74
-
75
- ```tsx
76
- <button className={cn(buttonVariants({ size, tone }), elevated && "shadow-md", className)} />
77
- ```
78
-
79
- Why: the named appearance choices stay in CVA while the independent boolean variant remains a direct conditional class.
80
-
81
- ## Incorrect — Compound Boolean Variant Styling Split Across Conditions
82
-
83
- ```tsx
84
- <button
85
- className={cn(
86
- buttonVariants({ size }),
87
- square && "justify-center",
88
- square && size === "sm" && "w-8 px-0",
89
- square && size === "lg" && "w-11 px-0",
90
- )}
91
- />
92
- ```
93
-
94
- Why: the boolean variant changes treatment according to another variant, but the styling matrix is spread across conditional expressions outside CVA.
95
-
96
- ## Correct — Boolean Variant with Compound Styling Uses CVA
97
-
98
- ```tsx
99
- const buttonVariants = cva("inline-flex", {
100
- variants: {
101
- size: {
102
- sm: "h-8 px-3",
103
- lg: "h-11 px-5",
104
- },
105
- square: {
106
- true: "justify-center",
107
- false: "justify-start",
108
- },
109
- },
110
- compoundVariants: [
111
- {
112
- size: "sm",
113
- square: true,
114
- className: "w-8 px-0",
115
- },
116
- {
117
- size: "lg",
118
- square: true,
119
- className: "w-11 px-0",
120
- },
121
- ],
122
- });
123
- ```
124
-
125
- Why: both boolean values have explicit treatment and the square appearance changes by size, so CVA owns the complete styling matrix.