@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,238 @@
1
+ import * as React from 'react'
2
+ import { Button } from '@utopia-studio-design/design-system/Button'
3
+ import type {
4
+ ChartAnnotationDefinition,
5
+ ChartDatum,
6
+ ChartSeriesDefinition,
7
+ ChartType,
8
+ } from '@utopia-studio-design/design-system/Charts'
9
+ import type {
10
+ DatabaseQuery,
11
+ DatabaseRecord,
12
+ DatabaseSchema,
13
+ } from '@utopia-studio-design/design-system/Database'
14
+ import type { CeramicLayoutBlock } from '@utopia-studio-design/design-system/Composition'
15
+ import { DatabaseTableBlock } from './DatabaseWorkspaceBlocks'
16
+
17
+ const LazyAnalyticsChartCard = React.lazy(async () => {
18
+ const module = await import('./AnalyticsDashboardBlocks')
19
+ return { default: module.AnalyticsChartCard }
20
+ })
21
+
22
+ const issueSchema: DatabaseSchema = {
23
+ id: 'composer-issues',
24
+ title: 'Delivery issues',
25
+ primaryPropertyId: 'title',
26
+ properties: [
27
+ { id: 'title', label: 'Issue', type: 'text', required: true, width: 260 },
28
+ {
29
+ id: 'status',
30
+ label: 'Status',
31
+ type: 'select',
32
+ width: 150,
33
+ options: [
34
+ { id: 'active', label: 'Active', tone: 'warning' },
35
+ { id: 'backlog', label: 'Backlog' },
36
+ { id: 'blocked', label: 'Blocked', tone: 'negative' },
37
+ { id: 'completed', label: 'Completed', tone: 'positive' },
38
+ ],
39
+ },
40
+ {
41
+ id: 'priority',
42
+ label: 'Priority',
43
+ type: 'select',
44
+ width: 130,
45
+ options: [
46
+ { id: 'high', label: 'High', tone: 'negative' },
47
+ { id: 'medium', label: 'Medium', tone: 'warning' },
48
+ { id: 'low', label: 'Low' },
49
+ ],
50
+ },
51
+ { id: 'owner', label: 'Owner', type: 'person', width: 140 },
52
+ { id: 'due', label: 'Due', type: 'date', width: 150 },
53
+ ],
54
+ }
55
+
56
+ const issueRecords: DatabaseRecord[] = [
57
+ { id: 'issue-1', values: { title: 'Audit Arabic calendar navigation', status: 'active', priority: 'high', owner: 'Mina', due: '2026-08-01' } },
58
+ { id: 'issue-2', values: { title: 'Review visual-editor payload', status: 'active', priority: 'medium', owner: 'Aiden', due: '2026-08-03' } },
59
+ { id: 'issue-3', values: { title: 'Document registry permissions', status: 'backlog', priority: 'medium', owner: 'Haajar', due: '2026-08-05' } },
60
+ { id: 'issue-4', values: { title: 'Resolve mobile table overflow', status: 'blocked', priority: 'high', owner: 'Sora', due: '2026-07-31' } },
61
+ { id: 'issue-5', values: { title: 'Verify Button asChild regression', status: 'completed', priority: 'high', owner: 'Mina', due: '2026-07-29' } },
62
+ { id: 'issue-6', values: { title: 'Publish Calendar QA fixture', status: 'completed', priority: 'low', owner: 'Aiden', due: '2026-07-30' } },
63
+ ]
64
+
65
+ const chartData: ChartDatum[] = [
66
+ { id: 'mon', label: 'Mon', values: { completed: 18, previous: 14 } },
67
+ { id: 'tue', label: 'Tue', values: { completed: 23, previous: 17 } },
68
+ { id: 'wed', label: 'Wed', values: { completed: 20, previous: 19 } },
69
+ { id: 'thu', label: 'Thu', values: { completed: 28, previous: 21 } },
70
+ { id: 'fri', label: 'Fri', values: { completed: 31, previous: 25 } },
71
+ ]
72
+
73
+ const currentSeries: ChartSeriesDefinition = {
74
+ id: 'completed',
75
+ label: 'Completed work',
76
+ tone: 'primary',
77
+ }
78
+ const previousSeries: ChartSeriesDefinition = {
79
+ id: 'previous',
80
+ label: 'Previous period',
81
+ pattern: 'dash',
82
+ tone: 'benchmark',
83
+ }
84
+
85
+ function issueQuery(view: unknown): DatabaseQuery {
86
+ if (view === 'active') {
87
+ return {
88
+ filters: [{
89
+ id: 'active-issues',
90
+ propertyId: 'status',
91
+ operator: 'not-equals',
92
+ value: 'completed',
93
+ }],
94
+ }
95
+ }
96
+ if (view === 'backlog' || view === 'blocked' || view === 'completed') {
97
+ return {
98
+ filters: [{
99
+ id: `${view}-issues`,
100
+ propertyId: 'status',
101
+ operator: 'equals',
102
+ value: view,
103
+ }],
104
+ }
105
+ }
106
+ return {}
107
+ }
108
+
109
+ export function IssueTableRegistryBlock({
110
+ block,
111
+ direction,
112
+ }: {
113
+ block: CeramicLayoutBlock
114
+ direction: 'ltr' | 'rtl'
115
+ }) {
116
+ const requestedColumns = Array.isArray(block.props.columns)
117
+ ? block.props.columns.filter((column): column is string => typeof column === 'string')
118
+ : []
119
+ const visibleColumnIds = new Set(
120
+ requestedColumns.length
121
+ ? ['title', ...requestedColumns]
122
+ : issueSchema.properties.map((property) => property.id),
123
+ )
124
+ const density = (
125
+ block.props.density === 'compact'
126
+ || block.props.density === 'comfortable'
127
+ || block.props.density === 'default'
128
+ )
129
+ ? block.props.density
130
+ : 'default'
131
+
132
+ return (
133
+ <div className="ceramic-issue-table-block">
134
+ <DatabaseTableBlock
135
+ density={density}
136
+ direction={direction}
137
+ permissions={{
138
+ bulkActions: false,
139
+ edit: false,
140
+ reorderColumns: false,
141
+ reorderRows: false,
142
+ resizeColumns: false,
143
+ }}
144
+ query={issueQuery(block.props.view)}
145
+ records={issueRecords}
146
+ schema={{
147
+ ...issueSchema,
148
+ properties: issueSchema.properties.map((property) => ({
149
+ ...property,
150
+ hidden: !visibleColumnIds.has(property.id),
151
+ })),
152
+ }}
153
+ state="ready"
154
+ />
155
+ </div>
156
+ )
157
+ }
158
+
159
+ class ChartLoadBoundary extends React.Component<
160
+ { children: React.ReactNode },
161
+ { failed: boolean }
162
+ > {
163
+ state = { failed: false }
164
+
165
+ static getDerivedStateFromError() {
166
+ return { failed: true }
167
+ }
168
+
169
+ componentDidCatch(error: Error) {
170
+ console.error('The registered chart renderer failed to load.', error)
171
+ }
172
+
173
+ render() {
174
+ if (this.state.failed) {
175
+ return (
176
+ <div className="ceramic-chart-panel-state" role="alert">
177
+ <strong>Chart preview could not load.</strong>
178
+ <span>The layout remains valid. Retry the optional visualization module.</span>
179
+ <Button size="sm" variant="outline" onClick={() => window.location.reload()}>
180
+ Retry chart
181
+ </Button>
182
+ </div>
183
+ )
184
+ }
185
+ return this.props.children
186
+ }
187
+ }
188
+
189
+ export function ChartPanelRegistryBlock({
190
+ block,
191
+ direction,
192
+ }: {
193
+ block: CeramicLayoutBlock
194
+ direction: 'ltr' | 'rtl'
195
+ }) {
196
+ const chartType = (
197
+ ['line', 'bar', 'area', 'stacked', 'scatter', 'donut'] as ChartType[]
198
+ ).includes(block.props.chartType as ChartType)
199
+ ? block.props.chartType as ChartType
200
+ : 'line'
201
+ const comparison = typeof block.props.comparison === 'string'
202
+ ? block.props.comparison
203
+ : 'previous-period'
204
+ const annotations: ChartAnnotationDefinition[] = comparison === 'benchmark'
205
+ ? [{ id: 'benchmark', label: 'Team benchmark', value: 24, tone: 'neutral' }]
206
+ : comparison === 'target'
207
+ ? [{ id: 'target', label: 'Weekly target', value: 30, tone: 'neutral' }]
208
+ : []
209
+ const series = comparison === 'previous-period'
210
+ ? [currentSeries, previousSeries]
211
+ : [currentSeries]
212
+
213
+ return (
214
+ <div className="ceramic-chart-panel-block" dir={direction}>
215
+ <ChartLoadBoundary>
216
+ <React.Suspense
217
+ fallback={(
218
+ <div className="ceramic-chart-panel-state" role="status">
219
+ Loading chart preview…
220
+ </div>
221
+ )}
222
+ >
223
+ <LazyAnalyticsChartCard
224
+ annotations={annotations}
225
+ data={chartData}
226
+ description="Completed delivery work by weekday"
227
+ direction={direction}
228
+ series={series}
229
+ showDataTable={block.props.showTable === true}
230
+ state="ready"
231
+ title="Delivery throughput"
232
+ type={chartType}
233
+ />
234
+ </React.Suspense>
235
+ </ChartLoadBoundary>
236
+ </div>
237
+ )
238
+ }
@@ -0,0 +1,96 @@
1
+ import { getLocaleTextDirection } from '@utopia-studio-design/design-system/Utilities'
2
+ import type {
3
+ ActivityFeedBlockProps,
4
+ ActivityFeedLabels,
5
+ } from './ActivityFeedBlock'
6
+
7
+ export const activityFeedLocaleOptions = ['en-US', 'ar-SA', 'ar-EG', 'ar-AE'] as const
8
+
9
+ export type ActivityFeedLocale = typeof activityFeedLocaleOptions[number]
10
+ export type ActivityFeedDirection = 'inherit' | 'auto' | 'ltr' | 'rtl'
11
+
12
+ export interface ActivityFeedConfiguration {
13
+ density: NonNullable<ActivityFeedBlockProps['density']>
14
+ direction?: 'ltr' | 'rtl'
15
+ groupBy: NonNullable<ActivityFeedBlockProps['groupBy']>
16
+ labels: ActivityFeedLabels
17
+ locale: ActivityFeedLocale
18
+ pageSize: number
19
+ showSources: boolean
20
+ timeZone: string
21
+ }
22
+
23
+ export interface ActivityFeedContext {
24
+ direction: 'ltr' | 'rtl'
25
+ }
26
+
27
+ const directionModes: ActivityFeedDirection[] = ['inherit', 'auto', 'ltr', 'rtl']
28
+ const densities: ActivityFeedConfiguration['density'][] = ['compact', 'default', 'comfortable']
29
+ const groupings: ActivityFeedConfiguration['groupBy'][] = ['none', 'day', 'source']
30
+
31
+ const englishLabels: ActivityFeedLabels = {
32
+ end: 'You have reached the end of the activity.',
33
+ emptyDescription: 'New updates will appear here when work changes.',
34
+ emptyTitle: 'No activity yet',
35
+ errorDescription: 'Reconnect the activity source and try again.',
36
+ errorTitle: 'Activity unavailable',
37
+ eyebrow: 'Activity',
38
+ invalidTime: 'Time unavailable',
39
+ loadMore: 'Load more',
40
+ loading: 'Loading activity…',
41
+ otherSource: 'Other',
42
+ retry: 'Try again',
43
+ title: 'Recent activity',
44
+ }
45
+
46
+ const arabicLabels: ActivityFeedLabels = {
47
+ end: 'وصلت إلى نهاية سجل النشاط.',
48
+ emptyDescription: 'ستظهر التحديثات الجديدة هنا عند تغيّر العمل.',
49
+ emptyTitle: 'لا يوجد نشاط بعد',
50
+ errorDescription: 'أعد الاتصال بمصدر النشاط ثم حاول مرة أخرى.',
51
+ errorTitle: 'النشاط غير متاح',
52
+ eyebrow: 'النشاط',
53
+ invalidTime: 'الوقت غير متاح',
54
+ loadMore: 'تحميل المزيد',
55
+ loading: 'جارٍ تحميل النشاط…',
56
+ otherSource: 'أخرى',
57
+ retry: 'حاول مرة أخرى',
58
+ title: 'النشاط الأخير',
59
+ }
60
+
61
+ export function getActivityFeedLabels(locale: string): ActivityFeedLabels {
62
+ return getLocaleTextDirection(locale) === 'rtl' ? arabicLabels : englishLabels
63
+ }
64
+
65
+ export function resolveActivityFeedConfiguration(
66
+ props: Record<string, unknown>,
67
+ context: ActivityFeedContext,
68
+ ): ActivityFeedConfiguration {
69
+ const locale = activityFeedLocaleOptions.includes(props.locale as ActivityFeedLocale)
70
+ ? props.locale as ActivityFeedLocale
71
+ : 'en-US'
72
+ const directionMode = directionModes.includes(props.direction as ActivityFeedDirection)
73
+ ? props.direction as ActivityFeedDirection
74
+ : 'inherit'
75
+ const direction = directionMode === 'inherit'
76
+ ? context.direction
77
+ : directionMode === 'auto' ? getLocaleTextDirection(locale) : directionMode
78
+ const pageSize = Number.isInteger(props.pageSize)
79
+ ? Math.min(20, Math.max(2, Number(props.pageSize)))
80
+ : 4
81
+
82
+ return {
83
+ density: densities.includes(props.density as ActivityFeedConfiguration['density'])
84
+ ? props.density as ActivityFeedConfiguration['density']
85
+ : 'default',
86
+ direction,
87
+ groupBy: groupings.includes(props.groupBy as ActivityFeedConfiguration['groupBy'])
88
+ ? props.groupBy as ActivityFeedConfiguration['groupBy']
89
+ : 'day',
90
+ labels: getActivityFeedLabels(locale),
91
+ locale,
92
+ pageSize,
93
+ showSources: props.showSources !== false,
94
+ timeZone: typeof props.timeZone === 'string' && props.timeZone ? props.timeZone : 'UTC',
95
+ }
96
+ }
@@ -0,0 +1,22 @@
1
+ import type { ActivityFeedItem } from './ActivityFeedBlock'
2
+
3
+ const activityItems: Record<'en' | 'ar', ActivityFeedItem[]> = {
4
+ en: [
5
+ { id: 'activity-1', label: 'Calendar block moved after the task list', source: 'Studio', timestamp: '2026-03-09T00:20:00.000Z', tone: 'info' },
6
+ { id: 'activity-2', label: 'Release QA completed without blockers', source: 'Mina', timestamp: '2026-03-09T01:10:00.000Z', tone: 'positive' },
7
+ { id: 'activity-3', label: 'Layout JSON published to this workspace', source: 'Agent', timestamp: '2026-03-09T02:30:00.000Z', tone: 'positive' },
8
+ { id: 'activity-4', label: 'Design-system package data synchronized', source: 'CLI', timestamp: '2026-03-08T06:40:00.000Z', tone: 'neutral' },
9
+ { id: 'activity-5', label: 'Arabic calendar review requested', source: 'Studio', timestamp: '2026-03-08T08:15:00.000Z', tone: 'warning' },
10
+ ],
11
+ ar: [
12
+ { id: 'activity-1', label: 'نُقلت كتلة التقويم بعد قائمة المهام', source: 'الاستوديو', timestamp: '2026-03-09T00:20:00.000Z', tone: 'info' },
13
+ { id: 'activity-2', label: 'اكتمل فحص الإصدار من دون عوائق', source: 'مينا', timestamp: '2026-03-09T01:10:00.000Z', tone: 'positive' },
14
+ { id: 'activity-3', label: 'نُشر ملف تخطيط JSON في مساحة العمل', source: 'الوكيل', timestamp: '2026-03-09T02:30:00.000Z', tone: 'positive' },
15
+ { id: 'activity-4', label: 'تمت مزامنة بيانات حزمة نظام التصميم', source: 'CLI', timestamp: '2026-03-08T06:40:00.000Z', tone: 'neutral' },
16
+ { id: 'activity-5', label: 'طُلبت مراجعة التقويم العربي', source: 'الاستوديو', timestamp: '2026-03-08T08:15:00.000Z', tone: 'warning' },
17
+ ],
18
+ }
19
+
20
+ export function getActivityFeedFixture(locale: string) {
21
+ return activityItems[locale.startsWith('ar') ? 'ar' : 'en']
22
+ }
@@ -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,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
+ }