@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 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
@@ -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