@utopia-studio-design/design-system-cli 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +23 -0
  2. package/bin/utopia-ds-mcp.mjs +9 -2
  3. package/bin/utopia-ds.mjs +106 -8
  4. package/data/blocks/activity-feed/ActivityFeedBlock.tsx +255 -0
  5. package/data/blocks/activity-feed/README.md +16 -0
  6. package/data/blocks/activity-feed/activity-feed-contract.ts +96 -0
  7. package/data/blocks/activity-feed/activity-feed-fixture.ts +22 -0
  8. package/data/blocks/activity-feed/activity-feed.css +154 -0
  9. package/data/blocks/agent-status/AgentStatusBlock.tsx +313 -0
  10. package/data/blocks/agent-status/README.md +15 -0
  11. package/data/blocks/agent-status/agent-status-contract.ts +133 -0
  12. package/data/blocks/agent-status/agent-status-fixture.ts +79 -0
  13. package/data/blocks/agent-status/agent-status.css +216 -0
  14. package/data/blocks/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
  15. package/data/blocks/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  16. package/data/blocks/analytics-dashboard/README.md +17 -0
  17. package/data/blocks/analytics-dashboard/analytics-dashboard.css +92 -0
  18. package/data/blocks/calendar-application/CalendarApplicationBlocks.tsx +519 -0
  19. package/data/blocks/calendar-application/README.md +20 -0
  20. package/data/blocks/calendar-application/calendar-application.css +216 -0
  21. package/data/blocks/calendar-application/calendar-localization.ts +90 -0
  22. package/data/blocks/calendar-application/calendar-panel-contract.ts +72 -0
  23. package/data/blocks/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
  24. package/data/blocks/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
  25. package/data/blocks/dashboard-composer/README.md +16 -0
  26. package/data/blocks/dashboard-composer/RegistryDataBlocks.tsx +238 -0
  27. package/data/blocks/dashboard-composer/dashboard-composer.css +328 -0
  28. package/data/blocks/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
  29. package/data/blocks/database-workspace/README.md +9 -0
  30. package/data/blocks/database-workspace/database-workspace.css +107 -0
  31. package/data/blocks/interaction-workbench/InteractionWorkbench.tsx +271 -0
  32. package/data/blocks/interaction-workbench/README.md +9 -0
  33. package/data/blocks/interaction-workbench/interaction-workbench.css +226 -0
  34. package/data/docs/calendar.md +164 -0
  35. package/data/docs/charts.md +95 -0
  36. package/data/docs/composition-registry.md +174 -0
  37. package/data/docs/dashboard-composer.md +94 -0
  38. package/data/docs/database.md +68 -0
  39. package/data/docs/examples/dashboard-layout.v1.json +65 -0
  40. package/data/docs/foundations.md +1 -1
  41. package/data/docs/interaction-foundation.md +109 -0
  42. package/data/docs/quick-start-ai.md +23 -1
  43. package/data/manifests/blocks.json +392 -0
  44. package/data/manifests/catalog.json +7 -4
  45. package/data/manifests/components.json +179 -0
  46. package/data/manifests/composition-registry.json +427 -0
  47. package/data/manifests/motion-profiles.json +1 -1
  48. package/data/manifests/patterns.json +113 -1
  49. package/data/manifests/templates.json +346 -0
  50. package/data/manifests/theme-renacore.json +142 -0
  51. package/data/manifests/theme-vyapti.json +125 -0
  52. package/data/manifests/themes.json +400 -28
  53. package/data/templates/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
  54. package/data/templates/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  55. package/data/templates/analytics-dashboard/README.md +6 -0
  56. package/data/templates/analytics-dashboard/analytics-dashboard.css +92 -0
  57. package/data/templates/analytics-dashboard/index.html +13 -0
  58. package/data/templates/analytics-dashboard/main.tsx +196 -0
  59. package/data/templates/analytics-dashboard/styles.css +120 -0
  60. package/data/templates/analytics-dashboard/template.manifest.json +17 -0
  61. package/data/templates/calendar-application/CalendarApplicationBlocks.tsx +519 -0
  62. package/data/templates/calendar-application/README.md +10 -0
  63. package/data/templates/calendar-application/calendar-application.css +216 -0
  64. package/data/templates/calendar-application/calendar-localization.ts +90 -0
  65. package/data/templates/calendar-application/index.html +14 -0
  66. package/data/templates/calendar-application/main.tsx +409 -0
  67. package/data/templates/calendar-application/styles.css +248 -0
  68. package/data/templates/calendar-application/template.manifest.json +27 -0
  69. package/data/templates/composition-registry-lab/README.md +9 -0
  70. package/data/templates/composition-registry-lab/index.html +14 -0
  71. package/data/templates/composition-registry-lab/main.tsx +217 -0
  72. package/data/templates/composition-registry-lab/sample-layout.json +63 -0
  73. package/data/templates/composition-registry-lab/styles.css +343 -0
  74. package/data/templates/composition-registry-lab/template.manifest.json +34 -0
  75. package/data/templates/dashboard-composer/ActivityFeedBlock.tsx +255 -0
  76. package/data/templates/dashboard-composer/AgentStatusBlock.tsx +313 -0
  77. package/data/templates/dashboard-composer/AnalyticsDashboardBlocks.tsx +107 -0
  78. package/data/templates/dashboard-composer/AnalyticsMetricBlocks.tsx +87 -0
  79. package/data/templates/dashboard-composer/CalendarApplicationBlocks.tsx +519 -0
  80. package/data/templates/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
  81. package/data/templates/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
  82. package/data/templates/dashboard-composer/DatabaseWorkspaceBlocks.tsx +240 -0
  83. package/data/templates/dashboard-composer/README.md +53 -0
  84. package/data/templates/dashboard-composer/RegistryDataBlocks.tsx +238 -0
  85. package/data/templates/dashboard-composer/activity-feed-contract.ts +96 -0
  86. package/data/templates/dashboard-composer/activity-feed-fixture.ts +22 -0
  87. package/data/templates/dashboard-composer/activity-feed.css +154 -0
  88. package/data/templates/dashboard-composer/agent-status-contract.ts +133 -0
  89. package/data/templates/dashboard-composer/agent-status-fixture.ts +79 -0
  90. package/data/templates/dashboard-composer/agent-status-lab/index.html +19 -0
  91. package/data/templates/dashboard-composer/agent-status-lab/main.tsx +151 -0
  92. package/data/templates/dashboard-composer/agent-status-lab/styles.css +139 -0
  93. package/data/templates/dashboard-composer/agent-status.css +216 -0
  94. package/data/templates/dashboard-composer/analytics-dashboard.css +92 -0
  95. package/data/templates/dashboard-composer/calendar-application.css +216 -0
  96. package/data/templates/dashboard-composer/calendar-localization.ts +90 -0
  97. package/data/templates/dashboard-composer/calendar-panel-contract.ts +72 -0
  98. package/data/templates/dashboard-composer/dashboard-composer.css +328 -0
  99. package/data/templates/dashboard-composer/database-workspace.css +107 -0
  100. package/data/templates/dashboard-composer/index.html +14 -0
  101. package/data/templates/dashboard-composer/main.tsx +550 -0
  102. package/data/templates/dashboard-composer/sample-layout.json +147 -0
  103. package/data/templates/dashboard-composer/styles.css +198 -0
  104. package/data/templates/dashboard-composer/template.manifest.json +34 -0
  105. package/data/templates/dashboard-composer/visual-editor/PuckEditorSurface.tsx +132 -0
  106. package/data/templates/dashboard-composer/visual-editor/VisualEditorApp.tsx +343 -0
  107. package/data/templates/dashboard-composer/visual-editor/index.html +17 -0
  108. package/data/templates/dashboard-composer/visual-editor/main.tsx +95 -0
  109. package/data/templates/dashboard-composer/visual-editor/styles.css +310 -0
  110. package/data/templates/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
  111. package/data/templates/database-workspace/README.md +5 -0
  112. package/data/templates/database-workspace/database-workspace.css +107 -0
  113. package/data/templates/database-workspace/index.html +14 -0
  114. package/data/templates/database-workspace/main.tsx +247 -0
  115. package/data/templates/database-workspace/styles.css +121 -0
  116. package/data/templates/database-workspace/template.manifest.json +19 -0
  117. package/data/templates/interaction-lab/InteractionWorkbench.tsx +271 -0
  118. package/data/templates/interaction-lab/README.md +7 -0
  119. package/data/templates/interaction-lab/index.html +14 -0
  120. package/data/templates/interaction-lab/interaction-workbench.css +226 -0
  121. package/data/templates/interaction-lab/main.tsx +104 -0
  122. package/data/templates/interaction-lab/styles.css +224 -0
  123. package/data/templates/interaction-lab/template.manifest.json +36 -0
  124. package/lib/api.mjs +72 -5
  125. package/lib/composition-runtime.mjs +382 -0
  126. package/package.json +4 -1
@@ -0,0 +1,154 @@
1
+ .ceramic-activity-feed {
2
+ display: grid;
3
+ align-content: start;
4
+ gap: var(--space-3);
5
+ min-inline-size: 0;
6
+ block-size: 100%;
7
+ padding: var(--space-4);
8
+ background: var(--surface);
9
+ color: var(--foreground);
10
+ text-align: start;
11
+ }
12
+
13
+ .ceramic-activity-feed > header,
14
+ .ceramic-activity-feed > header > div,
15
+ .ceramic-activity-items > li,
16
+ .ceramic-activity-copy > div,
17
+ .ceramic-activity-feed > footer {
18
+ display: flex;
19
+ align-items: center;
20
+ }
21
+
22
+ .ceramic-activity-feed > header {
23
+ justify-content: space-between;
24
+ gap: var(--space-3);
25
+ padding-block-end: var(--space-3);
26
+ border-block-end: var(--border-width-hairline) solid var(--border);
27
+ }
28
+
29
+ .ceramic-activity-feed > header > div {
30
+ align-items: start;
31
+ flex-direction: column;
32
+ gap: var(--space-1);
33
+ min-inline-size: 0;
34
+ }
35
+
36
+ .ceramic-activity-feed :is(h2, h3, p) {
37
+ margin: 0;
38
+ }
39
+
40
+ .ceramic-activity-feed > header span {
41
+ color: var(--muted-foreground);
42
+ font-size: var(--font-size-eyebrow);
43
+ font-weight: var(--font-weight-semibold);
44
+ letter-spacing: var(--tracking-eyebrow);
45
+ text-transform: uppercase;
46
+ }
47
+
48
+ .ceramic-activity-groups {
49
+ display: grid;
50
+ gap: var(--space-4);
51
+ }
52
+
53
+ .ceramic-activity-groups > section {
54
+ display: grid;
55
+ gap: var(--space-2);
56
+ min-inline-size: 0;
57
+ }
58
+
59
+ .ceramic-activity-groups h3 {
60
+ color: var(--muted-foreground);
61
+ font-size: var(--font-size-small);
62
+ font-weight: var(--font-weight-semibold);
63
+ }
64
+
65
+ .ceramic-activity-items {
66
+ display: grid;
67
+ gap: 0;
68
+ margin: 0;
69
+ padding: 0;
70
+ list-style: none;
71
+ }
72
+
73
+ .ceramic-activity-items > li {
74
+ position: relative;
75
+ align-items: start;
76
+ gap: var(--space-3);
77
+ min-inline-size: 0;
78
+ padding-block: var(--space-2);
79
+ border-block-end: var(--border-width-hairline) solid var(--border);
80
+ }
81
+
82
+ .ceramic-activity-feed[data-density='compact'] .ceramic-activity-items > li {
83
+ padding-block: var(--space-1);
84
+ }
85
+
86
+ .ceramic-activity-feed[data-density='comfortable'] .ceramic-activity-items > li {
87
+ padding-block: var(--space-3);
88
+ }
89
+
90
+ .ceramic-activity-marker {
91
+ flex: 0 0 auto;
92
+ margin-block-start: var(--space-1);
93
+ background: var(--muted-foreground);
94
+ }
95
+
96
+ .ceramic-activity-marker[data-tone='info'] {
97
+ background: var(--primary);
98
+ }
99
+
100
+ .ceramic-activity-marker[data-tone='positive'] {
101
+ background: var(--success);
102
+ }
103
+
104
+ .ceramic-activity-marker[data-tone='warning'] {
105
+ background: var(--warning);
106
+ }
107
+
108
+ .ceramic-activity-marker[data-tone='critical'] {
109
+ background: var(--destructive);
110
+ }
111
+
112
+ .ceramic-activity-copy {
113
+ display: grid;
114
+ gap: var(--space-1);
115
+ min-inline-size: 0;
116
+ }
117
+
118
+ .ceramic-activity-copy p {
119
+ overflow-wrap: anywhere;
120
+ line-height: var(--line-height-body);
121
+ }
122
+
123
+ .ceramic-activity-copy > div {
124
+ flex-wrap: wrap;
125
+ gap: var(--space-2);
126
+ color: var(--muted-foreground);
127
+ font-size: var(--font-size-small);
128
+ }
129
+
130
+ .ceramic-activity-state {
131
+ display: grid;
132
+ align-content: center;
133
+ justify-items: start;
134
+ gap: var(--space-2);
135
+ min-block-size: 10rem;
136
+ color: var(--muted-foreground);
137
+ }
138
+
139
+ .ceramic-activity-state strong {
140
+ color: var(--foreground);
141
+ }
142
+
143
+ .ceramic-activity-feed > footer {
144
+ justify-content: center;
145
+ min-block-size: var(--control-size-sm);
146
+ color: var(--muted-foreground);
147
+ font-size: var(--font-size-small);
148
+ }
149
+
150
+ @media (max-width: 28rem) {
151
+ .ceramic-activity-feed {
152
+ padding: var(--space-3);
153
+ }
154
+ }
@@ -0,0 +1,313 @@
1
+ import * as React from 'react'
2
+ import { Badge } from '@utopia-studio-design/design-system/Badge'
3
+ import { Button } from '@utopia-studio-design/design-system/Button'
4
+ import {
5
+ ProgressBar,
6
+ StatusDot,
7
+ } from '@utopia-studio-design/design-system/DataDisplay'
8
+ import './agent-status.css'
9
+
10
+ export type AgentExecutionState =
11
+ | 'queued'
12
+ | 'running'
13
+ | 'waiting-approval'
14
+ | 'blocked'
15
+ | 'failed'
16
+ | 'completed'
17
+
18
+ export type AgentRisk = 'none' | 'low' | 'medium' | 'high'
19
+ export type AgentStatusDensity = 'compact' | 'default' | 'comfortable'
20
+ export type AgentStatusFilter = 'all' | 'active' | 'attention' | 'completed'
21
+ export type AgentStatusState = 'loading' | 'empty' | 'error' | 'ready'
22
+
23
+ export interface AgentApprovalRequest {
24
+ id: string
25
+ description?: string
26
+ label: string
27
+ }
28
+
29
+ export interface AgentStatusItem {
30
+ id: string
31
+ approval?: AgentApprovalRequest
32
+ name: string
33
+ progress?: number
34
+ risk?: AgentRisk
35
+ state: AgentExecutionState
36
+ summary?: string
37
+ taskLabel: string
38
+ updatedAt?: string
39
+ }
40
+
41
+ export interface AgentStatusLabels {
42
+ approve: string
43
+ approvalRequested: string
44
+ count: (visible: string, total: string) => string
45
+ emptyDescription: string
46
+ emptyTitle: string
47
+ errorDescription: string
48
+ errorTitle: string
49
+ eyebrow: string
50
+ loading: string
51
+ noMatchingDescription: string
52
+ noMatchingTitle: string
53
+ progress: string
54
+ reject: string
55
+ retry: string
56
+ risk: Record<Exclude<AgentRisk, 'none'>, string>
57
+ state: Record<AgentExecutionState, string>
58
+ timeUnavailable: string
59
+ title: string
60
+ updated: string
61
+ }
62
+
63
+ export interface AgentStatusBlockProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title'> {
64
+ density?: AgentStatusDensity
65
+ direction?: 'ltr' | 'rtl'
66
+ filter?: AgentStatusFilter
67
+ items?: AgentStatusItem[]
68
+ labels: AgentStatusLabels
69
+ locale?: string
70
+ maxItems?: number
71
+ onApprove?: (item: AgentStatusItem, request: AgentApprovalRequest) => void
72
+ onReject?: (item: AgentStatusItem, request: AgentApprovalRequest) => void
73
+ onRetry?: () => void
74
+ showApprovals?: boolean
75
+ state?: AgentStatusState
76
+ timeZone?: string
77
+ title?: string
78
+ }
79
+
80
+ const attentionStates = new Set<AgentExecutionState>([
81
+ 'waiting-approval',
82
+ 'blocked',
83
+ 'failed',
84
+ ])
85
+
86
+ function filterItems(items: AgentStatusItem[], filter: AgentStatusFilter) {
87
+ if (filter === 'active') {
88
+ return items.filter((item) => item.state === 'queued' || item.state === 'running')
89
+ }
90
+ if (filter === 'attention') {
91
+ return items.filter((item) => attentionStates.has(item.state))
92
+ }
93
+ if (filter === 'completed') {
94
+ return items.filter((item) => item.state === 'completed')
95
+ }
96
+ return items
97
+ }
98
+
99
+ function formatNumber(locale: string, value: number) {
100
+ try {
101
+ return new Intl.NumberFormat(locale).format(value)
102
+ } catch {
103
+ return String(value)
104
+ }
105
+ }
106
+
107
+ function formatPercent(locale: string, value: number) {
108
+ try {
109
+ return new Intl.NumberFormat(locale, {
110
+ maximumFractionDigits: 0,
111
+ style: 'percent',
112
+ }).format(value / 100)
113
+ } catch {
114
+ return `${Math.round(value)}%`
115
+ }
116
+ }
117
+
118
+ function formatUpdatedAt(locale: string, timeZone: string, value?: string) {
119
+ if (!value) return null
120
+ const date = new Date(value)
121
+ if (Number.isNaN(date.valueOf())) return null
122
+ try {
123
+ return {
124
+ date,
125
+ label: new Intl.DateTimeFormat(locale, {
126
+ dateStyle: 'medium',
127
+ timeStyle: 'short',
128
+ timeZone,
129
+ }).format(date),
130
+ }
131
+ } catch {
132
+ return {
133
+ date,
134
+ label: new Intl.DateTimeFormat(locale, {
135
+ dateStyle: 'medium',
136
+ timeStyle: 'short',
137
+ timeZone: 'UTC',
138
+ }).format(date),
139
+ }
140
+ }
141
+ }
142
+
143
+ function statusVariant(state: AgentExecutionState) {
144
+ if (state === 'failed') return 'destructive' as const
145
+ if (state === 'completed') return 'secondary' as const
146
+ return 'outline' as const
147
+ }
148
+
149
+ function progressStatus(state: AgentExecutionState) {
150
+ if (state === 'failed') return 'destructive' as const
151
+ if (state === 'blocked' || state === 'waiting-approval') return 'warning' as const
152
+ if (state === 'completed') return 'success' as const
153
+ return 'default' as const
154
+ }
155
+
156
+ export function AgentStatusBlock({
157
+ density = 'default',
158
+ direction,
159
+ filter = 'all',
160
+ items = [],
161
+ labels,
162
+ locale = 'en-US',
163
+ maxItems = 6,
164
+ onApprove,
165
+ onReject,
166
+ onRetry,
167
+ showApprovals = true,
168
+ state: stateProp,
169
+ timeZone = 'UTC',
170
+ title,
171
+ ...props
172
+ }: AgentStatusBlockProps) {
173
+ const titleId = React.useId()
174
+ const filteredItems = filterItems(items, filter)
175
+ const visibleItems = filteredItems.slice(0, maxItems)
176
+ const state = stateProp ?? (items.length ? 'ready' : 'empty')
177
+ const visibleCount = formatNumber(locale, visibleItems.length)
178
+ const totalCount = formatNumber(locale, filteredItems.length)
179
+
180
+ return (
181
+ <section
182
+ aria-busy={state === 'loading'}
183
+ aria-labelledby={titleId}
184
+ className="ceramic-agent-status"
185
+ data-density={density}
186
+ data-filter={filter}
187
+ data-state={state}
188
+ dir={direction}
189
+ lang={locale}
190
+ {...props}
191
+ >
192
+ <header>
193
+ <div>
194
+ <span>{labels.eyebrow}</span>
195
+ <h2 id={titleId}>{title ?? labels.title}</h2>
196
+ </div>
197
+ {state === 'ready' ? (
198
+ <Badge variant="outline">{labels.count(visibleCount, totalCount)}</Badge>
199
+ ) : null}
200
+ </header>
201
+
202
+ {state === 'loading' ? (
203
+ <div className="ceramic-agent-status-state" role="status">{labels.loading}</div>
204
+ ) : null}
205
+ {state === 'error' ? (
206
+ <div className="ceramic-agent-status-state" role="alert">
207
+ <strong>{labels.errorTitle}</strong>
208
+ <span>{labels.errorDescription}</span>
209
+ {onRetry ? <Button size="sm" variant="outline" onClick={onRetry}>{labels.retry}</Button> : null}
210
+ </div>
211
+ ) : null}
212
+ {state === 'empty' ? (
213
+ <div className="ceramic-agent-status-state" role="status">
214
+ <strong>{labels.emptyTitle}</strong>
215
+ <span>{labels.emptyDescription}</span>
216
+ </div>
217
+ ) : null}
218
+ {state === 'ready' && visibleItems.length === 0 ? (
219
+ <div className="ceramic-agent-status-state" role="status">
220
+ <strong>{labels.noMatchingTitle}</strong>
221
+ <span>{labels.noMatchingDescription}</span>
222
+ </div>
223
+ ) : null}
224
+ {state === 'ready' && visibleItems.length > 0 ? (
225
+ <ul className="ceramic-agent-status-list">
226
+ {visibleItems.map((item) => {
227
+ const progress = Math.max(0, Math.min(100, item.progress ?? (
228
+ item.state === 'completed' ? 100 : 0
229
+ )))
230
+ const updatedAt = formatUpdatedAt(locale, timeZone, item.updatedAt)
231
+ const approvalTitleId = `${titleId}-${item.id}-approval`
232
+ return (
233
+ <li data-execution-state={item.state} key={item.id}>
234
+ <div className="ceramic-agent-status-heading">
235
+ <StatusDot aria-hidden="true" data-execution-state={item.state} />
236
+ <div>
237
+ <strong><bdi>{item.name}</bdi></strong>
238
+ <span><bdi>{item.taskLabel}</bdi></span>
239
+ </div>
240
+ <Badge variant={statusVariant(item.state)}>
241
+ {labels.state[item.state]}
242
+ </Badge>
243
+ </div>
244
+
245
+ {item.summary ? <p><bdi>{item.summary}</bdi></p> : null}
246
+
247
+ {(item.state === 'running' || item.state === 'completed') ? (
248
+ <ProgressBar
249
+ aria-label={`${item.name}: ${labels.progress}`}
250
+ label={labels.progress}
251
+ showValue
252
+ size={density === 'compact' ? 'sm' : 'md'}
253
+ status={progressStatus(item.state)}
254
+ value={progress}
255
+ valueText={formatPercent(locale, progress)}
256
+ />
257
+ ) : null}
258
+
259
+ <div className="ceramic-agent-status-meta">
260
+ {item.risk && item.risk !== 'none' ? (
261
+ <Badge data-risk={item.risk} variant="outline">
262
+ {labels.risk[item.risk]}
263
+ </Badge>
264
+ ) : null}
265
+ {updatedAt ? (
266
+ <span>
267
+ {labels.updated}{' '}
268
+ <time dateTime={updatedAt.date.toISOString()}>{updatedAt.label}</time>
269
+ </span>
270
+ ) : item.updatedAt ? <span>{labels.timeUnavailable}</span> : null}
271
+ </div>
272
+
273
+ {showApprovals && item.state === 'waiting-approval' && item.approval ? (
274
+ <section
275
+ aria-labelledby={approvalTitleId}
276
+ className="ceramic-agent-approval"
277
+ >
278
+ <div>
279
+ <strong id={approvalTitleId}>{labels.approvalRequested}</strong>
280
+ <span>{item.approval.label}</span>
281
+ {item.approval.description ? <p>{item.approval.description}</p> : null}
282
+ </div>
283
+ {onApprove || onReject ? (
284
+ <div>
285
+ {onReject ? (
286
+ <Button
287
+ size="sm"
288
+ variant="outline"
289
+ onClick={() => onReject(item, item.approval!)}
290
+ >
291
+ {labels.reject}
292
+ </Button>
293
+ ) : null}
294
+ {onApprove ? (
295
+ <Button
296
+ size="sm"
297
+ onClick={() => onApprove(item, item.approval!)}
298
+ >
299
+ {labels.approve}
300
+ </Button>
301
+ ) : null}
302
+ </div>
303
+ ) : null}
304
+ </section>
305
+ ) : null}
306
+ </li>
307
+ )
308
+ })}
309
+ </ul>
310
+ ) : null}
311
+ </section>
312
+ )
313
+ }
@@ -0,0 +1,15 @@
1
+ # Agent Status
2
+
3
+ `AgentStatusBlock` is a product-data surface for observing agent execution,
4
+ progress, risk, approval, failure, and completion without putting operational
5
+ records into Ceramic Layout JSON.
6
+
7
+ The registry controls only semantic presentation props. Products own execution
8
+ records, sanitized summaries, timestamps, retry behavior, and approval/rejection
9
+ callbacks. Never pass raw stack traces, prompts, credentials, or command output
10
+ as an item summary.
11
+
12
+ Use `resolveAgentStatusConfiguration` in every runtime/editor adapter so locale,
13
+ direction, filter, density, item limit, approval visibility, and timezone remain
14
+ consistent. Arabic fixtures demonstrate scoped RTL and mixed-direction agent
15
+ names.
@@ -0,0 +1,133 @@
1
+ import { getLocaleTextDirection } from '@utopia-studio-design/design-system/Utilities'
2
+ import type {
3
+ AgentStatusBlockProps,
4
+ AgentStatusFilter,
5
+ AgentStatusLabels,
6
+ } from './AgentStatusBlock'
7
+
8
+ export const agentStatusLocaleOptions = ['en-US', 'ar-SA', 'ar-EG', 'ar-AE'] as const
9
+
10
+ export type AgentStatusLocale = typeof agentStatusLocaleOptions[number]
11
+ export type AgentStatusDirection = 'inherit' | 'auto' | 'ltr' | 'rtl'
12
+
13
+ export interface AgentStatusConfiguration {
14
+ density: NonNullable<AgentStatusBlockProps['density']>
15
+ direction?: 'ltr' | 'rtl'
16
+ filter: AgentStatusFilter
17
+ labels: AgentStatusLabels
18
+ locale: AgentStatusLocale
19
+ maxItems: number
20
+ showApprovals: boolean
21
+ timeZone: string
22
+ }
23
+
24
+ export interface AgentStatusContext {
25
+ direction: 'ltr' | 'rtl'
26
+ }
27
+
28
+ const directions: AgentStatusDirection[] = ['inherit', 'auto', 'ltr', 'rtl']
29
+ const densities: AgentStatusConfiguration['density'][] = ['compact', 'default', 'comfortable']
30
+ const filters: AgentStatusFilter[] = ['all', 'active', 'attention', 'completed']
31
+
32
+ const englishLabels: AgentStatusLabels = {
33
+ approve: 'Approve',
34
+ approvalRequested: 'Approval requested',
35
+ count: (visible, total) => `${visible} of ${total}`,
36
+ emptyDescription: 'Agent executions will appear here when work begins.',
37
+ emptyTitle: 'No agent executions',
38
+ errorDescription: 'Reconnect the execution source and try again.',
39
+ errorTitle: 'Agent status unavailable',
40
+ eyebrow: 'Agent operations',
41
+ loading: 'Loading agent status…',
42
+ noMatchingDescription: 'Change the semantic filter to review other executions.',
43
+ noMatchingTitle: 'No executions match this view',
44
+ progress: 'Progress',
45
+ reject: 'Reject',
46
+ retry: 'Try again',
47
+ risk: {
48
+ high: 'High risk',
49
+ low: 'Low risk',
50
+ medium: 'Medium risk',
51
+ },
52
+ state: {
53
+ blocked: 'Blocked',
54
+ completed: 'Completed',
55
+ failed: 'Failed',
56
+ queued: 'Queued',
57
+ running: 'Running',
58
+ 'waiting-approval': 'Waiting for approval',
59
+ },
60
+ timeUnavailable: 'Update time unavailable',
61
+ title: 'Agent status',
62
+ updated: 'Updated',
63
+ }
64
+
65
+ const arabicLabels: AgentStatusLabels = {
66
+ approve: 'موافقة',
67
+ approvalRequested: 'طلب موافقة',
68
+ count: (visible, total) => `${visible} من ${total}`,
69
+ emptyDescription: 'ستظهر عمليات الوكلاء هنا عند بدء العمل.',
70
+ emptyTitle: 'لا توجد عمليات للوكلاء',
71
+ errorDescription: 'أعد الاتصال بمصدر التنفيذ ثم حاول مرة أخرى.',
72
+ errorTitle: 'حالة الوكلاء غير متاحة',
73
+ eyebrow: 'عمليات الوكلاء',
74
+ loading: 'جارٍ تحميل حالة الوكلاء…',
75
+ noMatchingDescription: 'غيّر عامل التصفية الدلالي لمراجعة العمليات الأخرى.',
76
+ noMatchingTitle: 'لا توجد عمليات مطابقة لهذا العرض',
77
+ progress: 'التقدم',
78
+ reject: 'رفض',
79
+ retry: 'حاول مرة أخرى',
80
+ risk: {
81
+ high: 'مخاطر عالية',
82
+ low: 'مخاطر منخفضة',
83
+ medium: 'مخاطر متوسطة',
84
+ },
85
+ state: {
86
+ blocked: 'متوقف',
87
+ completed: 'مكتمل',
88
+ failed: 'فشل',
89
+ queued: 'في قائمة الانتظار',
90
+ running: 'قيد التشغيل',
91
+ 'waiting-approval': 'بانتظار الموافقة',
92
+ },
93
+ timeUnavailable: 'وقت التحديث غير متاح',
94
+ title: 'حالة الوكلاء',
95
+ updated: 'آخر تحديث',
96
+ }
97
+
98
+ export function getAgentStatusLabels(locale: string): AgentStatusLabels {
99
+ return getLocaleTextDirection(locale) === 'rtl' ? arabicLabels : englishLabels
100
+ }
101
+
102
+ export function resolveAgentStatusConfiguration(
103
+ props: Record<string, unknown>,
104
+ context: AgentStatusContext,
105
+ ): AgentStatusConfiguration {
106
+ const locale = agentStatusLocaleOptions.includes(props.locale as AgentStatusLocale)
107
+ ? props.locale as AgentStatusLocale
108
+ : 'en-US'
109
+ const directionMode = directions.includes(props.direction as AgentStatusDirection)
110
+ ? props.direction as AgentStatusDirection
111
+ : 'inherit'
112
+ const direction = directionMode === 'inherit'
113
+ ? context.direction
114
+ : directionMode === 'auto' ? getLocaleTextDirection(locale) : directionMode
115
+ const maxItems = Number.isInteger(props.maxItems)
116
+ ? Math.min(12, Math.max(1, Number(props.maxItems)))
117
+ : 6
118
+
119
+ return {
120
+ density: densities.includes(props.density as AgentStatusConfiguration['density'])
121
+ ? props.density as AgentStatusConfiguration['density']
122
+ : 'default',
123
+ direction,
124
+ filter: filters.includes(props.filter as AgentStatusFilter)
125
+ ? props.filter as AgentStatusFilter
126
+ : 'all',
127
+ labels: getAgentStatusLabels(locale),
128
+ locale,
129
+ maxItems,
130
+ showApprovals: props.showApprovals !== false,
131
+ timeZone: typeof props.timeZone === 'string' && props.timeZone ? props.timeZone : 'UTC',
132
+ }
133
+ }
@@ -0,0 +1,79 @@
1
+ import type { AgentStatusItem } from './AgentStatusBlock'
2
+
3
+ const englishItems: AgentStatusItem[] = [
4
+ {
5
+ id: 'mina-qa',
6
+ name: 'Mina UX-QA',
7
+ progress: 68,
8
+ risk: 'low',
9
+ state: 'running',
10
+ summary: 'Checking responsive, Arabic, and interaction contracts.',
11
+ taskLabel: 'Dashboard composition QA',
12
+ updatedAt: '2026-07-30T08:42:00.000Z',
13
+ },
14
+ {
15
+ approval: {
16
+ id: 'publish-approval',
17
+ description: 'Publishes the validated patch release to the configured npm registry.',
18
+ label: 'Publish Ceramic patch release',
19
+ },
20
+ id: 'release-agent',
21
+ name: 'Release Agent',
22
+ risk: 'medium',
23
+ state: 'waiting-approval',
24
+ summary: 'All local release gates passed. External publishing requires a human decision.',
25
+ taskLabel: 'Prepare npm release',
26
+ updatedAt: '2026-07-30T08:39:00.000Z',
27
+ },
28
+ {
29
+ id: 'registry-agent',
30
+ name: 'Registry Agent',
31
+ risk: 'high',
32
+ state: 'blocked',
33
+ summary: 'A required product data adapter has not been connected.',
34
+ taskLabel: 'Sync production activity source',
35
+ updatedAt: '2026-07-30T08:31:00.000Z',
36
+ },
37
+ {
38
+ id: 'build-agent',
39
+ name: 'Build Agent',
40
+ progress: 100,
41
+ risk: 'none',
42
+ state: 'completed',
43
+ summary: 'Standalone Calendar and Dashboard templates built successfully.',
44
+ taskLabel: 'Verify generated templates',
45
+ updatedAt: '2026-07-30T08:20:00.000Z',
46
+ },
47
+ ]
48
+
49
+ const arabicItems: AgentStatusItem[] = [
50
+ {
51
+ ...englishItems[0],
52
+ summary: 'جارٍ فحص عقود الاستجابة والعربية والتفاعل.',
53
+ taskLabel: 'فحص جودة تكوين لوحة المعلومات',
54
+ },
55
+ {
56
+ ...englishItems[1],
57
+ approval: {
58
+ id: 'publish-approval',
59
+ description: 'سيتم نشر إصدار التصحيح المعتمد في سجل npm المحدد.',
60
+ label: 'نشر إصدار تصحيح Ceramic',
61
+ },
62
+ summary: 'نجحت جميع فحوصات الإصدار المحلية. يتطلب النشر الخارجي قراراً بشرياً.',
63
+ taskLabel: 'إعداد إصدار npm',
64
+ },
65
+ {
66
+ ...englishItems[2],
67
+ summary: 'لم يتم ربط محول بيانات المنتج المطلوب.',
68
+ taskLabel: 'مزامنة مصدر النشاط في الإنتاج',
69
+ },
70
+ {
71
+ ...englishItems[3],
72
+ summary: 'تم بناء قوالب التقويم ولوحة المعلومات المستقلة بنجاح.',
73
+ taskLabel: 'التحقق من القوالب المولدة',
74
+ },
75
+ ]
76
+
77
+ export function getAgentStatusFixture(locale: string): AgentStatusItem[] {
78
+ return locale.startsWith('ar') ? arabicItems : englishItems
79
+ }