@opensite/ui 3.2.0 → 3.2.2

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 CHANGED
@@ -445,6 +445,18 @@ const results = searchBlocks("alternating");
445
445
  - `getAllCategories()` - Get all available categories
446
446
  - `searchBlocks(query: string)` - Search blocks by name/description/tags
447
447
 
448
+ ### Builder Contract Bundle
449
+
450
+ `builder-contract-bundle.json` is the versioned machine-readable contract for the semantic builder pipeline. It is generated from the same registry/export path as `registry-export.json`, but adds the structural rules that downstream services enforce:
451
+
452
+ - `blocks` expose stable `blockRef` values derived from public `@opensite/ui/blocks/*` exports.
453
+ - `sharedLayout` declares the canonical `_layout.header` and `_layout.footer` sources.
454
+ - `dynamicSources` keeps symbolic sources such as `blog_feed` in canonical page JSON until `dashtrack-ai` hydrates them at routing-build time.
455
+ - `designTokens` treats `theme_config` as canonical and `tailwind_css` as derived.
456
+ - `pageRules` documents the route-centric payload shape (`block_name`, `block_ref`, `data`) that rendering must follow exactly.
457
+
458
+ If you change a block's public export path, ID, category, semantic tags, or registry description, rebuild the package so the contract bundle stays authoritative.
459
+
448
460
  **Block Categories:**
449
461
 
450
462
  - about, features, cta, testimonials, services, hero, footer, header, pricing, team, stats, faq, contact, gallery, timeline, process, benefits, comparison
@@ -558,9 +570,10 @@ pnpm build
558
570
 
559
571
  - `package.json` export maps (via `generate:exports`)
560
572
  - `registry-export.json` (via `scripts/export-registry.js`)
573
+ - `builder-contract-bundle.json` (via `scripts/export-registry.js`)
561
574
 
562
575
  If you change a block's design/intent, update its registry metadata in
563
- `src/registry/blocks.ts` before building so the exported JSON stays accurate.
576
+ `src/registry/blocks.ts` before building so the exported JSON and builder contract stay accurate.
564
577
 
565
578
  ### Testing
566
579
 
@@ -665,7 +665,7 @@ function BlogFilteredResultsComponent({
665
665
  background,
666
666
  spacing = "hero",
667
667
  pattern,
668
- containerClassName = "mx-auto w-full px-4 lg:px-8 max-w-full md:max-w-7xl relative z-10 flex justify-center",
668
+ containerClassName = "mx-auto w-full px-4 lg:px-8 max-w-full md:max-w-7xl relative z-10 flex items-center flex-col",
669
669
  patternOpacity
670
670
  }) {
671
671
  const effectivePostsPerPage = postsPerPage || POSTS_PER_PAGE;
@@ -641,7 +641,7 @@ function BlogFilteredResultsComponent({
641
641
  background,
642
642
  spacing = "hero",
643
643
  pattern,
644
- containerClassName = "mx-auto w-full px-4 lg:px-8 max-w-full md:max-w-7xl relative z-10 flex justify-center",
644
+ containerClassName = "mx-auto w-full px-4 lg:px-8 max-w-full md:max-w-7xl relative z-10 flex items-center flex-col",
645
645
  patternOpacity
646
646
  }) {
647
647
  const effectivePostsPerPage = postsPerPage || POSTS_PER_PAGE;
package/dist/registry.cjs CHANGED
@@ -37069,7 +37069,7 @@ function BlogFilteredResultsComponent({
37069
37069
  background,
37070
37070
  spacing = "hero",
37071
37071
  pattern,
37072
- containerClassName = "mx-auto w-full px-4 lg:px-8 max-w-full md:max-w-7xl relative z-10 flex justify-center",
37072
+ containerClassName = "mx-auto w-full px-4 lg:px-8 max-w-full md:max-w-7xl relative z-10 flex items-center flex-col",
37073
37073
  patternOpacity
37074
37074
  }) {
37075
37075
  const effectivePostsPerPage = postsPerPage || POSTS_PER_PAGE;
@@ -119879,7 +119879,169 @@ function searchBlocks(query) {
119879
119879
  );
119880
119880
  }
119881
119881
 
119882
+ // src/registry/builder-contract.ts
119883
+ var BUILDER_CONTRACT_VERSION = "v1";
119884
+ var REQUIRED_TOKEN_FAMILIES = [
119885
+ "color",
119886
+ "typography",
119887
+ "spacing",
119888
+ "radius",
119889
+ "shadow"
119890
+ ];
119891
+ var REQUIRED_SEMANTIC_COLOR_ROLES = [
119892
+ "background",
119893
+ "foreground",
119894
+ "card",
119895
+ "card-foreground",
119896
+ "popover",
119897
+ "popover-foreground",
119898
+ "primary",
119899
+ "primary-foreground",
119900
+ "secondary",
119901
+ "secondary-foreground",
119902
+ "muted",
119903
+ "muted-foreground",
119904
+ "accent",
119905
+ "accent-foreground",
119906
+ "destructive",
119907
+ "destructive-foreground",
119908
+ "border",
119909
+ "input",
119910
+ "ring"
119911
+ ];
119912
+ function toPascalCase(value) {
119913
+ return value.split(/[-_\/]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
119914
+ }
119915
+ function normalizeCategorySegment(category) {
119916
+ if (category === "navbar") {
119917
+ return "navbars";
119918
+ }
119919
+ if (category === "footer") {
119920
+ return "footers";
119921
+ }
119922
+ return category;
119923
+ }
119924
+ function inferLayoutRole(blockRef, category) {
119925
+ if (blockRef.startsWith("navbars/") || category === "navbar") {
119926
+ return "header";
119927
+ }
119928
+ if (blockRef.startsWith("footers/") || category === "footer") {
119929
+ return "footer";
119930
+ }
119931
+ return "page";
119932
+ }
119933
+ function normalizeBlock(block, source) {
119934
+ const blockRef = source?.exportPath.replace(/^\.\/blocks\//, "") ?? `${normalizeCategorySegment(block.category)}/${block.id}`;
119935
+ const component = block.component;
119936
+ const blockName = component.displayName || component.name || toPascalCase(block.id);
119937
+ return {
119938
+ componentId: block.id,
119939
+ blockName,
119940
+ blockRef,
119941
+ displayName: block.name,
119942
+ description: block.description,
119943
+ category: block.category,
119944
+ semanticTags: [...block.semanticTags],
119945
+ layoutRole: inferLayoutRole(blockRef, block.category),
119946
+ propsContract: {
119947
+ type: "typescript-type-reference",
119948
+ reference: block.props,
119949
+ runtimeSchema: null,
119950
+ runtimeSchemaStatus: "missing"
119951
+ },
119952
+ examples: {
119953
+ exampleUsage: block.exampleUsage || null,
119954
+ defaultData: null
119955
+ },
119956
+ source: source ?? null
119957
+ };
119958
+ }
119959
+ function createBuilderContractBundle({
119960
+ blocks,
119961
+ uiVersion,
119962
+ exportedAt = (/* @__PURE__ */ new Date()).toISOString(),
119963
+ source = "@opensite/ui",
119964
+ blockSources = {}
119965
+ }) {
119966
+ const normalizedBlocks = blocks.map((block) => normalizeBlock(block, blockSources[block.id])).sort((left, right) => left.blockRef.localeCompare(right.blockRef));
119967
+ const headerBlockRefs = normalizedBlocks.filter((block) => block.layoutRole === "header").map((block) => block.blockRef);
119968
+ const footerBlockRefs = normalizedBlocks.filter((block) => block.layoutRole === "footer").map((block) => block.blockRef);
119969
+ return {
119970
+ metadata: {
119971
+ contractVersion: BUILDER_CONTRACT_VERSION,
119972
+ uiVersion,
119973
+ exportedAt,
119974
+ source,
119975
+ totalBlocks: normalizedBlocks.length
119976
+ },
119977
+ blocks: normalizedBlocks,
119978
+ sharedLayout: {
119979
+ canonicalLayoutKey: "_layout",
119980
+ sections: {
119981
+ header: {
119982
+ sourceType: "website_navbar",
119983
+ sourceOfTruth: "WebsiteNavBar",
119984
+ aiAuthoring: "variant_request_only",
119985
+ canonicalPayloadKey: "header",
119986
+ allowedBlockRefs: headerBlockRefs
119987
+ },
119988
+ footer: {
119989
+ sourceType: "shared_footer_page",
119990
+ sourceOfTruth: "aliased/shared footer pages",
119991
+ aiAuthoring: "variant_request_only",
119992
+ canonicalPayloadKey: "footer",
119993
+ allowedBlockRefs: footerBlockRefs
119994
+ }
119995
+ }
119996
+ },
119997
+ dynamicSources: {
119998
+ blog_feed: {
119999
+ sourceType: "blog_feed",
120000
+ symbolic: true,
120001
+ hydrationOwner: "dashtrack-ai",
120002
+ hydrationPhase: "routing-build",
120003
+ canonicalPayloadExpectation: "Keep blog feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
120004
+ requiredFields: ["type"],
120005
+ optionalFields: ["limit", "offset", "category", "tag", "featuredOnly"]
120006
+ }
120007
+ },
120008
+ designTokens: {
120009
+ canonicalSource: "theme_config",
120010
+ derivedArtifacts: ["tailwind_css"],
120011
+ requiredTokenFamilies: [...REQUIRED_TOKEN_FAMILIES],
120012
+ requiredSemanticColorRoles: [...REQUIRED_SEMANTIC_COLOR_ROLES],
120013
+ policy: "theme_config is canonical and tailwind_css must be derived from it; blocks should consume semantic tokens rather than hardcoded theme values."
120014
+ },
120015
+ pageRules: {
120016
+ outputFormat: "route-map",
120017
+ routeKeyPattern: "^/$|^/.+",
120018
+ sharedLayoutKey: "_layout",
120019
+ routeEntry: {
120020
+ requiredKeys: ["pageId", "pageIds", "blocks"],
120021
+ blocksKey: "blocks"
120022
+ },
120023
+ blockEntry: {
120024
+ requiredKeys: ["block_name", "block_ref", "data"],
120025
+ blockRefSource: "BuilderContractBundle.blocks[].blockRef",
120026
+ blockNameSource: "BuilderContractBundle.blocks[].blockName"
120027
+ },
120028
+ sharedLayoutComposition: {
120029
+ owner: "dashtrack-ai",
120030
+ headerSource: "WebsiteNavBar",
120031
+ footerSource: "aliased/shared footer pages"
120032
+ },
120033
+ dynamicHydration: {
120034
+ symbolicInCanonicalPageJson: true,
120035
+ owner: "dashtrack-ai",
120036
+ phase: "routing-build"
120037
+ }
120038
+ }
120039
+ };
120040
+ }
120041
+
119882
120042
  exports.BLOCK_REGISTRY = BLOCK_REGISTRY;
120043
+ exports.BUILDER_CONTRACT_VERSION = BUILDER_CONTRACT_VERSION;
120044
+ exports.createBuilderContractBundle = createBuilderContractBundle;
119883
120045
  exports.getAllBlocks = getAllBlocks;
119884
120046
  exports.getAllCategories = getAllCategories;
119885
120047
  exports.getBlockById = getBlockById;
@@ -12,44 +12,197 @@
12
12
  * - props: TypeScript type for the component's props
13
13
  * - exampleUsage: Code example showing how to use the block
14
14
  */
15
- interface BlockRegistryEntry<T = any> {
15
+ interface BlockRegistryEntry$1<T = any> {
16
16
  id: string;
17
17
  name: string;
18
18
  description: string;
19
19
  semanticTags: string[];
20
- category: BlockCategory;
20
+ category: BlockCategory$1;
21
21
  component: React.ComponentType<T>;
22
22
  props: string;
23
23
  exampleUsage: string;
24
24
  }
25
- type BlockCategory = "about" | "features" | "cta" | "testimonials" | "services" | "hero" | "footer" | "header" | "pricing" | "team" | "stats" | "faq" | "contact" | "carousel" | "gallery" | "timeline" | "process" | "benefits" | "comparison" | "background-pattern-hero" | "blog" | "article" | "case-studies-list" | "case-study-detail" | "navbar" | "logos" | "project-list" | "project-detail" | "list" | "offer-modal" | "banner" | "industries" | "resource-detail" | "service-detail" | "services-list" | "resource-list" | "link-page";
25
+ type BlockCategory$1 = "about" | "features" | "cta" | "testimonials" | "services" | "hero" | "footer" | "header" | "pricing" | "team" | "stats" | "faq" | "contact" | "carousel" | "gallery" | "timeline" | "process" | "benefits" | "comparison" | "background-pattern-hero" | "blog" | "article" | "case-studies-list" | "case-study-detail" | "navbar" | "logos" | "project-list" | "project-detail" | "list" | "offer-modal" | "banner" | "industries" | "resource-detail" | "service-detail" | "services-list" | "resource-list" | "link-page";
26
26
  /**
27
27
  * Block Registry - Central registry of all available UI blocks
28
28
  */
29
- declare const BLOCK_REGISTRY: Record<string, BlockRegistryEntry>;
29
+ declare const BLOCK_REGISTRY: Record<string, BlockRegistryEntry$1>;
30
30
  /**
31
31
  * Get blocks by semantic tag
32
32
  */
33
- declare function getBlocksBySemanticTag(tag: string): BlockRegistryEntry[];
33
+ declare function getBlocksBySemanticTag(tag: string): BlockRegistryEntry$1[];
34
34
  /**
35
35
  * Get blocks by category
36
36
  */
37
- declare function getBlocksByCategory(category: BlockCategory): BlockRegistryEntry[];
37
+ declare function getBlocksByCategory(category: BlockCategory$1): BlockRegistryEntry$1[];
38
38
  /**
39
39
  * Get block by ID
40
40
  */
41
- declare function getBlockById(id: string): BlockRegistryEntry | undefined;
41
+ declare function getBlockById(id: string): BlockRegistryEntry$1 | undefined;
42
42
  /**
43
43
  * Get all available blocks
44
44
  */
45
- declare function getAllBlocks(): BlockRegistryEntry[];
45
+ declare function getAllBlocks(): BlockRegistryEntry$1[];
46
46
  /**
47
47
  * Get all categories
48
48
  */
49
- declare function getAllCategories(): BlockCategory[];
49
+ declare function getAllCategories(): BlockCategory$1[];
50
50
  /**
51
51
  * Search blocks by query (searches name, description, and semantic tags)
52
52
  */
53
- declare function searchBlocks(query: string): BlockRegistryEntry[];
53
+ declare function searchBlocks(query: string): BlockRegistryEntry$1[];
54
+
55
+ /**
56
+ * Block Registry Types
57
+ *
58
+ * Shared type definitions for the block registry system.
59
+ */
60
+ type BlockCategory = "about" | "features" | "cta" | "testimonials" | "services" | "hero" | "footer" | "header" | "pricing" | "team" | "stats" | "faq" | "contact" | "carousel" | "gallery" | "timeline" | "process" | "benefits" | "comparison" | "background-pattern-hero" | "blog" | "article" | "case-studies-list" | "case-study-detail" | "navbar" | "logos" | "project-list" | "project-detail" | "list" | "offer-modal" | "banner" | "industries" | "resource-detail" | "service-detail" | "services-list" | "resource-list" | "link-page";
61
+ /**
62
+ * Metadata-only block registry entry (no component import)
63
+ * Used for AI-driven component discovery without bundling all components
64
+ */
65
+ interface BlockMetadata {
66
+ id: string;
67
+ name: string;
68
+ description: string;
69
+ semanticTags: string[];
70
+ category: BlockCategory;
71
+ /** Module path for dynamic import, e.g., "@opensite/ui/blocks/about/alternating-blocks" */
72
+ modulePath: string;
73
+ /** Export name from the module */
74
+ exportName: string;
75
+ /** TypeScript props type name */
76
+ props: string;
77
+ /** Example usage code */
78
+ exampleUsage: string;
79
+ }
80
+ /**
81
+ * Full block registry entry with component reference
82
+ * @deprecated Use BlockMetadata for new code - this type causes all components to be bundled
83
+ */
84
+ interface BlockRegistryEntry<T = any> {
85
+ id: string;
86
+ name: string;
87
+ description: string;
88
+ semanticTags: string[];
89
+ category: BlockCategory;
90
+ component: React.ComponentType<T>;
91
+ props: string;
92
+ exampleUsage: string;
93
+ }
94
+ type BuilderContractLayoutRole = "page" | "header" | "footer";
95
+ interface BuilderContractBlockSource {
96
+ exportPath: string;
97
+ modulePath: string;
98
+ typesPath?: string;
99
+ importPath?: string;
100
+ requirePath?: string;
101
+ }
102
+ interface BuilderContractPropsContract {
103
+ type: "typescript-type-reference";
104
+ reference: string;
105
+ runtimeSchema: null;
106
+ runtimeSchemaStatus: "missing";
107
+ }
108
+ interface BuilderContractExamples {
109
+ exampleUsage: string | null;
110
+ defaultData: null;
111
+ }
112
+ interface BuilderContractBlock {
113
+ componentId: string;
114
+ blockName: string;
115
+ blockRef: string;
116
+ displayName: string;
117
+ description: string;
118
+ category: BlockCategory;
119
+ semanticTags: string[];
120
+ layoutRole: BuilderContractLayoutRole;
121
+ propsContract: BuilderContractPropsContract;
122
+ examples: BuilderContractExamples;
123
+ source: BuilderContractBlockSource | null;
124
+ }
125
+ interface BuilderContractMetadata {
126
+ contractVersion: string;
127
+ uiVersion: string;
128
+ exportedAt: string;
129
+ source: string;
130
+ totalBlocks: number;
131
+ }
132
+ interface BuilderContractSharedLayoutSection {
133
+ sourceType: string;
134
+ sourceOfTruth: string;
135
+ aiAuthoring: "variant_request_only";
136
+ canonicalPayloadKey: "header" | "footer";
137
+ allowedBlockRefs: string[];
138
+ }
139
+ interface BuilderContractSharedLayout {
140
+ canonicalLayoutKey: "_layout";
141
+ sections: {
142
+ header: BuilderContractSharedLayoutSection;
143
+ footer: BuilderContractSharedLayoutSection;
144
+ };
145
+ }
146
+ interface BuilderContractDynamicSourceDefinition {
147
+ sourceType: string;
148
+ symbolic: true;
149
+ hydrationOwner: string;
150
+ hydrationPhase: string;
151
+ canonicalPayloadExpectation: string;
152
+ requiredFields: string[];
153
+ optionalFields: string[];
154
+ }
155
+ interface BuilderContractDynamicSources {
156
+ blog_feed: BuilderContractDynamicSourceDefinition;
157
+ }
158
+ interface BuilderContractDesignTokens {
159
+ canonicalSource: "theme_config";
160
+ derivedArtifacts: ["tailwind_css"];
161
+ requiredTokenFamilies: string[];
162
+ requiredSemanticColorRoles: string[];
163
+ policy: string;
164
+ }
165
+ interface BuilderContractPageRules {
166
+ outputFormat: "route-map";
167
+ routeKeyPattern: string;
168
+ sharedLayoutKey: "_layout";
169
+ routeEntry: {
170
+ requiredKeys: string[];
171
+ blocksKey: "blocks";
172
+ };
173
+ blockEntry: {
174
+ requiredKeys: string[];
175
+ blockRefSource: string;
176
+ blockNameSource: string;
177
+ };
178
+ sharedLayoutComposition: {
179
+ owner: string;
180
+ headerSource: string;
181
+ footerSource: string;
182
+ };
183
+ dynamicHydration: {
184
+ symbolicInCanonicalPageJson: true;
185
+ owner: string;
186
+ phase: string;
187
+ };
188
+ }
189
+ interface BuilderContractBundle {
190
+ metadata: BuilderContractMetadata;
191
+ blocks: BuilderContractBlock[];
192
+ sharedLayout: BuilderContractSharedLayout;
193
+ dynamicSources: BuilderContractDynamicSources;
194
+ designTokens: BuilderContractDesignTokens;
195
+ pageRules: BuilderContractPageRules;
196
+ }
197
+
198
+ declare const BUILDER_CONTRACT_VERSION = "v1";
199
+ interface CreateBuilderContractBundleOptions {
200
+ blocks: BlockRegistryEntry[];
201
+ uiVersion: string;
202
+ exportedAt?: string;
203
+ source?: string;
204
+ blockSources?: Record<string, BuilderContractBlockSource>;
205
+ }
206
+ declare function createBuilderContractBundle({ blocks, uiVersion, exportedAt, source, blockSources, }: CreateBuilderContractBundleOptions): BuilderContractBundle;
54
207
 
55
- export { BLOCK_REGISTRY, type BlockCategory, type BlockRegistryEntry, getAllBlocks, getAllCategories, getBlockById, getBlocksByCategory, getBlocksBySemanticTag, searchBlocks };
208
+ export { BLOCK_REGISTRY, BUILDER_CONTRACT_VERSION, type BlockCategory, type BlockMetadata, type BlockRegistryEntry, type BuilderContractBlock, type BuilderContractBlockSource, type BuilderContractBundle, type BuilderContractDesignTokens, type BuilderContractDynamicSourceDefinition, type BuilderContractDynamicSources, type BuilderContractExamples, type BuilderContractLayoutRole, type BuilderContractMetadata, type BuilderContractPageRules, type BuilderContractPropsContract, type BuilderContractSharedLayout, type BuilderContractSharedLayoutSection, createBuilderContractBundle, getAllBlocks, getAllCategories, getBlockById, getBlocksByCategory, getBlocksBySemanticTag, searchBlocks };
@@ -12,44 +12,197 @@
12
12
  * - props: TypeScript type for the component's props
13
13
  * - exampleUsage: Code example showing how to use the block
14
14
  */
15
- interface BlockRegistryEntry<T = any> {
15
+ interface BlockRegistryEntry$1<T = any> {
16
16
  id: string;
17
17
  name: string;
18
18
  description: string;
19
19
  semanticTags: string[];
20
- category: BlockCategory;
20
+ category: BlockCategory$1;
21
21
  component: React.ComponentType<T>;
22
22
  props: string;
23
23
  exampleUsage: string;
24
24
  }
25
- type BlockCategory = "about" | "features" | "cta" | "testimonials" | "services" | "hero" | "footer" | "header" | "pricing" | "team" | "stats" | "faq" | "contact" | "carousel" | "gallery" | "timeline" | "process" | "benefits" | "comparison" | "background-pattern-hero" | "blog" | "article" | "case-studies-list" | "case-study-detail" | "navbar" | "logos" | "project-list" | "project-detail" | "list" | "offer-modal" | "banner" | "industries" | "resource-detail" | "service-detail" | "services-list" | "resource-list" | "link-page";
25
+ type BlockCategory$1 = "about" | "features" | "cta" | "testimonials" | "services" | "hero" | "footer" | "header" | "pricing" | "team" | "stats" | "faq" | "contact" | "carousel" | "gallery" | "timeline" | "process" | "benefits" | "comparison" | "background-pattern-hero" | "blog" | "article" | "case-studies-list" | "case-study-detail" | "navbar" | "logos" | "project-list" | "project-detail" | "list" | "offer-modal" | "banner" | "industries" | "resource-detail" | "service-detail" | "services-list" | "resource-list" | "link-page";
26
26
  /**
27
27
  * Block Registry - Central registry of all available UI blocks
28
28
  */
29
- declare const BLOCK_REGISTRY: Record<string, BlockRegistryEntry>;
29
+ declare const BLOCK_REGISTRY: Record<string, BlockRegistryEntry$1>;
30
30
  /**
31
31
  * Get blocks by semantic tag
32
32
  */
33
- declare function getBlocksBySemanticTag(tag: string): BlockRegistryEntry[];
33
+ declare function getBlocksBySemanticTag(tag: string): BlockRegistryEntry$1[];
34
34
  /**
35
35
  * Get blocks by category
36
36
  */
37
- declare function getBlocksByCategory(category: BlockCategory): BlockRegistryEntry[];
37
+ declare function getBlocksByCategory(category: BlockCategory$1): BlockRegistryEntry$1[];
38
38
  /**
39
39
  * Get block by ID
40
40
  */
41
- declare function getBlockById(id: string): BlockRegistryEntry | undefined;
41
+ declare function getBlockById(id: string): BlockRegistryEntry$1 | undefined;
42
42
  /**
43
43
  * Get all available blocks
44
44
  */
45
- declare function getAllBlocks(): BlockRegistryEntry[];
45
+ declare function getAllBlocks(): BlockRegistryEntry$1[];
46
46
  /**
47
47
  * Get all categories
48
48
  */
49
- declare function getAllCategories(): BlockCategory[];
49
+ declare function getAllCategories(): BlockCategory$1[];
50
50
  /**
51
51
  * Search blocks by query (searches name, description, and semantic tags)
52
52
  */
53
- declare function searchBlocks(query: string): BlockRegistryEntry[];
53
+ declare function searchBlocks(query: string): BlockRegistryEntry$1[];
54
+
55
+ /**
56
+ * Block Registry Types
57
+ *
58
+ * Shared type definitions for the block registry system.
59
+ */
60
+ type BlockCategory = "about" | "features" | "cta" | "testimonials" | "services" | "hero" | "footer" | "header" | "pricing" | "team" | "stats" | "faq" | "contact" | "carousel" | "gallery" | "timeline" | "process" | "benefits" | "comparison" | "background-pattern-hero" | "blog" | "article" | "case-studies-list" | "case-study-detail" | "navbar" | "logos" | "project-list" | "project-detail" | "list" | "offer-modal" | "banner" | "industries" | "resource-detail" | "service-detail" | "services-list" | "resource-list" | "link-page";
61
+ /**
62
+ * Metadata-only block registry entry (no component import)
63
+ * Used for AI-driven component discovery without bundling all components
64
+ */
65
+ interface BlockMetadata {
66
+ id: string;
67
+ name: string;
68
+ description: string;
69
+ semanticTags: string[];
70
+ category: BlockCategory;
71
+ /** Module path for dynamic import, e.g., "@opensite/ui/blocks/about/alternating-blocks" */
72
+ modulePath: string;
73
+ /** Export name from the module */
74
+ exportName: string;
75
+ /** TypeScript props type name */
76
+ props: string;
77
+ /** Example usage code */
78
+ exampleUsage: string;
79
+ }
80
+ /**
81
+ * Full block registry entry with component reference
82
+ * @deprecated Use BlockMetadata for new code - this type causes all components to be bundled
83
+ */
84
+ interface BlockRegistryEntry<T = any> {
85
+ id: string;
86
+ name: string;
87
+ description: string;
88
+ semanticTags: string[];
89
+ category: BlockCategory;
90
+ component: React.ComponentType<T>;
91
+ props: string;
92
+ exampleUsage: string;
93
+ }
94
+ type BuilderContractLayoutRole = "page" | "header" | "footer";
95
+ interface BuilderContractBlockSource {
96
+ exportPath: string;
97
+ modulePath: string;
98
+ typesPath?: string;
99
+ importPath?: string;
100
+ requirePath?: string;
101
+ }
102
+ interface BuilderContractPropsContract {
103
+ type: "typescript-type-reference";
104
+ reference: string;
105
+ runtimeSchema: null;
106
+ runtimeSchemaStatus: "missing";
107
+ }
108
+ interface BuilderContractExamples {
109
+ exampleUsage: string | null;
110
+ defaultData: null;
111
+ }
112
+ interface BuilderContractBlock {
113
+ componentId: string;
114
+ blockName: string;
115
+ blockRef: string;
116
+ displayName: string;
117
+ description: string;
118
+ category: BlockCategory;
119
+ semanticTags: string[];
120
+ layoutRole: BuilderContractLayoutRole;
121
+ propsContract: BuilderContractPropsContract;
122
+ examples: BuilderContractExamples;
123
+ source: BuilderContractBlockSource | null;
124
+ }
125
+ interface BuilderContractMetadata {
126
+ contractVersion: string;
127
+ uiVersion: string;
128
+ exportedAt: string;
129
+ source: string;
130
+ totalBlocks: number;
131
+ }
132
+ interface BuilderContractSharedLayoutSection {
133
+ sourceType: string;
134
+ sourceOfTruth: string;
135
+ aiAuthoring: "variant_request_only";
136
+ canonicalPayloadKey: "header" | "footer";
137
+ allowedBlockRefs: string[];
138
+ }
139
+ interface BuilderContractSharedLayout {
140
+ canonicalLayoutKey: "_layout";
141
+ sections: {
142
+ header: BuilderContractSharedLayoutSection;
143
+ footer: BuilderContractSharedLayoutSection;
144
+ };
145
+ }
146
+ interface BuilderContractDynamicSourceDefinition {
147
+ sourceType: string;
148
+ symbolic: true;
149
+ hydrationOwner: string;
150
+ hydrationPhase: string;
151
+ canonicalPayloadExpectation: string;
152
+ requiredFields: string[];
153
+ optionalFields: string[];
154
+ }
155
+ interface BuilderContractDynamicSources {
156
+ blog_feed: BuilderContractDynamicSourceDefinition;
157
+ }
158
+ interface BuilderContractDesignTokens {
159
+ canonicalSource: "theme_config";
160
+ derivedArtifacts: ["tailwind_css"];
161
+ requiredTokenFamilies: string[];
162
+ requiredSemanticColorRoles: string[];
163
+ policy: string;
164
+ }
165
+ interface BuilderContractPageRules {
166
+ outputFormat: "route-map";
167
+ routeKeyPattern: string;
168
+ sharedLayoutKey: "_layout";
169
+ routeEntry: {
170
+ requiredKeys: string[];
171
+ blocksKey: "blocks";
172
+ };
173
+ blockEntry: {
174
+ requiredKeys: string[];
175
+ blockRefSource: string;
176
+ blockNameSource: string;
177
+ };
178
+ sharedLayoutComposition: {
179
+ owner: string;
180
+ headerSource: string;
181
+ footerSource: string;
182
+ };
183
+ dynamicHydration: {
184
+ symbolicInCanonicalPageJson: true;
185
+ owner: string;
186
+ phase: string;
187
+ };
188
+ }
189
+ interface BuilderContractBundle {
190
+ metadata: BuilderContractMetadata;
191
+ blocks: BuilderContractBlock[];
192
+ sharedLayout: BuilderContractSharedLayout;
193
+ dynamicSources: BuilderContractDynamicSources;
194
+ designTokens: BuilderContractDesignTokens;
195
+ pageRules: BuilderContractPageRules;
196
+ }
197
+
198
+ declare const BUILDER_CONTRACT_VERSION = "v1";
199
+ interface CreateBuilderContractBundleOptions {
200
+ blocks: BlockRegistryEntry[];
201
+ uiVersion: string;
202
+ exportedAt?: string;
203
+ source?: string;
204
+ blockSources?: Record<string, BuilderContractBlockSource>;
205
+ }
206
+ declare function createBuilderContractBundle({ blocks, uiVersion, exportedAt, source, blockSources, }: CreateBuilderContractBundleOptions): BuilderContractBundle;
54
207
 
55
- export { BLOCK_REGISTRY, type BlockCategory, type BlockRegistryEntry, getAllBlocks, getAllCategories, getBlockById, getBlocksByCategory, getBlocksBySemanticTag, searchBlocks };
208
+ export { BLOCK_REGISTRY, BUILDER_CONTRACT_VERSION, type BlockCategory, type BlockMetadata, type BlockRegistryEntry, type BuilderContractBlock, type BuilderContractBlockSource, type BuilderContractBundle, type BuilderContractDesignTokens, type BuilderContractDynamicSourceDefinition, type BuilderContractDynamicSources, type BuilderContractExamples, type BuilderContractLayoutRole, type BuilderContractMetadata, type BuilderContractPageRules, type BuilderContractPropsContract, type BuilderContractSharedLayout, type BuilderContractSharedLayoutSection, createBuilderContractBundle, getAllBlocks, getAllCategories, getBlockById, getBlocksByCategory, getBlocksBySemanticTag, searchBlocks };
package/dist/registry.js CHANGED
@@ -37029,7 +37029,7 @@ function BlogFilteredResultsComponent({
37029
37029
  background,
37030
37030
  spacing = "hero",
37031
37031
  pattern,
37032
- containerClassName = "mx-auto w-full px-4 lg:px-8 max-w-full md:max-w-7xl relative z-10 flex justify-center",
37032
+ containerClassName = "mx-auto w-full px-4 lg:px-8 max-w-full md:max-w-7xl relative z-10 flex items-center flex-col",
37033
37033
  patternOpacity
37034
37034
  }) {
37035
37035
  const effectivePostsPerPage = postsPerPage || POSTS_PER_PAGE;
@@ -119839,4 +119839,164 @@ function searchBlocks(query) {
119839
119839
  );
119840
119840
  }
119841
119841
 
119842
- export { BLOCK_REGISTRY, getAllBlocks, getAllCategories, getBlockById, getBlocksByCategory, getBlocksBySemanticTag, searchBlocks };
119842
+ // src/registry/builder-contract.ts
119843
+ var BUILDER_CONTRACT_VERSION = "v1";
119844
+ var REQUIRED_TOKEN_FAMILIES = [
119845
+ "color",
119846
+ "typography",
119847
+ "spacing",
119848
+ "radius",
119849
+ "shadow"
119850
+ ];
119851
+ var REQUIRED_SEMANTIC_COLOR_ROLES = [
119852
+ "background",
119853
+ "foreground",
119854
+ "card",
119855
+ "card-foreground",
119856
+ "popover",
119857
+ "popover-foreground",
119858
+ "primary",
119859
+ "primary-foreground",
119860
+ "secondary",
119861
+ "secondary-foreground",
119862
+ "muted",
119863
+ "muted-foreground",
119864
+ "accent",
119865
+ "accent-foreground",
119866
+ "destructive",
119867
+ "destructive-foreground",
119868
+ "border",
119869
+ "input",
119870
+ "ring"
119871
+ ];
119872
+ function toPascalCase(value) {
119873
+ return value.split(/[-_\/]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
119874
+ }
119875
+ function normalizeCategorySegment(category) {
119876
+ if (category === "navbar") {
119877
+ return "navbars";
119878
+ }
119879
+ if (category === "footer") {
119880
+ return "footers";
119881
+ }
119882
+ return category;
119883
+ }
119884
+ function inferLayoutRole(blockRef, category) {
119885
+ if (blockRef.startsWith("navbars/") || category === "navbar") {
119886
+ return "header";
119887
+ }
119888
+ if (blockRef.startsWith("footers/") || category === "footer") {
119889
+ return "footer";
119890
+ }
119891
+ return "page";
119892
+ }
119893
+ function normalizeBlock(block, source) {
119894
+ const blockRef = source?.exportPath.replace(/^\.\/blocks\//, "") ?? `${normalizeCategorySegment(block.category)}/${block.id}`;
119895
+ const component = block.component;
119896
+ const blockName = component.displayName || component.name || toPascalCase(block.id);
119897
+ return {
119898
+ componentId: block.id,
119899
+ blockName,
119900
+ blockRef,
119901
+ displayName: block.name,
119902
+ description: block.description,
119903
+ category: block.category,
119904
+ semanticTags: [...block.semanticTags],
119905
+ layoutRole: inferLayoutRole(blockRef, block.category),
119906
+ propsContract: {
119907
+ type: "typescript-type-reference",
119908
+ reference: block.props,
119909
+ runtimeSchema: null,
119910
+ runtimeSchemaStatus: "missing"
119911
+ },
119912
+ examples: {
119913
+ exampleUsage: block.exampleUsage || null,
119914
+ defaultData: null
119915
+ },
119916
+ source: source ?? null
119917
+ };
119918
+ }
119919
+ function createBuilderContractBundle({
119920
+ blocks,
119921
+ uiVersion,
119922
+ exportedAt = (/* @__PURE__ */ new Date()).toISOString(),
119923
+ source = "@opensite/ui",
119924
+ blockSources = {}
119925
+ }) {
119926
+ const normalizedBlocks = blocks.map((block) => normalizeBlock(block, blockSources[block.id])).sort((left, right) => left.blockRef.localeCompare(right.blockRef));
119927
+ const headerBlockRefs = normalizedBlocks.filter((block) => block.layoutRole === "header").map((block) => block.blockRef);
119928
+ const footerBlockRefs = normalizedBlocks.filter((block) => block.layoutRole === "footer").map((block) => block.blockRef);
119929
+ return {
119930
+ metadata: {
119931
+ contractVersion: BUILDER_CONTRACT_VERSION,
119932
+ uiVersion,
119933
+ exportedAt,
119934
+ source,
119935
+ totalBlocks: normalizedBlocks.length
119936
+ },
119937
+ blocks: normalizedBlocks,
119938
+ sharedLayout: {
119939
+ canonicalLayoutKey: "_layout",
119940
+ sections: {
119941
+ header: {
119942
+ sourceType: "website_navbar",
119943
+ sourceOfTruth: "WebsiteNavBar",
119944
+ aiAuthoring: "variant_request_only",
119945
+ canonicalPayloadKey: "header",
119946
+ allowedBlockRefs: headerBlockRefs
119947
+ },
119948
+ footer: {
119949
+ sourceType: "shared_footer_page",
119950
+ sourceOfTruth: "aliased/shared footer pages",
119951
+ aiAuthoring: "variant_request_only",
119952
+ canonicalPayloadKey: "footer",
119953
+ allowedBlockRefs: footerBlockRefs
119954
+ }
119955
+ }
119956
+ },
119957
+ dynamicSources: {
119958
+ blog_feed: {
119959
+ sourceType: "blog_feed",
119960
+ symbolic: true,
119961
+ hydrationOwner: "dashtrack-ai",
119962
+ hydrationPhase: "routing-build",
119963
+ canonicalPayloadExpectation: "Keep blog feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
119964
+ requiredFields: ["type"],
119965
+ optionalFields: ["limit", "offset", "category", "tag", "featuredOnly"]
119966
+ }
119967
+ },
119968
+ designTokens: {
119969
+ canonicalSource: "theme_config",
119970
+ derivedArtifacts: ["tailwind_css"],
119971
+ requiredTokenFamilies: [...REQUIRED_TOKEN_FAMILIES],
119972
+ requiredSemanticColorRoles: [...REQUIRED_SEMANTIC_COLOR_ROLES],
119973
+ policy: "theme_config is canonical and tailwind_css must be derived from it; blocks should consume semantic tokens rather than hardcoded theme values."
119974
+ },
119975
+ pageRules: {
119976
+ outputFormat: "route-map",
119977
+ routeKeyPattern: "^/$|^/.+",
119978
+ sharedLayoutKey: "_layout",
119979
+ routeEntry: {
119980
+ requiredKeys: ["pageId", "pageIds", "blocks"],
119981
+ blocksKey: "blocks"
119982
+ },
119983
+ blockEntry: {
119984
+ requiredKeys: ["block_name", "block_ref", "data"],
119985
+ blockRefSource: "BuilderContractBundle.blocks[].blockRef",
119986
+ blockNameSource: "BuilderContractBundle.blocks[].blockName"
119987
+ },
119988
+ sharedLayoutComposition: {
119989
+ owner: "dashtrack-ai",
119990
+ headerSource: "WebsiteNavBar",
119991
+ footerSource: "aliased/shared footer pages"
119992
+ },
119993
+ dynamicHydration: {
119994
+ symbolicInCanonicalPageJson: true,
119995
+ owner: "dashtrack-ai",
119996
+ phase: "routing-build"
119997
+ }
119998
+ }
119999
+ };
120000
+ }
120001
+
120002
+ export { BLOCK_REGISTRY, BUILDER_CONTRACT_VERSION, createBuilderContractBundle, getAllBlocks, getAllCategories, getBlockById, getBlocksByCategory, getBlocksBySemanticTag, searchBlocks };
@@ -77,6 +77,6 @@ interface SocialLinkIconProps extends Omit<PressableProps, "children">, SocialLi
77
77
  * />
78
78
  * ```
79
79
  */
80
- declare const SocialLinkIcon: React.ForwardRefExoticComponent<SocialLinkIconProps & React.RefAttributes<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement>>;
80
+ declare const SocialLinkIcon: React.ForwardRefExoticComponent<SocialLinkIconProps & React.RefAttributes<HTMLButtonElement | HTMLAnchorElement | HTMLSpanElement>>;
81
81
 
82
82
  export { SocialLinkIcon, type SocialLinkIconDynamicIconProps, type SocialLinkIconProps };
@@ -77,6 +77,6 @@ interface SocialLinkIconProps extends Omit<PressableProps, "children">, SocialLi
77
77
  * />
78
78
  * ```
79
79
  */
80
- declare const SocialLinkIcon: React.ForwardRefExoticComponent<SocialLinkIconProps & React.RefAttributes<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement>>;
80
+ declare const SocialLinkIcon: React.ForwardRefExoticComponent<SocialLinkIconProps & React.RefAttributes<HTMLButtonElement | HTMLAnchorElement | HTMLSpanElement>>;
81
81
 
82
82
  export { SocialLinkIcon, type SocialLinkIconDynamicIconProps, type SocialLinkIconProps };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensite/ui",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "Foundational UI component library for OpenSite Semantic Site Builder with tree-shakable exports and abstract styling",
5
5
  "keywords": [
6
6
  "react",