@contractspec/bundle.marketing 3.8.8 → 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 (96) hide show
  1. package/.turbo/turbo-build.log +73 -29
  2. package/CHANGELOG.md +63 -0
  3. package/dist/browser/components/templates/TemplateCard.js +83 -0
  4. package/dist/browser/components/templates/TemplateCommandDialog.js +110 -0
  5. package/dist/browser/components/templates/TemplatePreviewContent.js +96 -0
  6. package/dist/browser/components/templates/TemplatesBrowseControls.js +130 -0
  7. package/dist/browser/components/templates/TemplatesCatalogSection.js +307 -0
  8. package/dist/browser/components/templates/TemplatesClientPage.js +1020 -917
  9. package/dist/browser/components/templates/TemplatesHeroSection.js +87 -0
  10. package/dist/browser/components/templates/TemplatesNextStepsSection.js +126 -0
  11. package/dist/browser/components/templates/TemplatesOverlays.js +2874 -0
  12. package/dist/browser/components/templates/TemplatesPreviewModal.js +136 -126
  13. package/dist/browser/components/templates/index.js +1055 -952
  14. package/dist/browser/components/templates/template-catalog.js +83 -0
  15. package/dist/browser/components/templates/template-filters.js +99 -0
  16. package/dist/browser/components/templates/template-new.js +23 -0
  17. package/dist/browser/components/templates/template-preview.js +43 -0
  18. package/dist/browser/components/templates/template-source.js +19 -0
  19. package/dist/browser/components/templates/template-tag-visibility.js +40 -0
  20. package/dist/browser/components/templates/useTemplateBrowseState.js +191 -0
  21. package/dist/browser/index.js +1055 -952
  22. package/dist/components/templates/TemplateCard.d.ts +12 -0
  23. package/dist/components/templates/TemplateCard.js +78 -0
  24. package/dist/components/templates/TemplateCommandDialog.d.ts +6 -0
  25. package/dist/components/templates/TemplateCommandDialog.js +105 -0
  26. package/dist/components/templates/TemplatePreviewContent.d.ts +5 -0
  27. package/dist/components/templates/TemplatePreviewContent.js +91 -0
  28. package/dist/components/templates/TemplatesBrowseControls.d.ts +18 -0
  29. package/dist/components/templates/TemplatesBrowseControls.js +125 -0
  30. package/dist/components/templates/TemplatesCatalogSection.d.ts +17 -0
  31. package/dist/components/templates/TemplatesCatalogSection.js +302 -0
  32. package/dist/components/templates/TemplatesClientPage.js +1020 -917
  33. package/dist/components/templates/TemplatesHeroSection.d.ts +5 -0
  34. package/dist/components/templates/TemplatesHeroSection.js +82 -0
  35. package/dist/components/templates/TemplatesNextStepsSection.d.ts +1 -0
  36. package/dist/components/templates/TemplatesNextStepsSection.js +121 -0
  37. package/dist/components/templates/TemplatesOverlays.d.ts +10 -0
  38. package/dist/components/templates/TemplatesOverlays.js +2869 -0
  39. package/dist/components/templates/TemplatesPreviewModal.d.ts +3 -4
  40. package/dist/components/templates/TemplatesPreviewModal.js +136 -126
  41. package/dist/components/templates/index.js +1055 -952
  42. package/dist/components/templates/template-catalog.d.ts +28 -0
  43. package/dist/components/templates/template-catalog.js +78 -0
  44. package/dist/components/templates/template-catalog.test.d.ts +1 -0
  45. package/dist/components/templates/template-filters.d.ts +12 -0
  46. package/dist/components/templates/template-filters.js +94 -0
  47. package/dist/components/templates/template-new.d.ts +2 -0
  48. package/dist/components/templates/template-new.js +18 -0
  49. package/dist/components/templates/template-preview.d.ts +18 -0
  50. package/dist/components/templates/template-preview.js +38 -0
  51. package/dist/components/templates/template-source.d.ts +3 -0
  52. package/dist/components/templates/template-source.js +14 -0
  53. package/dist/components/templates/template-tag-visibility.d.ts +10 -0
  54. package/dist/components/templates/template-tag-visibility.js +35 -0
  55. package/dist/components/templates/useTemplateBrowseState.d.ts +22 -0
  56. package/dist/components/templates/useTemplateBrowseState.js +186 -0
  57. package/dist/index.js +1055 -952
  58. package/dist/node/components/templates/TemplateCard.js +78 -0
  59. package/dist/node/components/templates/TemplateCommandDialog.js +105 -0
  60. package/dist/node/components/templates/TemplatePreviewContent.js +91 -0
  61. package/dist/node/components/templates/TemplatesBrowseControls.js +125 -0
  62. package/dist/node/components/templates/TemplatesCatalogSection.js +302 -0
  63. package/dist/node/components/templates/TemplatesClientPage.js +1020 -917
  64. package/dist/node/components/templates/TemplatesHeroSection.js +82 -0
  65. package/dist/node/components/templates/TemplatesNextStepsSection.js +121 -0
  66. package/dist/node/components/templates/TemplatesOverlays.js +2869 -0
  67. package/dist/node/components/templates/TemplatesPreviewModal.js +136 -126
  68. package/dist/node/components/templates/index.js +1055 -952
  69. package/dist/node/components/templates/template-catalog.js +78 -0
  70. package/dist/node/components/templates/template-filters.js +94 -0
  71. package/dist/node/components/templates/template-new.js +18 -0
  72. package/dist/node/components/templates/template-preview.js +38 -0
  73. package/dist/node/components/templates/template-source.js +14 -0
  74. package/dist/node/components/templates/template-tag-visibility.js +35 -0
  75. package/dist/node/components/templates/useTemplateBrowseState.js +186 -0
  76. package/dist/node/index.js +1055 -952
  77. package/package.json +237 -26
  78. package/src/components/templates/TemplateCard.tsx +74 -0
  79. package/src/components/templates/TemplateCommandDialog.tsx +92 -0
  80. package/src/components/templates/TemplatePreviewContent.tsx +182 -0
  81. package/src/components/templates/TemplatesBrowseControls.tsx +144 -0
  82. package/src/components/templates/TemplatesCatalogSection.tsx +191 -0
  83. package/src/components/templates/TemplatesClientPage.tsx +85 -773
  84. package/src/components/templates/TemplatesHeroSection.tsx +41 -0
  85. package/src/components/templates/TemplatesNextStepsSection.tsx +80 -0
  86. package/src/components/templates/TemplatesOverlays.tsx +65 -0
  87. package/src/components/templates/TemplatesPreviewModal.tsx +19 -294
  88. package/src/components/templates/template-catalog.test.ts +162 -0
  89. package/src/components/templates/template-catalog.ts +140 -0
  90. package/src/components/templates/template-filters.ts +57 -0
  91. package/src/components/templates/template-new.ts +12 -0
  92. package/src/components/templates/template-preview.ts +57 -0
  93. package/src/components/templates/template-source.ts +13 -0
  94. package/src/components/templates/template-tag-visibility.ts +58 -0
  95. package/src/components/templates/useTemplateBrowseState.ts +101 -0
  96. package/.turbo/turbo-prebuild.log +0 -1
@@ -0,0 +1,41 @@
1
+ export interface TemplatesHeroSectionProps {
2
+ localTemplateCount: number;
3
+ sourceCount: number;
4
+ }
5
+
6
+ export function TemplatesHeroSection({
7
+ localTemplateCount,
8
+ sourceCount,
9
+ }: TemplatesHeroSectionProps) {
10
+ return (
11
+ <section className="section-padding hero-gradient border-border/70 border-b">
12
+ <div className="editorial-shell space-y-8">
13
+ <div className="max-w-4xl space-y-5">
14
+ <p className="editorial-kicker">Proof through real scenarios</p>
15
+ <h1 className="editorial-title">
16
+ Templates that show the open system in practice.
17
+ </h1>
18
+ <p className="editorial-subtitle">
19
+ These scenarios are the fastest way to understand ContractSpec:
20
+ explicit contracts, aligned surfaces, and an adoption path from OSS
21
+ exploration into Studio deployment.
22
+ </p>
23
+ </div>
24
+ <div className="editorial-proof-strip">
25
+ <div className="editorial-stat">
26
+ <span className="editorial-stat-value">{localTemplateCount}</span>
27
+ <span className="editorial-label">curated scenarios</span>
28
+ </div>
29
+ <div className="editorial-stat">
30
+ <span className="editorial-stat-value">{sourceCount}</span>
31
+ <span className="editorial-label">entry paths</span>
32
+ </div>
33
+ <div className="editorial-stat">
34
+ <span className="editorial-stat-value">OSS</span>
35
+ <span className="editorial-label">first, Studio second</span>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </section>
40
+ );
41
+ }
@@ -0,0 +1,80 @@
1
+ import Link from 'next/link';
2
+
3
+ export function TemplatesNextStepsSection() {
4
+ return (
5
+ <section className="editorial-section bg-striped">
6
+ <div className="editorial-shell space-y-8">
7
+ <div className="max-w-3xl space-y-4">
8
+ <p className="editorial-kicker">From template to real system</p>
9
+ <h2 className="font-serif text-4xl tracking-[-0.04em] md:text-5xl">
10
+ Templates become useful when the system can absorb more context.
11
+ </h2>
12
+ <p className="editorial-copy">
13
+ Use templates to prove the base flow, then layer integrations,
14
+ knowledge, and runtime behavior on top without losing the same
15
+ contract source.
16
+ </p>
17
+ </div>
18
+ <div className="grid gap-6 md:grid-cols-3">
19
+ <div className="editorial-panel space-y-4">
20
+ <div className="text-3xl">💳</div>
21
+ <h3 className="font-serif text-2xl tracking-[-0.03em]">
22
+ Add payments
23
+ </h3>
24
+ <p className="text-muted-foreground text-sm">
25
+ Connect Stripe to any template for payment processing,
26
+ subscriptions, and invoicing. Type-safe and policy-enforced.
27
+ </p>
28
+ <Link
29
+ href="/docs/integrations/stripe"
30
+ className="font-medium text-[color:var(--blue)] text-sm hover:opacity-80"
31
+ >
32
+ Learn more →
33
+ </Link>
34
+ </div>
35
+ <div className="editorial-panel space-y-4">
36
+ <div className="text-3xl">📧</div>
37
+ <h3 className="font-serif text-2xl tracking-[-0.03em]">
38
+ Add notifications
39
+ </h3>
40
+ <p className="text-muted-foreground text-sm">
41
+ Send transactional emails via Postmark or Resend. Process inbound
42
+ emails with Gmail API. SMS via Twilio.
43
+ </p>
44
+ <Link
45
+ href="/docs/integrations"
46
+ className="font-medium text-[color:var(--blue)] text-sm hover:opacity-80"
47
+ >
48
+ View integrations →
49
+ </Link>
50
+ </div>
51
+ <div className="editorial-panel space-y-4">
52
+ <div className="text-3xl">🧠</div>
53
+ <h3 className="font-serif text-2xl tracking-[-0.03em]">
54
+ Add AI and knowledge
55
+ </h3>
56
+ <p className="text-muted-foreground text-sm">
57
+ Power templates with OpenAI, vector search via Qdrant, and
58
+ structured knowledge spaces for context-aware workflows.
59
+ </p>
60
+ <Link
61
+ href="/docs/knowledge"
62
+ className="font-medium text-[color:var(--blue)] text-sm hover:opacity-80"
63
+ >
64
+ Learn about knowledge →
65
+ </Link>
66
+ </div>
67
+ </div>
68
+ <div className="pt-4 text-center">
69
+ <p className="mb-4 text-muted-foreground text-sm">
70
+ All integrations are configured per-tenant with automatic health
71
+ checks and credential rotation.
72
+ </p>
73
+ <Link href="/docs/architecture" className="btn-primary">
74
+ View Architecture
75
+ </Link>
76
+ </div>
77
+ </div>
78
+ </section>
79
+ );
80
+ }
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ } from '@contractspec/lib.ui-kit-web/ui/dialog';
10
+ import { StudioSignupSection } from '../marketing';
11
+ import { TemplateCommandDialog } from './TemplateCommandDialog';
12
+ import { TemplatePreviewModal } from './TemplatesPreviewModal';
13
+
14
+ export interface TemplatesOverlaysProps {
15
+ previewTemplateId: string | null;
16
+ onPreviewClose: () => void;
17
+ studioSignupModalOpen: boolean;
18
+ onStudioSignupModalOpenChange: (open: boolean) => void;
19
+ selectedTemplateId: string | null;
20
+ onTemplateCommandClose: () => void;
21
+ onDeployStudio: () => void;
22
+ }
23
+
24
+ export function TemplatesOverlays({
25
+ previewTemplateId,
26
+ onPreviewClose,
27
+ studioSignupModalOpen,
28
+ onStudioSignupModalOpenChange,
29
+ selectedTemplateId,
30
+ onTemplateCommandClose,
31
+ onDeployStudio,
32
+ }: TemplatesOverlaysProps) {
33
+ return (
34
+ <>
35
+ {previewTemplateId ? (
36
+ <TemplatePreviewModal
37
+ templateId={previewTemplateId}
38
+ onClose={onPreviewClose}
39
+ />
40
+ ) : null}
41
+
42
+ <Dialog
43
+ open={studioSignupModalOpen}
44
+ onOpenChange={onStudioSignupModalOpenChange}
45
+ >
46
+ <DialogContent className="max-h-[90vh] max-w-2xl overflow-y-auto">
47
+ <DialogHeader>
48
+ <DialogTitle>Deploy in Studio</DialogTitle>
49
+ <DialogDescription>
50
+ Deploy templates in ContractSpec Studio and run the full
51
+ evidence-to-spec loop with your team.
52
+ </DialogDescription>
53
+ </DialogHeader>
54
+ <StudioSignupSection variant="compact" />
55
+ </DialogContent>
56
+ </Dialog>
57
+
58
+ <TemplateCommandDialog
59
+ templateId={selectedTemplateId}
60
+ onClose={onTemplateCommandClose}
61
+ onDeployStudio={onDeployStudio}
62
+ />
63
+ </>
64
+ );
65
+ }
@@ -1,313 +1,38 @@
1
1
  'use client';
2
2
 
3
3
  import type { TemplateId } from '@contractspec/lib.example-shared-ui';
4
- import { LoadingSpinner } from '@contractspec/lib.ui-kit-web/ui/atoms/LoadingSpinner';
5
4
  import { Dialog, DialogContent } from '@contractspec/lib.ui-kit-web/ui/dialog';
6
5
  import { ScrollArea } from '@contractspec/lib.ui-kit-web/ui/scroll-area';
7
- import dynamic from 'next/dynamic';
8
- import { useMemo } from 'react';
6
+ import { TemplateRuntimeProvider } from '@contractspec/module.examples';
7
+ import { TemplatePreviewContent } from './TemplatePreviewContent';
8
+ import { supportsInlineTemplatePreview } from './template-preview';
9
9
 
10
- // Dynamically import template components with ssr: false
11
- const TemplateShell = dynamic(
12
- () =>
13
- import('@contractspec/lib.example-shared-ui').then(
14
- (mod) => mod.TemplateShell
15
- ),
16
- { ssr: false, loading: () => <LoadingSpinner /> }
17
- );
18
-
19
- const TodosTaskList = dynamic(
20
- () =>
21
- import(
22
- '@contractspec/bundle.library/components/templates/todos/TaskList'
23
- ).then((mod) => mod.TaskList),
24
- { ssr: false, loading: () => <LoadingSpinner /> }
25
- );
26
-
27
- const MessagingWorkspace = dynamic(
28
- () =>
29
- import(
30
- '@contractspec/bundle.library/components/templates/messaging/MessagingWorkspace'
31
- ).then((mod) => mod.MessagingWorkspace),
32
- { ssr: false, loading: () => <LoadingSpinner /> }
33
- );
34
-
35
- const RecipesExperience = dynamic(
36
- () =>
37
- import(
38
- '@contractspec/bundle.library/components/templates/recipes/RecipeList'
39
- ).then((mod) => mod.RecipeList),
40
- { ssr: false, loading: () => <LoadingSpinner /> }
41
- );
42
-
43
- const SaasDashboard = dynamic(
44
- () =>
45
- import('@contractspec/example.saas-boilerplate').then(
46
- (mod) => mod.SaasDashboard
47
- ),
48
- { ssr: false, loading: () => <LoadingSpinner /> }
49
- );
50
-
51
- const CrmDashboard = dynamic(
52
- () =>
53
- import('@contractspec/example.crm-pipeline').then(
54
- (mod) => mod.CrmDashboard
55
- ),
56
- { ssr: false, loading: () => <LoadingSpinner /> }
57
- );
58
-
59
- const DataGridShowcase = dynamic(
60
- () =>
61
- import('@contractspec/example.data-grid-showcase/ui').then(
62
- (mod) => mod.DataGridShowcase
63
- ),
64
- { ssr: false, loading: () => <LoadingSpinner /> }
65
- );
66
-
67
- const VisualizationShowcase = dynamic(
68
- () =>
69
- import('@contractspec/example.visualization-showcase/ui').then(
70
- (mod) => mod.VisualizationShowcase
71
- ),
72
- { ssr: false, loading: () => <LoadingSpinner /> }
73
- );
74
-
75
- const AgentDashboard = dynamic(
76
- () =>
77
- import('@contractspec/example.agent-console/ui').then(
78
- (mod) => mod.AgentDashboard
79
- ),
80
- { ssr: false, loading: () => <LoadingSpinner /> }
81
- );
82
-
83
- const WorkflowDashboard = dynamic(
84
- () =>
85
- import('@contractspec/example.workflow-system/ui').then(
86
- (mod) => mod.WorkflowDashboard
87
- ),
88
- { ssr: false, loading: () => <LoadingSpinner /> }
89
- );
90
-
91
- const MarketplaceDashboard = dynamic(
92
- () =>
93
- import('@contractspec/example.marketplace/ui').then(
94
- (mod) => mod.MarketplaceDashboard
95
- ),
96
- { ssr: false, loading: () => <LoadingSpinner /> }
97
- );
98
-
99
- const IntegrationDashboard = dynamic(
100
- () =>
101
- import('@contractspec/example.integration-hub/ui').then(
102
- (mod) => mod.IntegrationDashboard
103
- ),
104
- { ssr: false, loading: () => <LoadingSpinner /> }
105
- );
106
-
107
- const AnalyticsDashboard = dynamic(
108
- () =>
109
- import('@contractspec/example.analytics-dashboard').then(
110
- (mod) => mod.AnalyticsDashboard
111
- ),
112
- { ssr: false, loading: () => <LoadingSpinner /> }
113
- );
114
-
115
- const AiChatAssistantDashboard = dynamic(
116
- () =>
117
- import('@contractspec/example.ai-chat-assistant').then(
118
- (mod) => mod.AiChatAssistantDashboard
119
- ),
120
- { ssr: false, loading: () => <LoadingSpinner /> }
121
- );
122
-
123
- interface TemplatePreviewModalProps {
124
- templateId: TemplateId | null;
10
+ export interface TemplatePreviewModalProps {
11
+ templateId: TemplateId;
125
12
  onClose: () => void;
126
13
  }
127
- //
128
- // return (
129
- // <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 px-4 py-10">
130
- // <div className="bg-background relative h-full max-h-[90vh] w-full max-w-5xl overflow-y-auto rounded-3xl p-6 shadow-2xl">
131
- // <button
132
- // type="button"
133
- // className="btn-ghost absolute top-4 right-4 text-sm"
134
- // onClick={onClose}
135
- // >
136
- // Close
137
- // </button>
138
- // {previewComponent}
139
- // </div>
140
- // </div>
141
- // );
142
- // };
143
14
 
144
- export const TemplatePreviewModal = ({
15
+ export function TemplatePreviewModal({
145
16
  templateId,
146
17
  onClose,
147
- }: TemplatePreviewModalProps) => {
148
- const previewComponent = useMemo(() => {
149
- switch (templateId) {
150
- case 'todos-app':
151
- return (
152
- <TemplateShell
153
- title="Starter tasks"
154
- description="Track work items with filters, priorities, and per-tenant data isolation."
155
- showSaveAction={false}
156
- >
157
- <TodosTaskList />
158
- </TemplateShell>
159
- );
160
- case 'messaging-app':
161
- return (
162
- <TemplateShell
163
- title="Messaging workspace"
164
- description="Realtime-ready messaging surface with optimistic delivery."
165
- showSaveAction={false}
166
- >
167
- <MessagingWorkspace />
168
- </TemplateShell>
169
- );
170
- case 'recipe-app-i18n':
171
- return (
172
- <TemplateShell
173
- title="Ceremony recipes"
174
- description="Switch locales and preview how rituals translate across teams."
175
- showSaveAction={false}
176
- >
177
- <RecipesExperience />
178
- </TemplateShell>
179
- );
180
- case 'saas-boilerplate':
181
- return (
182
- <TemplateShell
183
- title="SaaS Boilerplate"
184
- description="Multi-tenant organizations, projects, settings, and billing usage tracking."
185
- showSaveAction={false}
186
- >
187
- <SaasDashboard />
188
- </TemplateShell>
189
- );
190
- case 'crm-pipeline':
191
- return (
192
- <TemplateShell
193
- title="CRM Pipeline"
194
- description="Sales CRM with contacts, companies, deals, and pipeline stages."
195
- showSaveAction={false}
196
- >
197
- <CrmDashboard />
198
- </TemplateShell>
199
- );
200
- case 'data-grid-showcase':
201
- return (
202
- <TemplateShell
203
- title="Data Grid Showcase"
204
- description="Shared ContractSpec table primitives with client, server, and DataView-driven lanes."
205
- showSaveAction={false}
206
- >
207
- <DataGridShowcase />
208
- </TemplateShell>
209
- );
210
- case 'visualization-showcase':
211
- return (
212
- <TemplateShell
213
- title="Visualization Showcase"
214
- description="ContractSpec-owned chart primitives rendered through shared visualization contracts and design-system wrappers."
215
- showSaveAction={false}
216
- >
217
- <VisualizationShowcase />
218
- </TemplateShell>
219
- );
220
- case 'agent-console':
221
- return (
222
- <TemplateShell
223
- title="AI Agent Console"
224
- description="AI agent orchestration with tools, agents, runs, and execution logs."
225
- showSaveAction={false}
226
- >
227
- <AgentDashboard />
228
- </TemplateShell>
229
- );
230
- case 'ai-chat-assistant':
231
- return (
232
- <TemplateShell
233
- title="AI Chat Assistant"
234
- description="Focused assistant surface with reasoning, sources, suggestions, and MCP-aware tools."
235
- showSaveAction={false}
236
- >
237
- <AiChatAssistantDashboard />
238
- </TemplateShell>
239
- );
240
- case 'workflow-system':
241
- return (
242
- <TemplateShell
243
- title="Workflow System"
244
- description="Multi-step workflows with role-based approvals."
245
- showSaveAction={false}
246
- >
247
- <WorkflowDashboard />
248
- </TemplateShell>
249
- );
250
- case 'marketplace':
251
- return (
252
- <TemplateShell
253
- title="Marketplace"
254
- description="Two-sided marketplace with stores, products, and orders."
255
- showSaveAction={false}
256
- >
257
- <MarketplaceDashboard />
258
- </TemplateShell>
259
- );
260
- case 'integration-hub':
261
- return (
262
- <TemplateShell
263
- title="Integration Hub"
264
- description="Third-party integrations with sync and field mapping."
265
- showSaveAction={false}
266
- >
267
- <IntegrationDashboard />
268
- </TemplateShell>
269
- );
270
- case 'analytics-dashboard':
271
- return (
272
- <TemplateShell
273
- title="Analytics Dashboard"
274
- description="Custom dashboards with widgets and queries."
275
- showSaveAction={false}
276
- >
277
- <AnalyticsDashboard />
278
- </TemplateShell>
279
- );
280
- case null:
281
- return null;
282
- default:
283
- return null;
284
- }
285
- }, [templateId]);
18
+ }: TemplatePreviewModalProps) {
19
+ if (!supportsInlineTemplatePreview(templateId)) {
20
+ return null;
21
+ }
286
22
 
287
23
  return (
288
- <Dialog open={!!previewComponent} onOpenChange={onClose}>
289
- {/*<DialogTrigger asChild>*/}
290
- {/* <Button variant="outline">Fullscreen Dialog</Button>*/}
291
- {/*</DialogTrigger>*/}
24
+ <Dialog open onOpenChange={(open) => !open && onClose()}>
292
25
  <DialogContent className="mb-8 flex h-[calc(100vh-2rem)] min-w-[calc(100vw-2rem)] flex-col justify-between gap-0 p-0">
293
26
  <ScrollArea className="flex flex-col justify-between overflow-hidden">
294
- {/*<DialogHeader className="contents space-y-0 text-left">*/}
295
- {/* <DialogTitle className="px-6 pt-6">Product Information</DialogTitle>*/}
296
- {/* <DialogDescription asChild>*/}
297
- {/* </DialogDescription>*/}
298
- {/*</DialogHeader>*/}
299
- {previewComponent}
27
+ <TemplateRuntimeProvider
28
+ key={templateId}
29
+ templateId={templateId}
30
+ projectId={`marketing-preview-${templateId}`}
31
+ >
32
+ <TemplatePreviewContent templateId={templateId} />
33
+ </TemplateRuntimeProvider>
300
34
  </ScrollArea>
301
- {/*<DialogFooter className="px-6 pb-6 sm:justify-end">*/}
302
- {/* <DialogClose asChild>*/}
303
- {/* <Button variant="outline">*/}
304
- {/* <ChevronLeftIcon />*/}
305
- {/* Back*/}
306
- {/* </Button>*/}
307
- {/* </DialogClose>*/}
308
- {/* <Button type="button">Read More</Button>*/}
309
- {/*</DialogFooter>*/}
310
35
  </DialogContent>
311
36
  </Dialog>
312
37
  );
313
- };
38
+ }
@@ -0,0 +1,162 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { listExamples, listTemplates } from '@contractspec/module.examples';
3
+ import {
4
+ buildLocalTemplateCatalog,
5
+ matchesTemplateFilters,
6
+ } from './template-catalog';
7
+ import { buildTemplateFilterState } from './template-filters';
8
+ import { NEW_TEMPLATE_IDS } from './template-new';
9
+ import {
10
+ getAvailableTemplateSources,
11
+ isRegistryConfigured,
12
+ } from './template-source';
13
+ import {
14
+ DEFAULT_VISIBLE_TEMPLATE_TAGS,
15
+ getVisibleTemplateTagFacets,
16
+ } from './template-tag-visibility';
17
+
18
+ describe('template catalog', () => {
19
+ test('includes every public example exposed as a template', () => {
20
+ const catalog = buildLocalTemplateCatalog(listExamples(), listTemplates());
21
+ const actualIds = [...catalog].map((template) => template.id).sort();
22
+ const expectedIds = listExamples()
23
+ .filter(
24
+ (example) =>
25
+ example.meta.visibility === 'public' && example.surfaces.templates
26
+ )
27
+ .map((example) => example.meta.key)
28
+ .sort();
29
+
30
+ expect(actualIds).toEqual(expectedIds);
31
+ });
32
+
33
+ test('drops legacy conceptual cards and applies curated new badges', () => {
34
+ const catalog = buildLocalTemplateCatalog(listExamples(), listTemplates());
35
+ const ids = new Set(catalog.map((template) => template.id));
36
+ const newIds = catalog
37
+ .filter((template) => template.isNew)
38
+ .map((template) => template.id)
39
+ .sort();
40
+
41
+ expect(ids.has('plumber-ops')).toBe(false);
42
+ expect(ids.has('coliving-management')).toBe(false);
43
+ expect(ids.has('content-review')).toBe(false);
44
+ expect(newIds).toEqual([...NEW_TEMPLATE_IDS].sort());
45
+ });
46
+
47
+ test('derives searchable tags from real example metadata', () => {
48
+ const catalog = buildLocalTemplateCatalog(listExamples(), listTemplates());
49
+ const tags = new Set(catalog.flatMap((template) => template.tags));
50
+
51
+ expect(tags.has('quickstart')).toBe(true);
52
+ expect(tags.has('gradium')).toBe(true);
53
+ expect(
54
+ catalog.some((template) =>
55
+ matchesTemplateFilters(template, 'voice gradium', null)
56
+ )
57
+ ).toBe(true);
58
+ });
59
+
60
+ test('derives tag facets from the templates remaining after search', () => {
61
+ const catalog = buildLocalTemplateCatalog(listExamples(), listTemplates());
62
+ const state = buildTemplateFilterState(
63
+ catalog,
64
+ 'agent',
65
+ null,
66
+ (template) => template
67
+ );
68
+ const tags = state.tagFacets.map((facet) => facet.tag);
69
+
70
+ expect(state.searchScopedTemplates.length).toBeGreaterThan(0);
71
+ expect(tags).toContain('agents');
72
+ expect(tags).not.toContain('billing');
73
+ });
74
+
75
+ test('applies selected tags after search scoping', () => {
76
+ const catalog = buildLocalTemplateCatalog(listExamples(), listTemplates());
77
+ const state = buildTemplateFilterState(
78
+ catalog,
79
+ 'agent',
80
+ 'telegram',
81
+ (template) => template
82
+ );
83
+
84
+ expect(state.searchScopedTemplates.length).toBeGreaterThan(
85
+ state.finalTemplates.length
86
+ );
87
+ expect(
88
+ state.finalTemplates.every((template) =>
89
+ template.tags.includes('telegram')
90
+ )
91
+ ).toBe(true);
92
+ });
93
+
94
+ test('caps default tag visibility and keeps selected hidden tags visible', () => {
95
+ const tagFacets = Array.from(
96
+ { length: DEFAULT_VISIBLE_TEMPLATE_TAGS + 2 },
97
+ (_, index) => ({
98
+ tag: `tag-${index}`,
99
+ count: DEFAULT_VISIBLE_TEMPLATE_TAGS + 2 - index,
100
+ })
101
+ );
102
+ const { visibleTagFacets, hiddenTagFacets } = getVisibleTemplateTagFacets(
103
+ tagFacets,
104
+ 'tag-11',
105
+ false
106
+ );
107
+
108
+ expect(visibleTagFacets).toHaveLength(DEFAULT_VISIBLE_TEMPLATE_TAGS + 1);
109
+ expect(visibleTagFacets.some((facet) => facet.tag === 'tag-11')).toBe(true);
110
+ expect(hiddenTagFacets.some((facet) => facet.tag === 'tag-11')).toBe(false);
111
+ });
112
+
113
+ test('recomputes source-specific tags from the active source only', () => {
114
+ const localTemplates = [
115
+ {
116
+ title: 'Local agent console',
117
+ description: 'Agent workflows',
118
+ tags: ['agents', 'local'],
119
+ },
120
+ ];
121
+ const registryTemplates = [
122
+ {
123
+ title: 'Registry recipe app',
124
+ description: 'Cooking workflows',
125
+ tags: ['recipes', 'community'],
126
+ },
127
+ ];
128
+ const localState = buildTemplateFilterState(
129
+ localTemplates,
130
+ '',
131
+ null,
132
+ (template) => template
133
+ );
134
+ const registryState = buildTemplateFilterState(
135
+ registryTemplates,
136
+ '',
137
+ null,
138
+ (template) => template
139
+ );
140
+
141
+ expect(localState.tagFacets.map((facet) => facet.tag)).toEqual([
142
+ 'agents',
143
+ 'local',
144
+ ]);
145
+ expect(registryState.tagFacets.map((facet) => facet.tag)).toEqual([
146
+ 'community',
147
+ 'recipes',
148
+ ]);
149
+ });
150
+ });
151
+
152
+ describe('template source configuration', () => {
153
+ test('only exposes community source when a registry url is configured', () => {
154
+ expect(isRegistryConfigured(undefined)).toBe(false);
155
+ expect(isRegistryConfigured(' ')).toBe(false);
156
+ expect(isRegistryConfigured('https://registry.contractspec.io')).toBe(true);
157
+ expect(getAvailableTemplateSources(undefined)).toEqual(['local']);
158
+ expect(
159
+ getAvailableTemplateSources('https://registry.contractspec.io')
160
+ ).toEqual(['local', 'registry']);
161
+ });
162
+ });