@gallop.software/canon 1.0.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.
- package/README.md +83 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +116 -0
- package/guarantees.md +136 -0
- package/package.json +41 -0
- package/patterns/001-server-first-blocks.md +84 -0
- package/patterns/002-layout-hierarchy.md +84 -0
- package/patterns/003-typography-components.md +78 -0
- package/patterns/004-component-props.md +102 -0
- package/patterns/005-page-structure.md +110 -0
- package/patterns/006-block-naming.md +87 -0
- package/patterns/007-import-paths.md +113 -0
- package/patterns/008-tailwind-only.md +97 -0
- package/patterns/009-color-tokens.md +114 -0
- package/patterns/010-spacing-system.md +103 -0
- package/patterns/011-responsive-mobile-first.md +120 -0
- package/patterns/012-icon-system.md +120 -0
- package/patterns/013-new-component-pattern.md +135 -0
- package/patterns/014-clsx-not-classnames.md +128 -0
- package/patterns/015-no-inline-hover-styles.md +132 -0
- package/patterns/016-client-extraction.md +149 -0
- package/patterns/017-seo-metadata.md +181 -0
- package/schema.json +245 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Pattern 015: No Inline Hover Styles
|
|
2
|
+
|
|
3
|
+
**Canon Version:** 1.0
|
|
4
|
+
**Status:** Stable
|
|
5
|
+
**Category:** Styling
|
|
6
|
+
**Enforcement:** Documentation
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
Use Tailwind hover/focus classes. Do not use inline styles for interactive states.
|
|
11
|
+
|
|
12
|
+
## Rationale
|
|
13
|
+
|
|
14
|
+
1. **CSS handles it** — Pseudo-classes work in stylesheets, not inline
|
|
15
|
+
2. **No JavaScript** — Hover effects without React state
|
|
16
|
+
3. **Performance** — No re-renders on hover
|
|
17
|
+
4. **Consistency** — All hover states follow Tailwind patterns
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### Good: Tailwind Pseudo-Classes
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<button className="
|
|
25
|
+
bg-accent text-white
|
|
26
|
+
hover:bg-accent2
|
|
27
|
+
focus:ring-2 focus:ring-accent
|
|
28
|
+
active:bg-accent3
|
|
29
|
+
transition-colors
|
|
30
|
+
">
|
|
31
|
+
Click Me
|
|
32
|
+
</button>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Bad: State-Based Styling
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
function Button() {
|
|
39
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
style={{ backgroundColor: isHovered ? '#6b4a3a' : '#8b5a4a' }}
|
|
44
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
45
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
46
|
+
>
|
|
47
|
+
Click Me
|
|
48
|
+
</button>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Common Hover Patterns
|
|
54
|
+
|
|
55
|
+
### Color Changes
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// Background color
|
|
59
|
+
<div className="bg-gray-100 hover:bg-gray-200">
|
|
60
|
+
|
|
61
|
+
// Text color
|
|
62
|
+
<a className="text-body hover:text-accent">
|
|
63
|
+
|
|
64
|
+
// Border color
|
|
65
|
+
<div className="border-gray-300 hover:border-accent">
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Transforms
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// Scale up on hover
|
|
72
|
+
<div className="hover:scale-105 transition-transform">
|
|
73
|
+
|
|
74
|
+
// Lift effect
|
|
75
|
+
<div className="hover:-translate-y-1 hover:shadow-lg transition-all">
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Opacity
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// Fade in overlay
|
|
82
|
+
<div className="opacity-0 hover:opacity-100 transition-opacity">
|
|
83
|
+
|
|
84
|
+
// Dim on hover
|
|
85
|
+
<button className="hover:opacity-80 transition-opacity">
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Group Hover
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// Parent hover affects child
|
|
92
|
+
<div className="group">
|
|
93
|
+
<img className="group-hover:scale-110 transition-transform" />
|
|
94
|
+
<span className="group-hover:text-accent">Title</span>
|
|
95
|
+
</div>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Focus States
|
|
99
|
+
|
|
100
|
+
Always include focus states for accessibility:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
<button className="
|
|
104
|
+
hover:bg-accent2
|
|
105
|
+
focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2
|
|
106
|
+
">
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Transitions
|
|
110
|
+
|
|
111
|
+
Add transitions for smooth effects:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// Single property
|
|
115
|
+
<div className="transition-colors duration-200">
|
|
116
|
+
|
|
117
|
+
// Multiple properties
|
|
118
|
+
<div className="transition-all duration-300">
|
|
119
|
+
|
|
120
|
+
// Specific properties
|
|
121
|
+
<div className="transition-[transform,opacity] duration-200">
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Enforcement
|
|
125
|
+
|
|
126
|
+
- **Method:** Code review / Documentation
|
|
127
|
+
- **Check:** No `onMouseEnter`/`onMouseLeave` for styling
|
|
128
|
+
|
|
129
|
+
## References
|
|
130
|
+
|
|
131
|
+
- Tailwind hover/focus docs
|
|
132
|
+
- `src/components/button.tsx` — Button with hover states
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Pattern 016: Client Extraction
|
|
2
|
+
|
|
3
|
+
**Canon Version:** 1.0
|
|
4
|
+
**Status:** Stable
|
|
5
|
+
**Category:** Rendering
|
|
6
|
+
**Enforcement:** ESLint (via 001)
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
Extract client-side logic (hooks, event handlers) into dedicated client components. Blocks and most components should remain server components.
|
|
11
|
+
|
|
12
|
+
## Rationale
|
|
13
|
+
|
|
14
|
+
1. **Minimal client JavaScript** — Only interactive parts need client runtime
|
|
15
|
+
2. **Better performance** — Server components don't add to bundle
|
|
16
|
+
3. **Clear boundaries** — Easy to identify what runs where
|
|
17
|
+
4. **Easier testing** — Server components are pure renders
|
|
18
|
+
|
|
19
|
+
## Extraction Patterns
|
|
20
|
+
|
|
21
|
+
### Pattern A: Init Components (Side Effects Only)
|
|
22
|
+
|
|
23
|
+
For effects that don't render UI, create "init" components that return null:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// src/hooks/use-circle-animation.tsx
|
|
27
|
+
'use client'
|
|
28
|
+
import { useEffect } from 'react'
|
|
29
|
+
import { useInView } from 'react-intersection-observer'
|
|
30
|
+
|
|
31
|
+
export default function CircleAnimationInit({ targetId }: { targetId: string }) {
|
|
32
|
+
const { ref, inView } = useInView({ threshold: 0.1 })
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (inView) {
|
|
36
|
+
// Animation logic
|
|
37
|
+
}
|
|
38
|
+
}, [inView, targetId])
|
|
39
|
+
|
|
40
|
+
return null // No UI, just side effects
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Usage in server block:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// src/blocks/hero-16.tsx (server component)
|
|
48
|
+
import CircleAnimationInit from '@/hooks/use-circle-animation'
|
|
49
|
+
|
|
50
|
+
export default function Hero16() {
|
|
51
|
+
return (
|
|
52
|
+
<Section>
|
|
53
|
+
<div id="circle-text">...</div>
|
|
54
|
+
<CircleAnimationInit targetId="circle-text" />
|
|
55
|
+
</Section>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Pattern B: Interactive Components
|
|
61
|
+
|
|
62
|
+
For UI that needs interactivity, create client components:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// src/components/video-popup.tsx
|
|
66
|
+
'use client'
|
|
67
|
+
import { useState } from 'react'
|
|
68
|
+
import { Dialog } from '@headlessui/react'
|
|
69
|
+
|
|
70
|
+
export function VideoPopup({ videoId }: { videoId: string }) {
|
|
71
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<button onClick={() => setIsOpen(true)}>Play</button>
|
|
76
|
+
<Dialog open={isOpen} onClose={() => setIsOpen(false)}>
|
|
77
|
+
{/* Video player */}
|
|
78
|
+
</Dialog>
|
|
79
|
+
</>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Pattern C: Event Delegation
|
|
85
|
+
|
|
86
|
+
For many clickable items, use event delegation:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// src/components/sidebar-click-handler.tsx
|
|
90
|
+
'use client'
|
|
91
|
+
|
|
92
|
+
export function SidebarClickHandler({ children }: { children: React.ReactNode }) {
|
|
93
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
94
|
+
const link = (e.target as HTMLElement).closest('a[data-sidebar]')
|
|
95
|
+
if (link) {
|
|
96
|
+
e.preventDefault()
|
|
97
|
+
// Handle sidebar navigation
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return <div onClick={handleClick}>{children}</div>
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## What Stays Server-Side
|
|
106
|
+
|
|
107
|
+
- Static content rendering
|
|
108
|
+
- Data fetching
|
|
109
|
+
- Metadata generation
|
|
110
|
+
- Layout structure
|
|
111
|
+
|
|
112
|
+
## What Moves to Client
|
|
113
|
+
|
|
114
|
+
- `useState`, `useEffect`, `useRef`
|
|
115
|
+
- Event handlers (`onClick`, `onChange`)
|
|
116
|
+
- Browser APIs (`window`, `document`)
|
|
117
|
+
- Animation libraries (Framer Motion)
|
|
118
|
+
- Third-party interactive widgets
|
|
119
|
+
|
|
120
|
+
## Static IDs
|
|
121
|
+
|
|
122
|
+
When server components need to reference DOM elements for client scripts:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
// Server component
|
|
126
|
+
const sliderId = 'hero-slider'
|
|
127
|
+
const circleId = 'hero-circle'
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<div id={sliderId}>...</div>
|
|
132
|
+
<div id={circleId}>...</div>
|
|
133
|
+
<SwiperSliderInit swiperId={sliderId} />
|
|
134
|
+
<CircleAnimationInit targetId={circleId} />
|
|
135
|
+
</>
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Enforcement
|
|
140
|
+
|
|
141
|
+
- **ESLint rule:** `gallop/no-client-blocks` (Pattern 001)
|
|
142
|
+
- **Severity:** Warning
|
|
143
|
+
|
|
144
|
+
## References
|
|
145
|
+
|
|
146
|
+
- `src/hooks/use-circle-animation.tsx` — Init component pattern
|
|
147
|
+
- `src/hooks/swiper-slider-init.tsx` — Init component pattern
|
|
148
|
+
- `src/components/video-popup.tsx` — Interactive component pattern
|
|
149
|
+
- `src/components/sidebar-stack/` — Event delegation pattern
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Pattern 017: SEO Metadata
|
|
2
|
+
|
|
3
|
+
**Canon Version:** 1.0
|
|
4
|
+
**Status:** Stable
|
|
5
|
+
**Category:** SEO
|
|
6
|
+
**Enforcement:** Documentation
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
Every page must have complete SEO metadata including Open Graph, Twitter cards, and structured data.
|
|
11
|
+
|
|
12
|
+
## Rationale
|
|
13
|
+
|
|
14
|
+
1. **Search visibility** — Proper metadata improves rankings
|
|
15
|
+
2. **Social sharing** — OG/Twitter cards control previews
|
|
16
|
+
3. **Rich results** — Structured data enables rich snippets
|
|
17
|
+
4. **Consistency** — All pages follow the same pattern
|
|
18
|
+
|
|
19
|
+
## Required Metadata
|
|
20
|
+
|
|
21
|
+
### Core Properties
|
|
22
|
+
|
|
23
|
+
| Property | Required | Description |
|
|
24
|
+
|----------|----------|-------------|
|
|
25
|
+
| `title` | Yes | Page title with site name (50-60 chars) |
|
|
26
|
+
| `description` | Yes | SEO description (150-160 chars) |
|
|
27
|
+
| `keywords` | Yes | Array of relevant keywords |
|
|
28
|
+
| `focusKeyword` | Yes | Primary keyword for the page |
|
|
29
|
+
| `featuredImage` | Yes | OG image path |
|
|
30
|
+
| `alternates.canonical` | Yes | Canonical URL |
|
|
31
|
+
|
|
32
|
+
### Open Graph
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
openGraph: {
|
|
36
|
+
type: 'website', // or 'article' for blog posts
|
|
37
|
+
locale: 'en_US',
|
|
38
|
+
url: 'https://example.com/page',
|
|
39
|
+
siteName: 'Site Name',
|
|
40
|
+
title: 'Page Title',
|
|
41
|
+
description: 'Page description',
|
|
42
|
+
image: {
|
|
43
|
+
url: '/images/og-image.jpg',
|
|
44
|
+
alt: 'Image description',
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Twitter Cards
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
twitter: {
|
|
53
|
+
card: 'summary_large_image',
|
|
54
|
+
site: '@sitehandle',
|
|
55
|
+
creator: '@authorhandle',
|
|
56
|
+
title: 'Page Title',
|
|
57
|
+
description: 'Page description',
|
|
58
|
+
image: '/images/twitter-image.jpg',
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Structured Data
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
structuredData: [
|
|
66
|
+
{
|
|
67
|
+
'@context': 'https://schema.org',
|
|
68
|
+
'@type': 'WebPage', // or Organization, Product, Article, etc.
|
|
69
|
+
name: 'Page Title',
|
|
70
|
+
description: 'Page description',
|
|
71
|
+
// Additional properties based on type
|
|
72
|
+
},
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Common Structured Data Types
|
|
77
|
+
|
|
78
|
+
### Organization (Homepage)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
{
|
|
82
|
+
'@context': 'https://schema.org',
|
|
83
|
+
'@type': 'Organization',
|
|
84
|
+
name: 'Company Name',
|
|
85
|
+
url: 'https://example.com',
|
|
86
|
+
logo: 'https://example.com/logo.png',
|
|
87
|
+
contactPoint: {
|
|
88
|
+
'@type': 'ContactPoint',
|
|
89
|
+
telephone: '+1-555-555-5555',
|
|
90
|
+
contactType: 'customer service',
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### LocalBusiness (Contact Page)
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
{
|
|
99
|
+
'@context': 'https://schema.org',
|
|
100
|
+
'@type': 'LocalBusiness',
|
|
101
|
+
name: 'Business Name',
|
|
102
|
+
address: {
|
|
103
|
+
'@type': 'PostalAddress',
|
|
104
|
+
streetAddress: '123 Main St',
|
|
105
|
+
addressLocality: 'City',
|
|
106
|
+
addressRegion: 'State',
|
|
107
|
+
postalCode: '12345',
|
|
108
|
+
},
|
|
109
|
+
telephone: '+1-555-555-5555',
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Article (Blog Post)
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
{
|
|
117
|
+
'@context': 'https://schema.org',
|
|
118
|
+
'@type': 'Article',
|
|
119
|
+
headline: 'Article Title',
|
|
120
|
+
author: {
|
|
121
|
+
'@type': 'Person',
|
|
122
|
+
name: 'Author Name',
|
|
123
|
+
},
|
|
124
|
+
datePublished: '2026-01-15',
|
|
125
|
+
dateModified: '2026-01-15',
|
|
126
|
+
image: 'https://example.com/article-image.jpg',
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Product (Product Page)
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
{
|
|
134
|
+
'@context': 'https://schema.org',
|
|
135
|
+
'@type': 'Product',
|
|
136
|
+
name: 'Product Name',
|
|
137
|
+
description: 'Product description',
|
|
138
|
+
brand: {
|
|
139
|
+
'@type': 'Brand',
|
|
140
|
+
name: 'Brand Name',
|
|
141
|
+
},
|
|
142
|
+
offers: {
|
|
143
|
+
'@type': 'Offer',
|
|
144
|
+
price: '99.00',
|
|
145
|
+
priceCurrency: 'USD',
|
|
146
|
+
availability: 'https://schema.org/InStock',
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Image Guidelines
|
|
152
|
+
|
|
153
|
+
### Featured/OG Images
|
|
154
|
+
|
|
155
|
+
- Minimum size: 1200x630px
|
|
156
|
+
- Recommended: 1200x630px (1.91:1 ratio)
|
|
157
|
+
- Format: JPG or PNG
|
|
158
|
+
- Max file size: 5MB
|
|
159
|
+
|
|
160
|
+
### Twitter Images
|
|
161
|
+
|
|
162
|
+
- Minimum: 300x157px
|
|
163
|
+
- Recommended: 1200x600px (2:1 ratio)
|
|
164
|
+
- Format: JPG, PNG, or GIF
|
|
165
|
+
|
|
166
|
+
## Validation Tools
|
|
167
|
+
|
|
168
|
+
- [Google Rich Results Test](https://search.google.com/test/rich-results)
|
|
169
|
+
- [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/)
|
|
170
|
+
- [Twitter Card Validator](https://cards-dev.twitter.com/validator)
|
|
171
|
+
|
|
172
|
+
## Enforcement
|
|
173
|
+
|
|
174
|
+
- **Method:** Code review / SEO audit
|
|
175
|
+
- **Future:** CI validation of metadata completeness
|
|
176
|
+
|
|
177
|
+
## References
|
|
178
|
+
|
|
179
|
+
- `src/utils/page-helpers.ts` — Metadata generation utilities
|
|
180
|
+
- `src/app/(hero)/page.tsx` — Homepage metadata example
|
|
181
|
+
- Schema.org documentation: https://schema.org
|
package/schema.json
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"name": "Gallop Canon",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Gallop Enterprise Architecture Canon - Versioned, AI-compatible, auditable web architecture patterns",
|
|
6
|
+
"categories": [
|
|
7
|
+
{
|
|
8
|
+
"id": "rendering",
|
|
9
|
+
"name": "Rendering",
|
|
10
|
+
"description": "Server/client component boundaries and rendering strategies"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "layout",
|
|
14
|
+
"name": "Layout",
|
|
15
|
+
"description": "Layout hierarchy, spacing, and responsive design"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "typography",
|
|
19
|
+
"name": "Typography",
|
|
20
|
+
"description": "Text component usage and styling"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "structure",
|
|
24
|
+
"name": "Structure",
|
|
25
|
+
"description": "File and folder organization patterns"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "styling",
|
|
29
|
+
"name": "Styling",
|
|
30
|
+
"description": "CSS, Tailwind, and visual styling patterns"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "components",
|
|
34
|
+
"name": "Components",
|
|
35
|
+
"description": "Component design and implementation patterns"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "seo",
|
|
39
|
+
"name": "SEO",
|
|
40
|
+
"description": "Search engine optimization and metadata patterns"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"patterns": [
|
|
44
|
+
{
|
|
45
|
+
"id": "001",
|
|
46
|
+
"title": "Server-First Blocks",
|
|
47
|
+
"file": "patterns/001-server-first-blocks.md",
|
|
48
|
+
"category": "rendering",
|
|
49
|
+
"status": "stable",
|
|
50
|
+
"enforcement": "eslint",
|
|
51
|
+
"rule": "gallop/no-client-blocks",
|
|
52
|
+
"summary": "Blocks must be server components"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "002",
|
|
56
|
+
"title": "Layout Hierarchy",
|
|
57
|
+
"file": "patterns/002-layout-hierarchy.md",
|
|
58
|
+
"category": "layout",
|
|
59
|
+
"status": "stable",
|
|
60
|
+
"enforcement": "eslint",
|
|
61
|
+
"rule": "gallop/no-container-in-section",
|
|
62
|
+
"summary": "No Container inside Section"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "003",
|
|
66
|
+
"title": "Typography Components",
|
|
67
|
+
"file": "patterns/003-typography-components.md",
|
|
68
|
+
"category": "typography",
|
|
69
|
+
"status": "stable",
|
|
70
|
+
"enforcement": "eslint",
|
|
71
|
+
"rule": "gallop/prefer-typography-components",
|
|
72
|
+
"summary": "Use Paragraph/Span, not raw tags"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "004",
|
|
76
|
+
"title": "Component Props",
|
|
77
|
+
"file": "patterns/004-component-props.md",
|
|
78
|
+
"category": "typography",
|
|
79
|
+
"status": "stable",
|
|
80
|
+
"enforcement": "eslint",
|
|
81
|
+
"rule": "gallop/prefer-component-props",
|
|
82
|
+
"summary": "Use props over className for supported styles"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "005",
|
|
86
|
+
"title": "Page Structure",
|
|
87
|
+
"file": "patterns/005-page-structure.md",
|
|
88
|
+
"category": "structure",
|
|
89
|
+
"status": "stable",
|
|
90
|
+
"enforcement": "documentation",
|
|
91
|
+
"rule": null,
|
|
92
|
+
"summary": "PageWrapper, generatePageMetadata pattern"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "006",
|
|
96
|
+
"title": "Block Naming",
|
|
97
|
+
"file": "patterns/006-block-naming.md",
|
|
98
|
+
"category": "structure",
|
|
99
|
+
"status": "stable",
|
|
100
|
+
"enforcement": "documentation",
|
|
101
|
+
"rule": null,
|
|
102
|
+
"summary": "{type}-{n}.tsx naming, PascalCase exports"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "007",
|
|
106
|
+
"title": "Import Paths",
|
|
107
|
+
"file": "patterns/007-import-paths.md",
|
|
108
|
+
"category": "structure",
|
|
109
|
+
"status": "stable",
|
|
110
|
+
"enforcement": "documentation",
|
|
111
|
+
"rule": null,
|
|
112
|
+
"summary": "@/ aliases, destructured imports"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "008",
|
|
116
|
+
"title": "Tailwind Only",
|
|
117
|
+
"file": "patterns/008-tailwind-only.md",
|
|
118
|
+
"category": "styling",
|
|
119
|
+
"status": "stable",
|
|
120
|
+
"enforcement": "documentation",
|
|
121
|
+
"rule": null,
|
|
122
|
+
"summary": "No inline styles, use Tailwind exclusively"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "009",
|
|
126
|
+
"title": "Color Tokens",
|
|
127
|
+
"file": "patterns/009-color-tokens.md",
|
|
128
|
+
"category": "styling",
|
|
129
|
+
"status": "stable",
|
|
130
|
+
"enforcement": "documentation",
|
|
131
|
+
"rule": null,
|
|
132
|
+
"summary": "Use semantic color tokens"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"id": "010",
|
|
136
|
+
"title": "Spacing System",
|
|
137
|
+
"file": "patterns/010-spacing-system.md",
|
|
138
|
+
"category": "layout",
|
|
139
|
+
"status": "stable",
|
|
140
|
+
"enforcement": "documentation",
|
|
141
|
+
"rule": null,
|
|
142
|
+
"summary": "Standard padding/margin values"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"id": "011",
|
|
146
|
+
"title": "Responsive Mobile-First",
|
|
147
|
+
"file": "patterns/011-responsive-mobile-first.md",
|
|
148
|
+
"category": "layout",
|
|
149
|
+
"status": "stable",
|
|
150
|
+
"enforcement": "documentation",
|
|
151
|
+
"rule": null,
|
|
152
|
+
"summary": "sm/md/lg/xl breakpoint usage"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"id": "012",
|
|
156
|
+
"title": "Icon System",
|
|
157
|
+
"file": "patterns/012-icon-system.md",
|
|
158
|
+
"category": "components",
|
|
159
|
+
"status": "stable",
|
|
160
|
+
"enforcement": "documentation",
|
|
161
|
+
"rule": null,
|
|
162
|
+
"summary": "Iconify with Icon component"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"id": "013",
|
|
166
|
+
"title": "New Component Pattern",
|
|
167
|
+
"file": "patterns/013-new-component-pattern.md",
|
|
168
|
+
"category": "components",
|
|
169
|
+
"status": "stable",
|
|
170
|
+
"enforcement": "documentation",
|
|
171
|
+
"rule": null,
|
|
172
|
+
"summary": "Props for margin/color/fontSize"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": "014",
|
|
176
|
+
"title": "clsx Not classnames",
|
|
177
|
+
"file": "patterns/014-clsx-not-classnames.md",
|
|
178
|
+
"category": "styling",
|
|
179
|
+
"status": "stable",
|
|
180
|
+
"enforcement": "documentation",
|
|
181
|
+
"rule": null,
|
|
182
|
+
"summary": "Use clsx, never classnames package"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"id": "015",
|
|
186
|
+
"title": "No Inline Hover Styles",
|
|
187
|
+
"file": "patterns/015-no-inline-hover-styles.md",
|
|
188
|
+
"category": "styling",
|
|
189
|
+
"status": "stable",
|
|
190
|
+
"enforcement": "documentation",
|
|
191
|
+
"rule": null,
|
|
192
|
+
"summary": "Tailwind for hover states"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"id": "016",
|
|
196
|
+
"title": "Client Extraction",
|
|
197
|
+
"file": "patterns/016-client-extraction.md",
|
|
198
|
+
"category": "rendering",
|
|
199
|
+
"status": "stable",
|
|
200
|
+
"enforcement": "documentation",
|
|
201
|
+
"rule": null,
|
|
202
|
+
"summary": "Extract hooks to components, not blocks (see Pattern 001 for enforcement)"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"id": "017",
|
|
206
|
+
"title": "SEO Metadata",
|
|
207
|
+
"file": "patterns/017-seo-metadata.md",
|
|
208
|
+
"category": "seo",
|
|
209
|
+
"status": "stable",
|
|
210
|
+
"enforcement": "documentation",
|
|
211
|
+
"rule": null,
|
|
212
|
+
"summary": "PageMetadata structure, structured data"
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
"guarantees": [
|
|
216
|
+
{
|
|
217
|
+
"id": "SEO_STABLE",
|
|
218
|
+
"name": "SEO Stability",
|
|
219
|
+
"since": "1.0.0",
|
|
220
|
+
"status": "stable",
|
|
221
|
+
"patterns": ["001", "016", "017"]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": "PERF_BASELINE",
|
|
225
|
+
"name": "Performance Baseline",
|
|
226
|
+
"since": "1.0.0",
|
|
227
|
+
"status": "stable",
|
|
228
|
+
"patterns": ["001", "007", "010", "016"]
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"id": "MAINTAIN",
|
|
232
|
+
"name": "Maintainability",
|
|
233
|
+
"since": "1.0.0",
|
|
234
|
+
"status": "stable",
|
|
235
|
+
"patterns": ["004", "005", "006", "007", "013"]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"id": "DESIGN_SYSTEM",
|
|
239
|
+
"name": "Design System Compliance",
|
|
240
|
+
"since": "1.0.0",
|
|
241
|
+
"status": "stable",
|
|
242
|
+
"patterns": ["003", "004", "009", "010", "011"]
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|