@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
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @gallop/canon
|
|
2
|
+
|
|
3
|
+
**Gallop Enterprise Architecture Canon** — A versioned, AI-compatible, auditable system of approved web architecture patterns.
|
|
4
|
+
|
|
5
|
+
## What is the Canon?
|
|
6
|
+
|
|
7
|
+
The Canon is a closed set of authoritative patterns that define how serious web applications should be built. Each pattern is:
|
|
8
|
+
|
|
9
|
+
- **Versioned** — Pin to a specific version, upgrade deliberately
|
|
10
|
+
- **Documented** — Decision-level documentation, not just code comments
|
|
11
|
+
- **Enforced** — ESLint rules, CI validation, or AI constraints
|
|
12
|
+
- **Immutable** — Once released, patterns don't change
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @gallop/canon
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Access Pattern Metadata
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { patterns, getPattern, getPatternsByCategory } from '@gallop/canon'
|
|
26
|
+
|
|
27
|
+
// Get all patterns
|
|
28
|
+
console.log(patterns)
|
|
29
|
+
|
|
30
|
+
// Get a specific pattern
|
|
31
|
+
const pattern = getPattern('001')
|
|
32
|
+
console.log(pattern.title) // "Server-First Blocks"
|
|
33
|
+
|
|
34
|
+
// Get patterns by category
|
|
35
|
+
const renderingPatterns = getPatternsByCategory('rendering')
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Pattern Categories
|
|
39
|
+
|
|
40
|
+
| Category | Description |
|
|
41
|
+
|----------|-------------|
|
|
42
|
+
| `rendering` | Server/client component boundaries |
|
|
43
|
+
| `layout` | Layout hierarchy and spacing |
|
|
44
|
+
| `typography` | Text component usage |
|
|
45
|
+
| `structure` | File and folder organization |
|
|
46
|
+
| `styling` | CSS and Tailwind patterns |
|
|
47
|
+
| `components` | Component design patterns |
|
|
48
|
+
| `seo` | SEO and metadata patterns |
|
|
49
|
+
|
|
50
|
+
## Patterns
|
|
51
|
+
|
|
52
|
+
See the [patterns/](./patterns) directory for full documentation of each pattern.
|
|
53
|
+
|
|
54
|
+
### Enforced by ESLint
|
|
55
|
+
|
|
56
|
+
- **001** Server-First Blocks
|
|
57
|
+
- **002** Layout Hierarchy
|
|
58
|
+
- **003** Typography Components
|
|
59
|
+
- **004** Component Props
|
|
60
|
+
|
|
61
|
+
### Documentation-Only
|
|
62
|
+
|
|
63
|
+
- **005** Page Structure
|
|
64
|
+
- **006** Block Naming
|
|
65
|
+
- **007** Import Paths
|
|
66
|
+
- **008** Tailwind Only
|
|
67
|
+
- **009** Color Tokens
|
|
68
|
+
- **010** Spacing System
|
|
69
|
+
- **011** Responsive Mobile-First
|
|
70
|
+
- **012** Icon System
|
|
71
|
+
- **013** New Component Pattern
|
|
72
|
+
- **014** clsx Not classnames
|
|
73
|
+
- **015** No Inline Hover Styles
|
|
74
|
+
- **016** Client Extraction
|
|
75
|
+
- **017** SEO Metadata
|
|
76
|
+
|
|
77
|
+
## Guarantees
|
|
78
|
+
|
|
79
|
+
See [guarantees.md](./guarantees.md) for version-specific promises.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @gallop/canon
|
|
3
|
+
*
|
|
4
|
+
* Gallop Enterprise Architecture Canon
|
|
5
|
+
* Versioned, AI-compatible, auditable web architecture patterns
|
|
6
|
+
*/
|
|
7
|
+
export interface Pattern {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
file: string;
|
|
11
|
+
category: PatternCategory;
|
|
12
|
+
status: 'stable' | 'proposed' | 'deprecated';
|
|
13
|
+
enforcement: 'eslint' | 'documentation' | 'ci';
|
|
14
|
+
rule: string | null;
|
|
15
|
+
summary: string;
|
|
16
|
+
}
|
|
17
|
+
export interface Category {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
}
|
|
22
|
+
export interface Guarantee {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
since: string;
|
|
26
|
+
status: 'stable' | 'proposed' | 'deprecated';
|
|
27
|
+
patterns: string[];
|
|
28
|
+
}
|
|
29
|
+
export type PatternCategory = 'rendering' | 'layout' | 'typography' | 'structure' | 'styling' | 'components' | 'seo';
|
|
30
|
+
export interface CanonSchema {
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
description: string;
|
|
34
|
+
categories: Category[];
|
|
35
|
+
patterns: Pattern[];
|
|
36
|
+
guarantees: Guarantee[];
|
|
37
|
+
}
|
|
38
|
+
export declare const canon: CanonSchema;
|
|
39
|
+
export declare const version: string;
|
|
40
|
+
export declare const patterns: Pattern[];
|
|
41
|
+
export declare const categories: Category[];
|
|
42
|
+
export declare const guarantees: Guarantee[];
|
|
43
|
+
/**
|
|
44
|
+
* Get a pattern by ID
|
|
45
|
+
* @param id - Pattern ID (e.g., "001", "002")
|
|
46
|
+
* @returns Pattern object or undefined if not found
|
|
47
|
+
*/
|
|
48
|
+
export declare function getPattern(id: string): Pattern | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Get all patterns in a category
|
|
51
|
+
* @param category - Category ID (e.g., "rendering", "typography")
|
|
52
|
+
* @returns Array of patterns in that category
|
|
53
|
+
*/
|
|
54
|
+
export declare function getPatternsByCategory(category: PatternCategory): Pattern[];
|
|
55
|
+
/**
|
|
56
|
+
* Get all patterns enforced by ESLint
|
|
57
|
+
* @returns Array of ESLint-enforced patterns
|
|
58
|
+
*/
|
|
59
|
+
export declare function getEnforcedPatterns(): Pattern[];
|
|
60
|
+
/**
|
|
61
|
+
* Get all patterns with a specific ESLint rule
|
|
62
|
+
* @param rule - ESLint rule name (e.g., "gallop/no-client-blocks")
|
|
63
|
+
* @returns Array of patterns using that rule
|
|
64
|
+
*/
|
|
65
|
+
export declare function getPatternsByRule(rule: string): Pattern[];
|
|
66
|
+
/**
|
|
67
|
+
* Get a guarantee by ID
|
|
68
|
+
* @param id - Guarantee ID (e.g., "SEO_STABLE")
|
|
69
|
+
* @returns Guarantee object or undefined if not found
|
|
70
|
+
*/
|
|
71
|
+
export declare function getGuarantee(id: string): Guarantee | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Get all patterns associated with a guarantee
|
|
74
|
+
* @param guaranteeId - Guarantee ID (e.g., "SEO_STABLE")
|
|
75
|
+
* @returns Array of patterns that support this guarantee
|
|
76
|
+
*/
|
|
77
|
+
export declare function getPatternsForGuarantee(guaranteeId: string): Pattern[];
|
|
78
|
+
/**
|
|
79
|
+
* Check if a pattern ID is valid
|
|
80
|
+
* @param id - Pattern ID to check
|
|
81
|
+
* @returns true if valid, false otherwise
|
|
82
|
+
*/
|
|
83
|
+
export declare function isValidPattern(id: string): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Get pattern count by enforcement type
|
|
86
|
+
* @returns Object with counts per enforcement type
|
|
87
|
+
*/
|
|
88
|
+
export declare function getEnforcementStats(): Record<string, number>;
|
|
89
|
+
declare const _default: {
|
|
90
|
+
version: string;
|
|
91
|
+
patterns: Pattern[];
|
|
92
|
+
categories: Category[];
|
|
93
|
+
guarantees: Guarantee[];
|
|
94
|
+
getPattern: typeof getPattern;
|
|
95
|
+
getPatternsByCategory: typeof getPatternsByCategory;
|
|
96
|
+
getEnforcedPatterns: typeof getEnforcedPatterns;
|
|
97
|
+
getPatternsByRule: typeof getPatternsByRule;
|
|
98
|
+
getGuarantee: typeof getGuarantee;
|
|
99
|
+
getPatternsForGuarantee: typeof getPatternsForGuarantee;
|
|
100
|
+
isValidPattern: typeof isValidPattern;
|
|
101
|
+
getEnforcementStats: typeof getEnforcementStats;
|
|
102
|
+
};
|
|
103
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @gallop/canon
|
|
4
|
+
*
|
|
5
|
+
* Gallop Enterprise Architecture Canon
|
|
6
|
+
* Versioned, AI-compatible, auditable web architecture patterns
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.guarantees = exports.categories = exports.patterns = exports.version = exports.canon = void 0;
|
|
13
|
+
exports.getPattern = getPattern;
|
|
14
|
+
exports.getPatternsByCategory = getPatternsByCategory;
|
|
15
|
+
exports.getEnforcedPatterns = getEnforcedPatterns;
|
|
16
|
+
exports.getPatternsByRule = getPatternsByRule;
|
|
17
|
+
exports.getGuarantee = getGuarantee;
|
|
18
|
+
exports.getPatternsForGuarantee = getPatternsForGuarantee;
|
|
19
|
+
exports.isValidPattern = isValidPattern;
|
|
20
|
+
exports.getEnforcementStats = getEnforcementStats;
|
|
21
|
+
const schema_json_1 = __importDefault(require("../schema.json"));
|
|
22
|
+
// Export the full schema
|
|
23
|
+
exports.canon = schema_json_1.default;
|
|
24
|
+
// Export version
|
|
25
|
+
exports.version = schema_json_1.default.version;
|
|
26
|
+
// Export patterns array
|
|
27
|
+
exports.patterns = schema_json_1.default.patterns;
|
|
28
|
+
// Export categories array
|
|
29
|
+
exports.categories = schema_json_1.default.categories;
|
|
30
|
+
// Export guarantees array
|
|
31
|
+
exports.guarantees = schema_json_1.default.guarantees;
|
|
32
|
+
/**
|
|
33
|
+
* Get a pattern by ID
|
|
34
|
+
* @param id - Pattern ID (e.g., "001", "002")
|
|
35
|
+
* @returns Pattern object or undefined if not found
|
|
36
|
+
*/
|
|
37
|
+
function getPattern(id) {
|
|
38
|
+
return exports.patterns.find((p) => p.id === id);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get all patterns in a category
|
|
42
|
+
* @param category - Category ID (e.g., "rendering", "typography")
|
|
43
|
+
* @returns Array of patterns in that category
|
|
44
|
+
*/
|
|
45
|
+
function getPatternsByCategory(category) {
|
|
46
|
+
return exports.patterns.filter((p) => p.category === category);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get all patterns enforced by ESLint
|
|
50
|
+
* @returns Array of ESLint-enforced patterns
|
|
51
|
+
*/
|
|
52
|
+
function getEnforcedPatterns() {
|
|
53
|
+
return exports.patterns.filter((p) => p.enforcement === 'eslint');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get all patterns with a specific ESLint rule
|
|
57
|
+
* @param rule - ESLint rule name (e.g., "gallop/no-client-blocks")
|
|
58
|
+
* @returns Array of patterns using that rule
|
|
59
|
+
*/
|
|
60
|
+
function getPatternsByRule(rule) {
|
|
61
|
+
return exports.patterns.filter((p) => p.rule === rule);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get a guarantee by ID
|
|
65
|
+
* @param id - Guarantee ID (e.g., "SEO_STABLE")
|
|
66
|
+
* @returns Guarantee object or undefined if not found
|
|
67
|
+
*/
|
|
68
|
+
function getGuarantee(id) {
|
|
69
|
+
return exports.guarantees.find((g) => g.id === id);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get all patterns associated with a guarantee
|
|
73
|
+
* @param guaranteeId - Guarantee ID (e.g., "SEO_STABLE")
|
|
74
|
+
* @returns Array of patterns that support this guarantee
|
|
75
|
+
*/
|
|
76
|
+
function getPatternsForGuarantee(guaranteeId) {
|
|
77
|
+
const guarantee = getGuarantee(guaranteeId);
|
|
78
|
+
if (!guarantee)
|
|
79
|
+
return [];
|
|
80
|
+
return exports.patterns.filter((p) => guarantee.patterns.includes(p.id));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a pattern ID is valid
|
|
84
|
+
* @param id - Pattern ID to check
|
|
85
|
+
* @returns true if valid, false otherwise
|
|
86
|
+
*/
|
|
87
|
+
function isValidPattern(id) {
|
|
88
|
+
return exports.patterns.some((p) => p.id === id);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get pattern count by enforcement type
|
|
92
|
+
* @returns Object with counts per enforcement type
|
|
93
|
+
*/
|
|
94
|
+
function getEnforcementStats() {
|
|
95
|
+
const stats = {};
|
|
96
|
+
for (const pattern of exports.patterns) {
|
|
97
|
+
stats[pattern.enforcement] = (stats[pattern.enforcement] || 0) + 1;
|
|
98
|
+
}
|
|
99
|
+
return stats;
|
|
100
|
+
}
|
|
101
|
+
// Default export
|
|
102
|
+
exports.default = {
|
|
103
|
+
version: exports.version,
|
|
104
|
+
patterns: exports.patterns,
|
|
105
|
+
categories: exports.categories,
|
|
106
|
+
guarantees: exports.guarantees,
|
|
107
|
+
getPattern,
|
|
108
|
+
getPatternsByCategory,
|
|
109
|
+
getEnforcedPatterns,
|
|
110
|
+
getPatternsByRule,
|
|
111
|
+
getGuarantee,
|
|
112
|
+
getPatternsForGuarantee,
|
|
113
|
+
isValidPattern,
|
|
114
|
+
getEnforcementStats,
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7OztHQUtHOzs7Ozs7QUFvRUgsZ0NBRUM7QUFPRCxzREFFQztBQU1ELGtEQUVDO0FBT0QsOENBRUM7QUFPRCxvQ0FFQztBQU9ELDBEQUlDO0FBT0Qsd0NBRUM7QUFNRCxrREFNQztBQXZJRCxpRUFBbUM7QUE4Q25DLHlCQUF5QjtBQUNaLFFBQUEsS0FBSyxHQUFnQixxQkFBcUIsQ0FBQTtBQUV2RCxpQkFBaUI7QUFDSixRQUFBLE9BQU8sR0FBRyxxQkFBTSxDQUFDLE9BQU8sQ0FBQTtBQUVyQyx3QkFBd0I7QUFDWCxRQUFBLFFBQVEsR0FBYyxxQkFBTSxDQUFDLFFBQXFCLENBQUE7QUFFL0QsMEJBQTBCO0FBQ2IsUUFBQSxVQUFVLEdBQWUscUJBQU0sQ0FBQyxVQUFVLENBQUE7QUFFdkQsMEJBQTBCO0FBQ2IsUUFBQSxVQUFVLEdBQWdCLHFCQUFNLENBQUMsVUFBeUIsQ0FBQTtBQUV2RTs7OztHQUlHO0FBQ0gsU0FBZ0IsVUFBVSxDQUFDLEVBQVU7SUFDbkMsT0FBTyxnQkFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQTtBQUMxQyxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQWdCLHFCQUFxQixDQUFDLFFBQXlCO0lBQzdELE9BQU8sZ0JBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLEtBQUssUUFBUSxDQUFDLENBQUE7QUFDeEQsQ0FBQztBQUVEOzs7R0FHRztBQUNILFNBQWdCLG1CQUFtQjtJQUNqQyxPQUFPLGdCQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsV0FBVyxLQUFLLFFBQVEsQ0FBQyxDQUFBO0FBQzNELENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsU0FBZ0IsaUJBQWlCLENBQUMsSUFBWTtJQUM1QyxPQUFPLGdCQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxLQUFLLElBQUksQ0FBQyxDQUFBO0FBQ2hELENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsU0FBZ0IsWUFBWSxDQUFDLEVBQVU7SUFDckMsT0FBTyxrQkFBVSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQTtBQUM1QyxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQWdCLHVCQUF1QixDQUFDLFdBQW1CO0lBQ3pELE1BQU0sU0FBUyxHQUFHLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQTtJQUMzQyxJQUFJLENBQUMsU0FBUztRQUFFLE9BQU8sRUFBRSxDQUFBO0lBQ3pCLE9BQU8sZ0JBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFBO0FBQ2xFLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsU0FBZ0IsY0FBYyxDQUFDLEVBQVU7SUFDdkMsT0FBTyxnQkFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQTtBQUMxQyxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBZ0IsbUJBQW1CO0lBQ2pDLE1BQU0sS0FBSyxHQUEyQixFQUFFLENBQUE7SUFDeEMsS0FBSyxNQUFNLE9BQU8sSUFBSSxnQkFBUSxFQUFFLENBQUM7UUFDL0IsS0FBSyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBQ3BFLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQTtBQUNkLENBQUM7QUFFRCxpQkFBaUI7QUFDakIsa0JBQWU7SUFDYixPQUFPLEVBQVAsZUFBTztJQUNQLFFBQVEsRUFBUixnQkFBUTtJQUNSLFVBQVUsRUFBVixrQkFBVTtJQUNWLFVBQVUsRUFBVixrQkFBVTtJQUNWLFVBQVU7SUFDVixxQkFBcUI7SUFDckIsbUJBQW1CO0lBQ25CLGlCQUFpQjtJQUNqQixZQUFZO0lBQ1osdUJBQXVCO0lBQ3ZCLGNBQWM7SUFDZCxtQkFBbUI7Q0FDcEIsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGdhbGxvcC9jYW5vblxuICpcbiAqIEdhbGxvcCBFbnRlcnByaXNlIEFyY2hpdGVjdHVyZSBDYW5vblxuICogVmVyc2lvbmVkLCBBSS1jb21wYXRpYmxlLCBhdWRpdGFibGUgd2ViIGFyY2hpdGVjdHVyZSBwYXR0ZXJuc1xuICovXG5cbmltcG9ydCBzY2hlbWEgZnJvbSAnLi4vc2NoZW1hLmpzb24nXG5cbi8vIFR5cGVzXG5leHBvcnQgaW50ZXJmYWNlIFBhdHRlcm4ge1xuICBpZDogc3RyaW5nXG4gIHRpdGxlOiBzdHJpbmdcbiAgZmlsZTogc3RyaW5nXG4gIGNhdGVnb3J5OiBQYXR0ZXJuQ2F0ZWdvcnlcbiAgc3RhdHVzOiAnc3RhYmxlJyB8ICdwcm9wb3NlZCcgfCAnZGVwcmVjYXRlZCdcbiAgZW5mb3JjZW1lbnQ6ICdlc2xpbnQnIHwgJ2RvY3VtZW50YXRpb24nIHwgJ2NpJ1xuICBydWxlOiBzdHJpbmcgfCBudWxsXG4gIHN1bW1hcnk6IHN0cmluZ1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIENhdGVnb3J5IHtcbiAgaWQ6IHN0cmluZ1xuICBuYW1lOiBzdHJpbmdcbiAgZGVzY3JpcHRpb246IHN0cmluZ1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEd1YXJhbnRlZSB7XG4gIGlkOiBzdHJpbmdcbiAgbmFtZTogc3RyaW5nXG4gIHNpbmNlOiBzdHJpbmdcbiAgc3RhdHVzOiAnc3RhYmxlJyB8ICdwcm9wb3NlZCcgfCAnZGVwcmVjYXRlZCdcbiAgcGF0dGVybnM6IHN0cmluZ1tdXG59XG5cbmV4cG9ydCB0eXBlIFBhdHRlcm5DYXRlZ29yeSA9XG4gIHwgJ3JlbmRlcmluZydcbiAgfCAnbGF5b3V0J1xuICB8ICd0eXBvZ3JhcGh5J1xuICB8ICdzdHJ1Y3R1cmUnXG4gIHwgJ3N0eWxpbmcnXG4gIHwgJ2NvbXBvbmVudHMnXG4gIHwgJ3NlbydcblxuZXhwb3J0IGludGVyZmFjZSBDYW5vblNjaGVtYSB7XG4gIG5hbWU6IHN0cmluZ1xuICB2ZXJzaW9uOiBzdHJpbmdcbiAgZGVzY3JpcHRpb246IHN0cmluZ1xuICBjYXRlZ29yaWVzOiBDYXRlZ29yeVtdXG4gIHBhdHRlcm5zOiBQYXR0ZXJuW11cbiAgZ3VhcmFudGVlczogR3VhcmFudGVlW11cbn1cblxuLy8gRXhwb3J0IHRoZSBmdWxsIHNjaGVtYVxuZXhwb3J0IGNvbnN0IGNhbm9uOiBDYW5vblNjaGVtYSA9IHNjaGVtYSBhcyBDYW5vblNjaGVtYVxuXG4vLyBFeHBvcnQgdmVyc2lvblxuZXhwb3J0IGNvbnN0IHZlcnNpb24gPSBzY2hlbWEudmVyc2lvblxuXG4vLyBFeHBvcnQgcGF0dGVybnMgYXJyYXlcbmV4cG9ydCBjb25zdCBwYXR0ZXJuczogUGF0dGVybltdID0gc2NoZW1hLnBhdHRlcm5zIGFzIFBhdHRlcm5bXVxuXG4vLyBFeHBvcnQgY2F0ZWdvcmllcyBhcnJheVxuZXhwb3J0IGNvbnN0IGNhdGVnb3JpZXM6IENhdGVnb3J5W10gPSBzY2hlbWEuY2F0ZWdvcmllc1xuXG4vLyBFeHBvcnQgZ3VhcmFudGVlcyBhcnJheVxuZXhwb3J0IGNvbnN0IGd1YXJhbnRlZXM6IEd1YXJhbnRlZVtdID0gc2NoZW1hLmd1YXJhbnRlZXMgYXMgR3VhcmFudGVlW11cblxuLyoqXG4gKiBHZXQgYSBwYXR0ZXJuIGJ5IElEXG4gKiBAcGFyYW0gaWQgLSBQYXR0ZXJuIElEIChlLmcuLCBcIjAwMVwiLCBcIjAwMlwiKVxuICogQHJldHVybnMgUGF0dGVybiBvYmplY3Qgb3IgdW5kZWZpbmVkIGlmIG5vdCBmb3VuZFxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0UGF0dGVybihpZDogc3RyaW5nKTogUGF0dGVybiB8IHVuZGVmaW5lZCB7XG4gIHJldHVybiBwYXR0ZXJucy5maW5kKChwKSA9PiBwLmlkID09PSBpZClcbn1cblxuLyoqXG4gKiBHZXQgYWxsIHBhdHRlcm5zIGluIGEgY2F0ZWdvcnlcbiAqIEBwYXJhbSBjYXRlZ29yeSAtIENhdGVnb3J5IElEIChlLmcuLCBcInJlbmRlcmluZ1wiLCBcInR5cG9ncmFwaHlcIilcbiAqIEByZXR1cm5zIEFycmF5IG9mIHBhdHRlcm5zIGluIHRoYXQgY2F0ZWdvcnlcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFBhdHRlcm5zQnlDYXRlZ29yeShjYXRlZ29yeTogUGF0dGVybkNhdGVnb3J5KTogUGF0dGVybltdIHtcbiAgcmV0dXJuIHBhdHRlcm5zLmZpbHRlcigocCkgPT4gcC5jYXRlZ29yeSA9PT0gY2F0ZWdvcnkpXG59XG5cbi8qKlxuICogR2V0IGFsbCBwYXR0ZXJucyBlbmZvcmNlZCBieSBFU0xpbnRcbiAqIEByZXR1cm5zIEFycmF5IG9mIEVTTGludC1lbmZvcmNlZCBwYXR0ZXJuc1xuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0RW5mb3JjZWRQYXR0ZXJucygpOiBQYXR0ZXJuW10ge1xuICByZXR1cm4gcGF0dGVybnMuZmlsdGVyKChwKSA9PiBwLmVuZm9yY2VtZW50ID09PSAnZXNsaW50Jylcbn1cblxuLyoqXG4gKiBHZXQgYWxsIHBhdHRlcm5zIHdpdGggYSBzcGVjaWZpYyBFU0xpbnQgcnVsZVxuICogQHBhcmFtIHJ1bGUgLSBFU0xpbnQgcnVsZSBuYW1lIChlLmcuLCBcImdhbGxvcC9uby1jbGllbnQtYmxvY2tzXCIpXG4gKiBAcmV0dXJucyBBcnJheSBvZiBwYXR0ZXJucyB1c2luZyB0aGF0IHJ1bGVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFBhdHRlcm5zQnlSdWxlKHJ1bGU6IHN0cmluZyk6IFBhdHRlcm5bXSB7XG4gIHJldHVybiBwYXR0ZXJucy5maWx0ZXIoKHApID0+IHAucnVsZSA9PT0gcnVsZSlcbn1cblxuLyoqXG4gKiBHZXQgYSBndWFyYW50ZWUgYnkgSURcbiAqIEBwYXJhbSBpZCAtIEd1YXJhbnRlZSBJRCAoZS5nLiwgXCJTRU9fU1RBQkxFXCIpXG4gKiBAcmV0dXJucyBHdWFyYW50ZWUgb2JqZWN0IG9yIHVuZGVmaW5lZCBpZiBub3QgZm91bmRcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldEd1YXJhbnRlZShpZDogc3RyaW5nKTogR3VhcmFudGVlIHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIGd1YXJhbnRlZXMuZmluZCgoZykgPT4gZy5pZCA9PT0gaWQpXG59XG5cbi8qKlxuICogR2V0IGFsbCBwYXR0ZXJucyBhc3NvY2lhdGVkIHdpdGggYSBndWFyYW50ZWVcbiAqIEBwYXJhbSBndWFyYW50ZWVJZCAtIEd1YXJhbnRlZSBJRCAoZS5nLiwgXCJTRU9fU1RBQkxFXCIpXG4gKiBAcmV0dXJucyBBcnJheSBvZiBwYXR0ZXJucyB0aGF0IHN1cHBvcnQgdGhpcyBndWFyYW50ZWVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFBhdHRlcm5zRm9yR3VhcmFudGVlKGd1YXJhbnRlZUlkOiBzdHJpbmcpOiBQYXR0ZXJuW10ge1xuICBjb25zdCBndWFyYW50ZWUgPSBnZXRHdWFyYW50ZWUoZ3VhcmFudGVlSWQpXG4gIGlmICghZ3VhcmFudGVlKSByZXR1cm4gW11cbiAgcmV0dXJuIHBhdHRlcm5zLmZpbHRlcigocCkgPT4gZ3VhcmFudGVlLnBhdHRlcm5zLmluY2x1ZGVzKHAuaWQpKVxufVxuXG4vKipcbiAqIENoZWNrIGlmIGEgcGF0dGVybiBJRCBpcyB2YWxpZFxuICogQHBhcmFtIGlkIC0gUGF0dGVybiBJRCB0byBjaGVja1xuICogQHJldHVybnMgdHJ1ZSBpZiB2YWxpZCwgZmFsc2Ugb3RoZXJ3aXNlXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc1ZhbGlkUGF0dGVybihpZDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIHJldHVybiBwYXR0ZXJucy5zb21lKChwKSA9PiBwLmlkID09PSBpZClcbn1cblxuLyoqXG4gKiBHZXQgcGF0dGVybiBjb3VudCBieSBlbmZvcmNlbWVudCB0eXBlXG4gKiBAcmV0dXJucyBPYmplY3Qgd2l0aCBjb3VudHMgcGVyIGVuZm9yY2VtZW50IHR5cGVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldEVuZm9yY2VtZW50U3RhdHMoKTogUmVjb3JkPHN0cmluZywgbnVtYmVyPiB7XG4gIGNvbnN0IHN0YXRzOiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+ID0ge31cbiAgZm9yIChjb25zdCBwYXR0ZXJuIG9mIHBhdHRlcm5zKSB7XG4gICAgc3RhdHNbcGF0dGVybi5lbmZvcmNlbWVudF0gPSAoc3RhdHNbcGF0dGVybi5lbmZvcmNlbWVudF0gfHwgMCkgKyAxXG4gIH1cbiAgcmV0dXJuIHN0YXRzXG59XG5cbi8vIERlZmF1bHQgZXhwb3J0XG5leHBvcnQgZGVmYXVsdCB7XG4gIHZlcnNpb24sXG4gIHBhdHRlcm5zLFxuICBjYXRlZ29yaWVzLFxuICBndWFyYW50ZWVzLFxuICBnZXRQYXR0ZXJuLFxuICBnZXRQYXR0ZXJuc0J5Q2F0ZWdvcnksXG4gIGdldEVuZm9yY2VkUGF0dGVybnMsXG4gIGdldFBhdHRlcm5zQnlSdWxlLFxuICBnZXRHdWFyYW50ZWUsXG4gIGdldFBhdHRlcm5zRm9yR3VhcmFudGVlLFxuICBpc1ZhbGlkUGF0dGVybixcbiAgZ2V0RW5mb3JjZW1lbnRTdGF0cyxcbn1cbiJdfQ==
|
package/guarantees.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Gallop Canon v1.0 Guarantees
|
|
2
|
+
|
|
3
|
+
These guarantees are promises made by Canon v1.0. They are versioned and immutable once released.
|
|
4
|
+
|
|
5
|
+
## SEO Stability
|
|
6
|
+
|
|
7
|
+
**Guarantee ID:** `SEO_STABLE`
|
|
8
|
+
**Since:** v1.0
|
|
9
|
+
|
|
10
|
+
### Promise
|
|
11
|
+
|
|
12
|
+
Applications built following Canon v1.0 patterns will not experience SEO regressions from architectural decisions.
|
|
13
|
+
|
|
14
|
+
### What This Means
|
|
15
|
+
|
|
16
|
+
- **Server-rendered by default** — All blocks render on the server, content is visible to crawlers
|
|
17
|
+
- **No hydration mismatches** — Client/server boundaries are clearly defined
|
|
18
|
+
- **Structured metadata** — All pages have complete OG, Twitter, and Schema.org data
|
|
19
|
+
- **Canonical URLs** — Every page declares its canonical URL
|
|
20
|
+
- **Crawlable content** — No content hidden behind client-only rendering
|
|
21
|
+
|
|
22
|
+
### Patterns That Enforce This
|
|
23
|
+
|
|
24
|
+
- [001 Server-First Blocks](./patterns/001-server-first-blocks.md)
|
|
25
|
+
- [016 Client Extraction](./patterns/016-client-extraction.md)
|
|
26
|
+
- [017 SEO Metadata](./patterns/017-seo-metadata.md)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Performance Baseline
|
|
31
|
+
|
|
32
|
+
**Guarantee ID:** `PERF_BASELINE`
|
|
33
|
+
**Since:** v1.0
|
|
34
|
+
|
|
35
|
+
### Promise
|
|
36
|
+
|
|
37
|
+
Applications built following Canon v1.0 patterns will have predictable, minimal client-side JavaScript in blocks.
|
|
38
|
+
|
|
39
|
+
### What This Means
|
|
40
|
+
|
|
41
|
+
- **Minimal client bundle** — Blocks don't add to client JavaScript
|
|
42
|
+
- **No layout shift** — Typography and spacing are predictable
|
|
43
|
+
- **Efficient hydration** — Only interactive components hydrate
|
|
44
|
+
- **Tree-shakeable imports** — Unused code is eliminated
|
|
45
|
+
|
|
46
|
+
### Patterns That Enforce This
|
|
47
|
+
|
|
48
|
+
- [001 Server-First Blocks](./patterns/001-server-first-blocks.md)
|
|
49
|
+
- [007 Import Paths](./patterns/007-import-paths.md)
|
|
50
|
+
- [010 Spacing System](./patterns/010-spacing-system.md)
|
|
51
|
+
- [016 Client Extraction](./patterns/016-client-extraction.md)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Maintainability
|
|
56
|
+
|
|
57
|
+
**Guarantee ID:** `MAINTAIN`
|
|
58
|
+
**Since:** v1.0
|
|
59
|
+
|
|
60
|
+
### Promise
|
|
61
|
+
|
|
62
|
+
Applications built following Canon v1.0 patterns will be maintainable by any developer familiar with the Canon.
|
|
63
|
+
|
|
64
|
+
### What This Means
|
|
65
|
+
|
|
66
|
+
- **Consistent file structure** — Blocks, components, and pages follow naming conventions
|
|
67
|
+
- **Traceable component usage** — Components are imported from known locations
|
|
68
|
+
- **Auditable styling** — Tailwind classes and component props are searchable
|
|
69
|
+
- **Documented patterns** — Every decision is explained with rationale
|
|
70
|
+
|
|
71
|
+
### Patterns That Enforce This
|
|
72
|
+
|
|
73
|
+
- [005 Page Structure](./patterns/005-page-structure.md)
|
|
74
|
+
- [006 Block Naming](./patterns/006-block-naming.md)
|
|
75
|
+
- [007 Import Paths](./patterns/007-import-paths.md)
|
|
76
|
+
- [004 Component Props](./patterns/004-component-props.md)
|
|
77
|
+
- [013 New Component Pattern](./patterns/013-new-component-pattern.md)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Design System Compliance
|
|
82
|
+
|
|
83
|
+
**Guarantee ID:** `DESIGN_SYSTEM`
|
|
84
|
+
**Since:** v1.0
|
|
85
|
+
|
|
86
|
+
### Promise
|
|
87
|
+
|
|
88
|
+
Applications built following Canon v1.0 patterns will have consistent visual design through typography, spacing, and color systems.
|
|
89
|
+
|
|
90
|
+
### What This Means
|
|
91
|
+
|
|
92
|
+
- **Semantic typography** — Text uses Heading, Paragraph, Label components
|
|
93
|
+
- **Consistent spacing** — Margins and paddings follow the spacing scale
|
|
94
|
+
- **Semantic colors** — Colors use tokens, not raw values
|
|
95
|
+
- **Responsive patterns** — Mobile-first breakpoints are standard
|
|
96
|
+
|
|
97
|
+
### Patterns That Enforce This
|
|
98
|
+
|
|
99
|
+
- [003 Typography Components](./patterns/003-typography-components.md)
|
|
100
|
+
- [004 Component Props](./patterns/004-component-props.md)
|
|
101
|
+
- [009 Color Tokens](./patterns/009-color-tokens.md)
|
|
102
|
+
- [010 Spacing System](./patterns/010-spacing-system.md)
|
|
103
|
+
- [011 Responsive Mobile-First](./patterns/011-responsive-mobile-first.md)
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Versioning Policy
|
|
108
|
+
|
|
109
|
+
### Guarantee Lifecycle
|
|
110
|
+
|
|
111
|
+
1. **Proposed** — Under consideration, may change
|
|
112
|
+
2. **Stable** — Committed, won't change in this major version
|
|
113
|
+
3. **Deprecated** — Will be removed in next major version
|
|
114
|
+
4. **Removed** — No longer applies
|
|
115
|
+
|
|
116
|
+
### Breaking Changes
|
|
117
|
+
|
|
118
|
+
Guarantees are never removed or weakened within a major version. If a guarantee must change:
|
|
119
|
+
|
|
120
|
+
1. It is deprecated in a minor release
|
|
121
|
+
2. Migration guidance is provided
|
|
122
|
+
3. It is removed in the next major release
|
|
123
|
+
|
|
124
|
+
### Version Pinning
|
|
125
|
+
|
|
126
|
+
Organizations can pin to a specific Canon version:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"dependencies": {
|
|
131
|
+
"@gallop/canon": "1.0.0"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Upgrades should be deliberate and tested.
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gallop.software/canon",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Gallop Enterprise Architecture Canon - Versioned, AI-compatible, auditable web architecture patterns",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"patterns",
|
|
10
|
+
"guarantees.md",
|
|
11
|
+
"schema.json"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"gallop",
|
|
20
|
+
"architecture",
|
|
21
|
+
"patterns",
|
|
22
|
+
"canon",
|
|
23
|
+
"nextjs",
|
|
24
|
+
"react",
|
|
25
|
+
"governance",
|
|
26
|
+
"ai-compatible"
|
|
27
|
+
],
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "Gallop",
|
|
30
|
+
"url": "https://gallop.software"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/gallop-software/gallop",
|
|
36
|
+
"directory": "canon"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"typescript": "^5.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Pattern 001: Server-First Blocks
|
|
2
|
+
|
|
3
|
+
**Canon Version:** 1.0
|
|
4
|
+
**Status:** Stable
|
|
5
|
+
**Category:** Rendering
|
|
6
|
+
**Enforcement:** ESLint
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
Blocks in `src/blocks/` must be server components. They must not use the `'use client'` directive.
|
|
11
|
+
|
|
12
|
+
## Rationale
|
|
13
|
+
|
|
14
|
+
Server components provide significant benefits for web applications:
|
|
15
|
+
|
|
16
|
+
1. **Smaller client bundle** — Server components don't add to JavaScript shipped to browsers
|
|
17
|
+
2. **SEO-safe by default** — Content is rendered on the server, visible to crawlers
|
|
18
|
+
3. **Predictable hydration** — No hydration mismatches when blocks are server-rendered
|
|
19
|
+
4. **Faster initial load** — Less JavaScript to parse and execute
|
|
20
|
+
|
|
21
|
+
Blocks are compositional units that should remain static and SEO-friendly. Interactive behavior belongs in components, not blocks.
|
|
22
|
+
|
|
23
|
+
## Examples
|
|
24
|
+
|
|
25
|
+
### Bad
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
// src/blocks/hero-1.tsx
|
|
29
|
+
'use client'
|
|
30
|
+
import { useState } from 'react'
|
|
31
|
+
|
|
32
|
+
export default function Hero1() {
|
|
33
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
34
|
+
// ...
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Good
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// src/blocks/hero-1.tsx (server component)
|
|
42
|
+
import { InteractiveFeature } from '@/components/interactive-feature'
|
|
43
|
+
|
|
44
|
+
export default function Hero1() {
|
|
45
|
+
return (
|
|
46
|
+
<Section>
|
|
47
|
+
<Heading>Welcome</Heading>
|
|
48
|
+
<InteractiveFeature /> {/* Client logic extracted */}
|
|
49
|
+
</Section>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// src/components/interactive-feature.tsx
|
|
56
|
+
'use client'
|
|
57
|
+
import { useState } from 'react'
|
|
58
|
+
|
|
59
|
+
export function InteractiveFeature() {
|
|
60
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
61
|
+
// Client-side logic lives here
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Enforcement
|
|
66
|
+
|
|
67
|
+
- **ESLint rule:** `gallop/no-client-blocks`
|
|
68
|
+
- **Severity:** Warning (upgradeable to error)
|
|
69
|
+
|
|
70
|
+
## Escape Hatch
|
|
71
|
+
|
|
72
|
+
If a block absolutely requires client-side logic that cannot be extracted:
|
|
73
|
+
|
|
74
|
+
1. Document why extraction is impossible
|
|
75
|
+
2. Consider if it should be a component instead of a block
|
|
76
|
+
3. Use `// eslint-disable-next-line gallop/no-client-blocks` with a comment explaining why
|
|
77
|
+
|
|
78
|
+
This should be extremely rare. Most "exceptions" indicate a design issue.
|
|
79
|
+
|
|
80
|
+
## References
|
|
81
|
+
|
|
82
|
+
- `src/blocks/hero-1.tsx` — Server block with extracted video player
|
|
83
|
+
- `src/blocks/hero-16.tsx` — Server block with extracted animation init
|
|
84
|
+
- `src/components/gallery-popup.tsx` — Client component extracted from block
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Pattern 002: Layout Hierarchy
|
|
2
|
+
|
|
3
|
+
**Canon Version:** 1.0
|
|
4
|
+
**Status:** Stable
|
|
5
|
+
**Category:** Layout
|
|
6
|
+
**Enforcement:** ESLint
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
Do not use `Container` inside `Section`. The `Section` component already handles containment.
|
|
11
|
+
|
|
12
|
+
## Rationale
|
|
13
|
+
|
|
14
|
+
1. **Redundant nesting** — `Section` already provides max-width and padding
|
|
15
|
+
2. **Consistent spacing** — Avoids double-padding issues
|
|
16
|
+
3. **Simpler DOM** — Fewer wrapper elements
|
|
17
|
+
4. **Predictable layout** — One component handles section layout
|
|
18
|
+
|
|
19
|
+
The layout hierarchy should be:
|
|
20
|
+
```
|
|
21
|
+
Section → Columns → Column → Content
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Not:
|
|
25
|
+
```
|
|
26
|
+
Section → Container → Columns → Column → Content
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Bad
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Section>
|
|
35
|
+
<Container>
|
|
36
|
+
<Heading>Title</Heading>
|
|
37
|
+
<Paragraph>Content here</Paragraph>
|
|
38
|
+
</Container>
|
|
39
|
+
</Section>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Good
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<Section>
|
|
46
|
+
<Heading>Title</Heading>
|
|
47
|
+
<Paragraph>Content here</Paragraph>
|
|
48
|
+
</Section>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<Section innerAlign="content">
|
|
53
|
+
<Heading>Title</Heading>
|
|
54
|
+
<Paragraph>Content here</Paragraph>
|
|
55
|
+
</Section>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you need custom max-width, use a plain `div`:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<Section>
|
|
62
|
+
<div className="max-w-4xl">
|
|
63
|
+
<Heading>Title</Heading>
|
|
64
|
+
<Paragraph>Content here</Paragraph>
|
|
65
|
+
</div>
|
|
66
|
+
</Section>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Enforcement
|
|
70
|
+
|
|
71
|
+
- **ESLint rule:** `gallop/no-container-in-section`
|
|
72
|
+
- **Severity:** Warning
|
|
73
|
+
|
|
74
|
+
## Escape Hatch
|
|
75
|
+
|
|
76
|
+
If you need nested containment for a specific design:
|
|
77
|
+
|
|
78
|
+
1. Use a plain `div` with Tailwind classes instead of `Container`
|
|
79
|
+
2. Document why the nested structure is necessary
|
|
80
|
+
|
|
81
|
+
## References
|
|
82
|
+
|
|
83
|
+
- `src/components/section.tsx` — Section component with built-in containment
|
|
84
|
+
- `src/blocks/section-35.tsx` — Example using div for custom max-width
|