@contractspec/bundle.marketing 3.8.9 → 3.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.turbo/turbo-build.log +54 -42
  2. package/CHANGELOG.md +33 -0
  3. package/dist/browser/components/templates/TemplatesBrowseControls.js +37 -22
  4. package/dist/browser/components/templates/TemplatesCatalogSection.js +29 -6
  5. package/dist/browser/components/templates/TemplatesClientPage.js +269 -89
  6. package/dist/browser/components/templates/TemplatesOverlays.js +2874 -0
  7. package/dist/browser/components/templates/index.js +301 -121
  8. package/dist/browser/components/templates/template-catalog.js +5 -3
  9. package/dist/browser/components/templates/template-filters.js +99 -0
  10. package/dist/browser/components/templates/template-tag-visibility.js +40 -0
  11. package/dist/browser/components/templates/useTemplateBrowseState.js +191 -0
  12. package/dist/browser/index.js +301 -121
  13. package/dist/components/templates/TemplatesBrowseControls.d.ts +7 -2
  14. package/dist/components/templates/TemplatesBrowseControls.js +37 -22
  15. package/dist/components/templates/TemplatesCatalogSection.d.ts +4 -1
  16. package/dist/components/templates/TemplatesCatalogSection.js +29 -6
  17. package/dist/components/templates/TemplatesClientPage.js +269 -89
  18. package/dist/components/templates/TemplatesOverlays.d.ts +10 -0
  19. package/dist/components/templates/TemplatesOverlays.js +2869 -0
  20. package/dist/components/templates/index.js +301 -121
  21. package/dist/components/templates/template-catalog.d.ts +1 -0
  22. package/dist/components/templates/template-catalog.js +5 -3
  23. package/dist/components/templates/template-filters.d.ts +12 -0
  24. package/dist/components/templates/template-filters.js +94 -0
  25. package/dist/components/templates/template-tag-visibility.d.ts +10 -0
  26. package/dist/components/templates/template-tag-visibility.js +35 -0
  27. package/dist/components/templates/useTemplateBrowseState.d.ts +22 -0
  28. package/dist/components/templates/useTemplateBrowseState.js +186 -0
  29. package/dist/index.js +301 -121
  30. package/dist/node/components/templates/TemplatesBrowseControls.js +37 -22
  31. package/dist/node/components/templates/TemplatesCatalogSection.js +29 -6
  32. package/dist/node/components/templates/TemplatesClientPage.js +269 -89
  33. package/dist/node/components/templates/TemplatesOverlays.js +2869 -0
  34. package/dist/node/components/templates/index.js +301 -121
  35. package/dist/node/components/templates/template-catalog.js +5 -3
  36. package/dist/node/components/templates/template-filters.js +94 -0
  37. package/dist/node/components/templates/template-tag-visibility.js +35 -0
  38. package/dist/node/components/templates/useTemplateBrowseState.js +186 -0
  39. package/dist/node/index.js +301 -121
  40. package/package.json +82 -26
  41. package/src/components/templates/TemplatesBrowseControls.tsx +59 -35
  42. package/src/components/templates/TemplatesCatalogSection.tsx +29 -4
  43. package/src/components/templates/TemplatesClientPage.tsx +41 -97
  44. package/src/components/templates/TemplatesOverlays.tsx +65 -0
  45. package/src/components/templates/template-catalog.test.ts +96 -0
  46. package/src/components/templates/template-catalog.ts +14 -6
  47. package/src/components/templates/template-filters.ts +57 -0
  48. package/src/components/templates/template-tag-visibility.ts +58 -0
  49. package/src/components/templates/useTemplateBrowseState.ts +101 -0
@@ -15,7 +15,11 @@ function TemplatesBrowseControls({
15
15
  onSearchChange,
16
16
  selectedTag,
17
17
  onTagChange,
18
- availableTags
18
+ showTagFilters,
19
+ visibleTagFacets,
20
+ hiddenTagFacets,
21
+ showAllTags,
22
+ onShowAllTagsChange
19
23
  }) {
20
24
  return /* @__PURE__ */ jsxDEV("section", {
21
25
  className: "editorial-section",
@@ -76,29 +80,40 @@ function TemplatesBrowseControls({
76
80
  }, undefined, false, undefined, this)
77
81
  ]
78
82
  }, undefined, true, undefined, this),
79
- /* @__PURE__ */ jsxDEV("div", {
80
- className: "flex flex-wrap gap-2",
83
+ showTagFilters ? /* @__PURE__ */ jsxDEV("div", {
84
+ className: "space-y-3",
81
85
  children: [
82
- /* @__PURE__ */ jsxDEV("button", {
83
- onClick: () => onTagChange(null),
84
- className: cn("rounded-full px-4 py-2 font-medium text-sm transition-colors", {
85
- "bg-primary text-primary-foreground": selectedTag === null,
86
- "border border-border bg-card hover:bg-card/80": selectedTag !== null
87
- }),
88
- "aria-pressed": selectedTag === null,
89
- children: "All"
90
- }, undefined, false, undefined, this),
91
- availableTags.map((tag) => /* @__PURE__ */ jsxDEV("button", {
92
- onClick: () => onTagChange(tag),
93
- className: cn("rounded-full px-4 py-2 font-medium text-sm transition-colors", {
94
- "bg-primary text-primary-foreground": selectedTag === tag,
95
- "border border-border bg-card hover:bg-card/80": selectedTag !== tag
96
- }),
97
- "aria-pressed": selectedTag === tag,
98
- children: tag
99
- }, tag, false, undefined, this))
86
+ /* @__PURE__ */ jsxDEV("div", {
87
+ className: "flex flex-wrap gap-2",
88
+ children: [
89
+ /* @__PURE__ */ jsxDEV("button", {
90
+ onClick: () => onTagChange(null),
91
+ className: cn("rounded-full px-4 py-2 font-medium text-sm transition-colors", {
92
+ "bg-primary text-primary-foreground": selectedTag === null,
93
+ "border border-border bg-card hover:bg-card/80": selectedTag !== null
94
+ }),
95
+ "aria-pressed": selectedTag === null,
96
+ children: "All"
97
+ }, undefined, false, undefined, this),
98
+ visibleTagFacets.map((facet) => /* @__PURE__ */ jsxDEV("button", {
99
+ onClick: () => onTagChange(facet.tag),
100
+ className: cn("rounded-full px-4 py-2 font-medium text-sm transition-colors", {
101
+ "bg-primary text-primary-foreground": selectedTag === facet.tag,
102
+ "border border-border bg-card hover:bg-card/80": selectedTag !== facet.tag
103
+ }),
104
+ "aria-pressed": selectedTag === facet.tag,
105
+ children: facet.tag
106
+ }, facet.tag, false, undefined, this))
107
+ ]
108
+ }, undefined, true, undefined, this),
109
+ hiddenTagFacets.length > 0 || showAllTags ? /* @__PURE__ */ jsxDEV("button", {
110
+ type: "button",
111
+ onClick: () => onShowAllTagsChange(!showAllTags),
112
+ className: "text-muted-foreground text-sm transition-colors hover:text-foreground",
113
+ children: showAllTags ? "Show fewer" : "More tags"
114
+ }, undefined, false, undefined, this) : null
100
115
  ]
101
- }, undefined, true, undefined, this)
116
+ }, undefined, true, undefined, this) : null
102
117
  ]
103
118
  }, undefined, true, undefined, this)
104
119
  ]
@@ -111,15 +111,16 @@ function buildLocalTemplateCatalog(examples = listExamples(), templates = listTe
111
111
  }).sort(compareLocalTemplateCatalogItems);
112
112
  }
113
113
  function matchesTemplateFilters(template, search, selectedTag) {
114
+ return matchesTemplateSearch(template, search) && (selectedTag === null || template.tags.includes(selectedTag));
115
+ }
116
+ function matchesTemplateSearch(template, search) {
114
117
  const haystack = [
115
118
  template.title,
116
119
  template.description,
117
120
  template.tags.join(" ")
118
121
  ].join(" ").toLowerCase();
119
122
  const searchTokens = search.trim().toLowerCase().split(/\s+/).filter(Boolean);
120
- const matchesSearch = searchTokens.length === 0 || searchTokens.every((token) => haystack.includes(token));
121
- const matchesTag = selectedTag === null || template.tags.includes(selectedTag);
122
- return matchesSearch && matchesTag;
123
+ return searchTokens.length === 0 || searchTokens.every((token) => haystack.includes(token));
123
124
  }
124
125
  function formatExampleKindLabel(kind) {
125
126
  return kind.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
@@ -180,13 +181,17 @@ function TemplatesCatalogSection({
180
181
  source,
181
182
  registryConfigured,
182
183
  registryLoading,
184
+ registryHasTemplates,
183
185
  localTemplates,
184
186
  registryTemplates,
185
187
  localTemplateById,
186
188
  onPreview,
187
- onUseTemplate
189
+ onUseTemplate,
190
+ hasSearch,
191
+ selectedTag
188
192
  }) {
189
193
  const showRegistry = source === "registry" && registryConfigured;
194
+ const emptyStateMessage = getEmptyStateMessage(hasSearch, selectedTag);
190
195
  return /* @__PURE__ */ jsxDEV2("section", {
191
196
  className: "section-padding",
192
197
  children: /* @__PURE__ */ jsxDEV2("div", {
@@ -197,12 +202,18 @@ function TemplatesCatalogSection({
197
202
  className: "text-muted-foreground",
198
203
  children: "Loading community templates…"
199
204
  }, undefined, false, undefined, this)
200
- }, undefined, false, undefined, this) : registryTemplates.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
205
+ }, undefined, false, undefined, this) : !registryHasTemplates ? /* @__PURE__ */ jsxDEV2("div", {
201
206
  className: "py-12 text-center",
202
207
  children: /* @__PURE__ */ jsxDEV2("p", {
203
208
  className: "text-muted-foreground",
204
209
  children: "No community templates found."
205
210
  }, undefined, false, undefined, this)
211
+ }, undefined, false, undefined, this) : registryTemplates.length === 0 ? /* @__PURE__ */ jsxDEV2("div", {
212
+ className: "py-12 text-center",
213
+ children: /* @__PURE__ */ jsxDEV2("p", {
214
+ className: "text-muted-foreground",
215
+ children: emptyStateMessage
216
+ }, undefined, false, undefined, this)
206
217
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("div", {
207
218
  className: "grid gap-6 md:grid-cols-2 lg:grid-cols-3",
208
219
  children: registryTemplates.map((template) => {
@@ -238,7 +249,7 @@ function TemplatesCatalogSection({
238
249
  className: "py-12 text-center",
239
250
  children: /* @__PURE__ */ jsxDEV2("p", {
240
251
  className: "text-muted-foreground",
241
- children: "No templates match your filters. Try a different search."
252
+ children: emptyStateMessage
242
253
  }, undefined, false, undefined, this)
243
254
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("div", {
244
255
  className: "grid gap-6 md:grid-cols-2 lg:grid-cols-3",
@@ -274,6 +285,18 @@ function TemplatesCatalogSection({
274
285
  }, undefined, false, undefined, this)
275
286
  }, undefined, false, undefined, this);
276
287
  }
288
+ function getEmptyStateMessage(hasSearch, selectedTag) {
289
+ if (selectedTag !== null && hasSearch) {
290
+ return "No templates match this tag for the current search.";
291
+ }
292
+ if (selectedTag !== null) {
293
+ return "No templates match this tag. Try another tag or reset filters.";
294
+ }
295
+ if (hasSearch) {
296
+ return "No templates match your search. Try a different keyword.";
297
+ }
298
+ return "No templates match your filters. Try a different search.";
299
+ }
277
300
  export {
278
301
  TemplatesCatalogSection
279
302
  };
@@ -2834,7 +2834,11 @@ function TemplatesBrowseControls({
2834
2834
  onSearchChange,
2835
2835
  selectedTag,
2836
2836
  onTagChange,
2837
- availableTags
2837
+ showTagFilters,
2838
+ visibleTagFacets,
2839
+ hiddenTagFacets,
2840
+ showAllTags,
2841
+ onShowAllTagsChange
2838
2842
  }) {
2839
2843
  return /* @__PURE__ */ jsxDEV14("section", {
2840
2844
  className: "editorial-section",
@@ -2895,29 +2899,40 @@ function TemplatesBrowseControls({
2895
2899
  }, undefined, false, undefined, this)
2896
2900
  ]
2897
2901
  }, undefined, true, undefined, this),
2898
- /* @__PURE__ */ jsxDEV14("div", {
2899
- className: "flex flex-wrap gap-2",
2902
+ showTagFilters ? /* @__PURE__ */ jsxDEV14("div", {
2903
+ className: "space-y-3",
2900
2904
  children: [
2901
- /* @__PURE__ */ jsxDEV14("button", {
2902
- onClick: () => onTagChange(null),
2903
- className: cn("rounded-full px-4 py-2 font-medium text-sm transition-colors", {
2904
- "bg-primary text-primary-foreground": selectedTag === null,
2905
- "border border-border bg-card hover:bg-card/80": selectedTag !== null
2906
- }),
2907
- "aria-pressed": selectedTag === null,
2908
- children: "All"
2909
- }, undefined, false, undefined, this),
2910
- availableTags.map((tag) => /* @__PURE__ */ jsxDEV14("button", {
2911
- onClick: () => onTagChange(tag),
2912
- className: cn("rounded-full px-4 py-2 font-medium text-sm transition-colors", {
2913
- "bg-primary text-primary-foreground": selectedTag === tag,
2914
- "border border-border bg-card hover:bg-card/80": selectedTag !== tag
2915
- }),
2916
- "aria-pressed": selectedTag === tag,
2917
- children: tag
2918
- }, tag, false, undefined, this))
2905
+ /* @__PURE__ */ jsxDEV14("div", {
2906
+ className: "flex flex-wrap gap-2",
2907
+ children: [
2908
+ /* @__PURE__ */ jsxDEV14("button", {
2909
+ onClick: () => onTagChange(null),
2910
+ className: cn("rounded-full px-4 py-2 font-medium text-sm transition-colors", {
2911
+ "bg-primary text-primary-foreground": selectedTag === null,
2912
+ "border border-border bg-card hover:bg-card/80": selectedTag !== null
2913
+ }),
2914
+ "aria-pressed": selectedTag === null,
2915
+ children: "All"
2916
+ }, undefined, false, undefined, this),
2917
+ visibleTagFacets.map((facet) => /* @__PURE__ */ jsxDEV14("button", {
2918
+ onClick: () => onTagChange(facet.tag),
2919
+ className: cn("rounded-full px-4 py-2 font-medium text-sm transition-colors", {
2920
+ "bg-primary text-primary-foreground": selectedTag === facet.tag,
2921
+ "border border-border bg-card hover:bg-card/80": selectedTag !== facet.tag
2922
+ }),
2923
+ "aria-pressed": selectedTag === facet.tag,
2924
+ children: facet.tag
2925
+ }, facet.tag, false, undefined, this))
2926
+ ]
2927
+ }, undefined, true, undefined, this),
2928
+ hiddenTagFacets.length > 0 || showAllTags ? /* @__PURE__ */ jsxDEV14("button", {
2929
+ type: "button",
2930
+ onClick: () => onShowAllTagsChange(!showAllTags),
2931
+ className: "text-muted-foreground text-sm transition-colors hover:text-foreground",
2932
+ children: showAllTags ? "Show fewer" : "More tags"
2933
+ }, undefined, false, undefined, this) : null
2919
2934
  ]
2920
- }, undefined, true, undefined, this)
2935
+ }, undefined, true, undefined, this) : null
2921
2936
  ]
2922
2937
  }, undefined, true, undefined, this)
2923
2938
  ]
@@ -2962,15 +2977,16 @@ function buildLocalTemplateCatalog(examples = listExamples(), templates = listTe
2962
2977
  }).sort(compareLocalTemplateCatalogItems);
2963
2978
  }
2964
2979
  function matchesTemplateFilters(template, search, selectedTag) {
2980
+ return matchesTemplateSearch(template, search) && (selectedTag === null || template.tags.includes(selectedTag));
2981
+ }
2982
+ function matchesTemplateSearch(template, search) {
2965
2983
  const haystack = [
2966
2984
  template.title,
2967
2985
  template.description,
2968
2986
  template.tags.join(" ")
2969
2987
  ].join(" ").toLowerCase();
2970
2988
  const searchTokens = search.trim().toLowerCase().split(/\s+/).filter(Boolean);
2971
- const matchesSearch = searchTokens.length === 0 || searchTokens.every((token) => haystack.includes(token));
2972
- const matchesTag = selectedTag === null || template.tags.includes(selectedTag);
2973
- return matchesSearch && matchesTag;
2989
+ return searchTokens.length === 0 || searchTokens.every((token) => haystack.includes(token));
2974
2990
  }
2975
2991
  function formatExampleKindLabel(kind) {
2976
2992
  return kind.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
@@ -3031,13 +3047,17 @@ function TemplatesCatalogSection({
3031
3047
  source,
3032
3048
  registryConfigured,
3033
3049
  registryLoading,
3050
+ registryHasTemplates,
3034
3051
  localTemplates,
3035
3052
  registryTemplates,
3036
3053
  localTemplateById,
3037
3054
  onPreview,
3038
- onUseTemplate
3055
+ onUseTemplate,
3056
+ hasSearch,
3057
+ selectedTag
3039
3058
  }) {
3040
3059
  const showRegistry = source === "registry" && registryConfigured;
3060
+ const emptyStateMessage = getEmptyStateMessage(hasSearch, selectedTag);
3041
3061
  return /* @__PURE__ */ jsxDEV15("section", {
3042
3062
  className: "section-padding",
3043
3063
  children: /* @__PURE__ */ jsxDEV15("div", {
@@ -3048,12 +3068,18 @@ function TemplatesCatalogSection({
3048
3068
  className: "text-muted-foreground",
3049
3069
  children: "Loading community templates…"
3050
3070
  }, undefined, false, undefined, this)
3051
- }, undefined, false, undefined, this) : registryTemplates.length === 0 ? /* @__PURE__ */ jsxDEV15("div", {
3071
+ }, undefined, false, undefined, this) : !registryHasTemplates ? /* @__PURE__ */ jsxDEV15("div", {
3052
3072
  className: "py-12 text-center",
3053
3073
  children: /* @__PURE__ */ jsxDEV15("p", {
3054
3074
  className: "text-muted-foreground",
3055
3075
  children: "No community templates found."
3056
3076
  }, undefined, false, undefined, this)
3077
+ }, undefined, false, undefined, this) : registryTemplates.length === 0 ? /* @__PURE__ */ jsxDEV15("div", {
3078
+ className: "py-12 text-center",
3079
+ children: /* @__PURE__ */ jsxDEV15("p", {
3080
+ className: "text-muted-foreground",
3081
+ children: emptyStateMessage
3082
+ }, undefined, false, undefined, this)
3057
3083
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV15("div", {
3058
3084
  className: "grid gap-6 md:grid-cols-2 lg:grid-cols-3",
3059
3085
  children: registryTemplates.map((template) => {
@@ -3089,7 +3115,7 @@ function TemplatesCatalogSection({
3089
3115
  className: "py-12 text-center",
3090
3116
  children: /* @__PURE__ */ jsxDEV15("p", {
3091
3117
  className: "text-muted-foreground",
3092
- children: "No templates match your filters. Try a different search."
3118
+ children: emptyStateMessage
3093
3119
  }, undefined, false, undefined, this)
3094
3120
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV15("div", {
3095
3121
  className: "grid gap-6 md:grid-cols-2 lg:grid-cols-3",
@@ -3125,6 +3151,18 @@ function TemplatesCatalogSection({
3125
3151
  }, undefined, false, undefined, this)
3126
3152
  }, undefined, false, undefined, this);
3127
3153
  }
3154
+ function getEmptyStateMessage(hasSearch, selectedTag) {
3155
+ if (selectedTag !== null && hasSearch) {
3156
+ return "No templates match this tag for the current search.";
3157
+ }
3158
+ if (selectedTag !== null) {
3159
+ return "No templates match this tag. Try another tag or reset filters.";
3160
+ }
3161
+ if (hasSearch) {
3162
+ return "No templates match your search. Try a different keyword.";
3163
+ }
3164
+ return "No templates match your filters. Try a different search.";
3165
+ }
3128
3166
 
3129
3167
  // src/components/templates/TemplatesHeroSection.tsx
3130
3168
  import { jsxDEV as jsxDEV16 } from "react/jsx-dev-runtime";
@@ -3351,6 +3389,82 @@ function TemplatePreviewModal({
3351
3389
  }, undefined, false, undefined, this);
3352
3390
  }
3353
3391
 
3392
+ // src/components/templates/TemplatesOverlays.tsx
3393
+ import {
3394
+ Dialog as Dialog4,
3395
+ DialogContent as DialogContent4,
3396
+ DialogDescription as DialogDescription3,
3397
+ DialogHeader as DialogHeader3,
3398
+ DialogTitle as DialogTitle3
3399
+ } from "@contractspec/lib.ui-kit-web/ui/dialog";
3400
+ import { jsxDEV as jsxDEV19, Fragment } from "react/jsx-dev-runtime";
3401
+ "use client";
3402
+ function TemplatesOverlays({
3403
+ previewTemplateId,
3404
+ onPreviewClose,
3405
+ studioSignupModalOpen,
3406
+ onStudioSignupModalOpenChange,
3407
+ selectedTemplateId,
3408
+ onTemplateCommandClose,
3409
+ onDeployStudio
3410
+ }) {
3411
+ return /* @__PURE__ */ jsxDEV19(Fragment, {
3412
+ children: [
3413
+ previewTemplateId ? /* @__PURE__ */ jsxDEV19(TemplatePreviewModal, {
3414
+ templateId: previewTemplateId,
3415
+ onClose: onPreviewClose
3416
+ }, undefined, false, undefined, this) : null,
3417
+ /* @__PURE__ */ jsxDEV19(Dialog4, {
3418
+ open: studioSignupModalOpen,
3419
+ onOpenChange: onStudioSignupModalOpenChange,
3420
+ children: /* @__PURE__ */ jsxDEV19(DialogContent4, {
3421
+ className: "max-h-[90vh] max-w-2xl overflow-y-auto",
3422
+ children: [
3423
+ /* @__PURE__ */ jsxDEV19(DialogHeader3, {
3424
+ children: [
3425
+ /* @__PURE__ */ jsxDEV19(DialogTitle3, {
3426
+ children: "Deploy in Studio"
3427
+ }, undefined, false, undefined, this),
3428
+ /* @__PURE__ */ jsxDEV19(DialogDescription3, {
3429
+ children: "Deploy templates in ContractSpec Studio and run the full evidence-to-spec loop with your team."
3430
+ }, undefined, false, undefined, this)
3431
+ ]
3432
+ }, undefined, true, undefined, this),
3433
+ /* @__PURE__ */ jsxDEV19(StudioSignupSection, {
3434
+ variant: "compact"
3435
+ }, undefined, false, undefined, this)
3436
+ ]
3437
+ }, undefined, true, undefined, this)
3438
+ }, undefined, false, undefined, this),
3439
+ /* @__PURE__ */ jsxDEV19(TemplateCommandDialog, {
3440
+ templateId: selectedTemplateId,
3441
+ onClose: onTemplateCommandClose,
3442
+ onDeployStudio
3443
+ }, undefined, false, undefined, this)
3444
+ ]
3445
+ }, undefined, true, undefined, this);
3446
+ }
3447
+
3448
+ // src/components/templates/template-filters.ts
3449
+ function buildTemplateFilterState(templates, search, selectedTag, getCandidate) {
3450
+ const searchScopedTemplates = templates.filter((template) => matchesTemplateSearch(getCandidate(template), search));
3451
+ const finalTemplates = selectedTag === null ? searchScopedTemplates : searchScopedTemplates.filter((template) => getCandidate(template).tags.includes(selectedTag));
3452
+ return {
3453
+ searchScopedTemplates,
3454
+ finalTemplates,
3455
+ tagFacets: buildTemplateTagFacets(searchScopedTemplates, getCandidate)
3456
+ };
3457
+ }
3458
+ function buildTemplateTagFacets(templates, getCandidate) {
3459
+ const counts = new Map;
3460
+ for (const template of templates) {
3461
+ for (const tag of new Set(getCandidate(template).tags)) {
3462
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
3463
+ }
3464
+ }
3465
+ return [...counts.entries()].map(([tag, count]) => ({ tag, count })).sort((left, right) => right.count - left.count || left.tag.localeCompare(right.tag));
3466
+ }
3467
+
3354
3468
  // src/components/templates/template-source.ts
3355
3469
  function isRegistryConfigured(registryUrl) {
3356
3470
  return Boolean(registryUrl?.trim());
@@ -3359,51 +3473,132 @@ function getAvailableTemplateSources(registryUrl) {
3359
3473
  return isRegistryConfigured(registryUrl) ? ["local", "registry"] : ["local"];
3360
3474
  }
3361
3475
 
3362
- // src/components/templates/TemplatesClientPage.tsx
3363
- import {
3364
- analyticsEventNames as analyticsEventNames3,
3365
- captureAnalyticsEvent as captureAnalyticsEvent3
3366
- } from "@contractspec/bundle.library/libs/posthog/client";
3476
+ // src/components/templates/template-tag-visibility.ts
3477
+ var DEFAULT_VISIBLE_TEMPLATE_TAGS = 10;
3478
+ function getVisibleTemplateTagFacets(tagFacets, selectedTag, expanded, visibleCount = DEFAULT_VISIBLE_TEMPLATE_TAGS) {
3479
+ if (expanded) {
3480
+ return {
3481
+ visibleTagFacets: pinSelectedTagFacet(tagFacets, selectedTag),
3482
+ hiddenTagFacets: []
3483
+ };
3484
+ }
3485
+ const visibleTagFacets = pinSelectedTagFacet(tagFacets.slice(0, visibleCount), selectedTag, tagFacets);
3486
+ const visibleTags = new Set(visibleTagFacets.map((facet) => facet.tag));
3487
+ return {
3488
+ visibleTagFacets,
3489
+ hiddenTagFacets: tagFacets.filter((facet) => !visibleTags.has(facet.tag))
3490
+ };
3491
+ }
3492
+ function pinSelectedTagFacet(tagFacets, selectedTag, fallbackTagFacets = tagFacets) {
3493
+ if (selectedTag === null || tagFacets.some((facet) => facet.tag === selectedTag)) {
3494
+ return [...tagFacets];
3495
+ }
3496
+ return [
3497
+ ...tagFacets,
3498
+ fallbackTagFacets.find((facet) => facet.tag === selectedTag) ?? {
3499
+ tag: selectedTag,
3500
+ count: 0
3501
+ }
3502
+ ];
3503
+ }
3504
+
3505
+ // src/components/templates/useTemplateBrowseState.ts
3367
3506
  import { useRegistryTemplates } from "@contractspec/lib.example-shared-ui";
3368
- import {
3369
- Dialog as Dialog4,
3370
- DialogContent as DialogContent4,
3371
- DialogDescription as DialogDescription3,
3372
- DialogHeader as DialogHeader3,
3373
- DialogTitle as DialogTitle3
3374
- } from "@contractspec/lib.ui-kit-web/ui/dialog";
3375
- import { useMemo, useState as useState2 } from "react";
3376
- import { jsxDEV as jsxDEV19, Fragment } from "react/jsx-dev-runtime";
3507
+ import { useEffect, useMemo, useState as useState2 } from "react";
3377
3508
  "use client";
3378
3509
  var REGISTRY_URL = process.env.NEXT_PUBLIC_CONTRACTSPEC_REGISTRY_URL;
3379
- var TemplatesPage = () => {
3510
+ function useTemplateBrowseState() {
3380
3511
  const [selectedTag, setSelectedTag] = useState2(null);
3381
3512
  const [search, setSearch] = useState2("");
3382
- const [previewTemplateId, setPreviewTemplateId] = useState2(null);
3383
- const [studioSignupModalOpen, setStudioSignupModalOpen] = useState2(false);
3384
- const [selectedTemplateId, setSelectedTemplateId] = useState2(null);
3385
3513
  const [source, setSource] = useState2("local");
3514
+ const [showAllTags, setShowAllTags] = useState2(false);
3386
3515
  const registryConfigured = isRegistryConfigured(REGISTRY_URL);
3387
3516
  const availableSources = getAvailableTemplateSources(REGISTRY_URL);
3388
3517
  const localTemplates = useMemo(() => buildLocalTemplateCatalog(), []);
3389
3518
  const localTemplateById = useMemo(() => new Map(localTemplates.map((template) => [template.id, template])), [localTemplates]);
3390
- const availableTags = useMemo(() => Array.from(new Set(localTemplates.flatMap((template) => template.tags))).sort((left, right) => left.localeCompare(right)), [localTemplates]);
3391
3519
  const { data: registryTemplates = [], isLoading: registryLoading } = useRegistryTemplates();
3392
- const filteredLocalTemplates = useMemo(() => localTemplates.filter((template) => matchesTemplateFilters(template, search, selectedTag)), [localTemplates, search, selectedTag]);
3393
- const filteredRegistryTemplates = useMemo(() => registryTemplates.filter((template) => matchesTemplateFilters({
3520
+ const localFilterState = useMemo(() => buildTemplateFilterState(localTemplates, search, selectedTag, (template) => ({
3521
+ title: template.title,
3522
+ description: template.description,
3523
+ tags: template.tags
3524
+ })), [localTemplates, search, selectedTag]);
3525
+ const registryFilterState = useMemo(() => buildTemplateFilterState(registryTemplates, search, selectedTag, (template) => ({
3394
3526
  title: template.name,
3395
3527
  description: template.description,
3396
3528
  tags: template.tags
3397
- }, search, selectedTag)), [registryTemplates, search, selectedTag]);
3398
- return /* @__PURE__ */ jsxDEV19(Fragment, {
3529
+ })), [registryTemplates, search, selectedTag]);
3530
+ const activeFilterState = source === "registry" ? registryFilterState : localFilterState;
3531
+ const suppressTagRail = source === "registry" && (registryLoading || registryTemplates.length === 0);
3532
+ const { visibleTagFacets, hiddenTagFacets } = useMemo(() => getVisibleTemplateTagFacets(activeFilterState.tagFacets, selectedTag, showAllTags), [activeFilterState.tagFacets, selectedTag, showAllTags]);
3533
+ const showTagFilters = !suppressTagRail && (visibleTagFacets.length > 0 || hiddenTagFacets.length > 0);
3534
+ useEffect(() => {
3535
+ setShowAllTags(false);
3536
+ }, [search, showTagFilters, source]);
3537
+ return {
3538
+ selectedTag,
3539
+ setSelectedTag,
3540
+ search,
3541
+ setSearch,
3542
+ source,
3543
+ setSource,
3544
+ showAllTags,
3545
+ setShowAllTags,
3546
+ registryConfigured,
3547
+ availableSources,
3548
+ localTemplates,
3549
+ localTemplateById,
3550
+ registryTemplates,
3551
+ registryLoading,
3552
+ localFilterState,
3553
+ registryFilterState,
3554
+ visibleTagFacets,
3555
+ hiddenTagFacets,
3556
+ showTagFilters
3557
+ };
3558
+ }
3559
+
3560
+ // src/components/templates/TemplatesClientPage.tsx
3561
+ import {
3562
+ analyticsEventNames as analyticsEventNames3,
3563
+ captureAnalyticsEvent as captureAnalyticsEvent3
3564
+ } from "@contractspec/bundle.library/libs/posthog/client";
3565
+ import { useState as useState3 } from "react";
3566
+ import { jsxDEV as jsxDEV20, Fragment as Fragment2 } from "react/jsx-dev-runtime";
3567
+ "use client";
3568
+ var TemplatesPage = () => {
3569
+ const [previewTemplateId, setPreviewTemplateId] = useState3(null);
3570
+ const [studioSignupModalOpen, setStudioSignupModalOpen] = useState3(false);
3571
+ const [selectedTemplateId, setSelectedTemplateId] = useState3(null);
3572
+ const {
3573
+ selectedTag,
3574
+ setSelectedTag,
3575
+ search,
3576
+ setSearch,
3577
+ source,
3578
+ setSource,
3579
+ showAllTags,
3580
+ setShowAllTags,
3581
+ registryConfigured,
3582
+ availableSources,
3583
+ localTemplates,
3584
+ localTemplateById,
3585
+ registryTemplates,
3586
+ registryLoading,
3587
+ localFilterState,
3588
+ registryFilterState,
3589
+ visibleTagFacets,
3590
+ hiddenTagFacets,
3591
+ showTagFilters
3592
+ } = useTemplateBrowseState();
3593
+ return /* @__PURE__ */ jsxDEV20(Fragment2, {
3399
3594
  children: [
3400
- /* @__PURE__ */ jsxDEV19("main", {
3595
+ /* @__PURE__ */ jsxDEV20("main", {
3401
3596
  children: [
3402
- /* @__PURE__ */ jsxDEV19(TemplatesHeroSection, {
3597
+ /* @__PURE__ */ jsxDEV20(TemplatesHeroSection, {
3403
3598
  localTemplateCount: localTemplates.length,
3404
3599
  sourceCount: availableSources.length
3405
3600
  }, undefined, false, undefined, this),
3406
- /* @__PURE__ */ jsxDEV19(TemplatesBrowseControls, {
3601
+ /* @__PURE__ */ jsxDEV20(TemplatesBrowseControls, {
3407
3602
  registryConfigured,
3408
3603
  availableSources,
3409
3604
  source,
@@ -3412,14 +3607,19 @@ var TemplatesPage = () => {
3412
3607
  onSearchChange: setSearch,
3413
3608
  selectedTag,
3414
3609
  onTagChange: setSelectedTag,
3415
- availableTags
3610
+ showTagFilters,
3611
+ visibleTagFacets,
3612
+ hiddenTagFacets,
3613
+ showAllTags,
3614
+ onShowAllTagsChange: setShowAllTags
3416
3615
  }, undefined, false, undefined, this),
3417
- /* @__PURE__ */ jsxDEV19(TemplatesCatalogSection, {
3616
+ /* @__PURE__ */ jsxDEV20(TemplatesCatalogSection, {
3418
3617
  source,
3419
3618
  registryConfigured,
3420
3619
  registryLoading,
3421
- localTemplates: filteredLocalTemplates,
3422
- registryTemplates: filteredRegistryTemplates,
3620
+ registryHasTemplates: registryTemplates.length > 0,
3621
+ localTemplates: localFilterState.finalTemplates,
3622
+ registryTemplates: registryFilterState.finalTemplates,
3423
3623
  localTemplateById,
3424
3624
  onPreview: setPreviewTemplateId,
3425
3625
  onUseTemplate: (templateId, templateSource) => {
@@ -3429,40 +3629,20 @@ var TemplatesPage = () => {
3429
3629
  source: templateSource
3430
3630
  });
3431
3631
  setSelectedTemplateId(templateId);
3432
- }
3632
+ },
3633
+ hasSearch: search.trim().length > 0,
3634
+ selectedTag
3433
3635
  }, undefined, false, undefined, this),
3434
- /* @__PURE__ */ jsxDEV19(TemplatesNextStepsSection, {}, undefined, false, undefined, this)
3636
+ /* @__PURE__ */ jsxDEV20(TemplatesNextStepsSection, {}, undefined, false, undefined, this)
3435
3637
  ]
3436
3638
  }, undefined, true, undefined, this),
3437
- previewTemplateId ? /* @__PURE__ */ jsxDEV19(TemplatePreviewModal, {
3438
- templateId: previewTemplateId,
3439
- onClose: () => setPreviewTemplateId(null)
3440
- }, undefined, false, undefined, this) : null,
3441
- /* @__PURE__ */ jsxDEV19(Dialog4, {
3442
- open: studioSignupModalOpen,
3443
- onOpenChange: setStudioSignupModalOpen,
3444
- children: /* @__PURE__ */ jsxDEV19(DialogContent4, {
3445
- className: "max-h-[90vh] max-w-2xl overflow-y-auto",
3446
- children: [
3447
- /* @__PURE__ */ jsxDEV19(DialogHeader3, {
3448
- children: [
3449
- /* @__PURE__ */ jsxDEV19(DialogTitle3, {
3450
- children: "Deploy in Studio"
3451
- }, undefined, false, undefined, this),
3452
- /* @__PURE__ */ jsxDEV19(DialogDescription3, {
3453
- children: "Deploy templates in ContractSpec Studio and run the full evidence-to-spec loop with your team."
3454
- }, undefined, false, undefined, this)
3455
- ]
3456
- }, undefined, true, undefined, this),
3457
- /* @__PURE__ */ jsxDEV19(StudioSignupSection, {
3458
- variant: "compact"
3459
- }, undefined, false, undefined, this)
3460
- ]
3461
- }, undefined, true, undefined, this)
3462
- }, undefined, false, undefined, this),
3463
- /* @__PURE__ */ jsxDEV19(TemplateCommandDialog, {
3464
- templateId: selectedTemplateId,
3465
- onClose: () => setSelectedTemplateId(null),
3639
+ /* @__PURE__ */ jsxDEV20(TemplatesOverlays, {
3640
+ previewTemplateId,
3641
+ onPreviewClose: () => setPreviewTemplateId(null),
3642
+ studioSignupModalOpen,
3643
+ onStudioSignupModalOpenChange: setStudioSignupModalOpen,
3644
+ selectedTemplateId,
3645
+ onTemplateCommandClose: () => setSelectedTemplateId(null),
3466
3646
  onDeployStudio: () => {
3467
3647
  setSelectedTemplateId(null);
3468
3648
  setStudioSignupModalOpen(true);