@gntik-ai/templates 0.0.0-stage → 0.5.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 (300) hide show
  1. package/dist/index.d.ts +2420 -0
  2. package/dist/index.js +14520 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +64 -3
  5. package/src/accept-invitation/Page.test.tsx +40 -0
  6. package/src/accept-invitation/Page.tsx +153 -0
  7. package/src/accept-invitation/data.ts +22 -0
  8. package/src/accept-invitation/template.meta.ts +12 -0
  9. package/src/assistant-chat/Page.test.tsx +51 -0
  10. package/src/assistant-chat/Page.tsx +273 -0
  11. package/src/assistant-chat/data.ts +71 -0
  12. package/src/assistant-chat/template.meta.ts +12 -0
  13. package/src/audit-log/Page.test.tsx +58 -0
  14. package/src/audit-log/Page.tsx +284 -0
  15. package/src/audit-log/data.ts +133 -0
  16. package/src/audit-log/template.meta.ts +12 -0
  17. package/src/board/Lane.tsx +117 -0
  18. package/src/board/Page.test.tsx +68 -0
  19. package/src/board/Page.tsx +178 -0
  20. package/src/board/data.ts +127 -0
  21. package/src/board/template.meta.ts +12 -0
  22. package/src/board/useBoard.ts +146 -0
  23. package/src/changelog/Page.test.tsx +31 -0
  24. package/src/changelog/Page.tsx +174 -0
  25. package/src/changelog/data.ts +105 -0
  26. package/src/changelog/template.meta.ts +12 -0
  27. package/src/code-editor/Page.test.tsx +83 -0
  28. package/src/code-editor/Page.tsx +207 -0
  29. package/src/code-editor/data.ts +130 -0
  30. package/src/code-editor/template.meta.ts +12 -0
  31. package/src/contact/Page.test.tsx +33 -0
  32. package/src/contact/Page.tsx +272 -0
  33. package/src/contact/PublicFrame.tsx +84 -0
  34. package/src/contact/data.ts +129 -0
  35. package/src/contact/template.meta.ts +12 -0
  36. package/src/create-wizard/Overlay.test.tsx +68 -0
  37. package/src/create-wizard/Page.test.tsx +57 -0
  38. package/src/create-wizard/Page.tsx +246 -0
  39. package/src/create-wizard/data.ts +139 -0
  40. package/src/create-wizard/examples/Overlay.tsx +60 -0
  41. package/src/create-wizard/template.meta.ts +12 -0
  42. package/src/create-workspace/Page.test.tsx +41 -0
  43. package/src/create-workspace/Page.tsx +227 -0
  44. package/src/create-workspace/data.ts +61 -0
  45. package/src/create-workspace/template.meta.ts +12 -0
  46. package/src/data-explorer/Page.test.tsx +45 -0
  47. package/src/data-explorer/Page.tsx +247 -0
  48. package/src/data-explorer/data.ts +94 -0
  49. package/src/data-explorer/template.meta.ts +12 -0
  50. package/src/docs-article/ArticleFeedback.tsx +75 -0
  51. package/src/docs-article/Page.test.tsx +34 -0
  52. package/src/docs-article/Page.tsx +200 -0
  53. package/src/docs-article/data.ts +65 -0
  54. package/src/docs-article/template.meta.ts +12 -0
  55. package/src/evaluations/Page.test.tsx +40 -0
  56. package/src/evaluations/Page.tsx +238 -0
  57. package/src/evaluations/data.ts +194 -0
  58. package/src/evaluations/template.meta.ts +12 -0
  59. package/src/forgot-password/Page.test.tsx +199 -0
  60. package/src/forgot-password/Page.tsx +66 -0
  61. package/src/forgot-password/data.ts +5 -0
  62. package/src/forgot-password/examples/UsernameOrEmail.tsx +25 -0
  63. package/src/forgot-password/template.meta.ts +12 -0
  64. package/src/getting-started/Page.test.tsx +27 -0
  65. package/src/getting-started/Page.tsx +125 -0
  66. package/src/getting-started/data.ts +67 -0
  67. package/src/getting-started/template.meta.ts +12 -0
  68. package/src/help-home/Page.test.tsx +33 -0
  69. package/src/help-home/Page.tsx +159 -0
  70. package/src/help-home/data.ts +151 -0
  71. package/src/help-home/template.meta.ts +12 -0
  72. package/src/home-dashboard/Page.test.tsx +57 -0
  73. package/src/home-dashboard/Page.tsx +152 -0
  74. package/src/home-dashboard/data.ts +66 -0
  75. package/src/home-dashboard/template.meta.ts +12 -0
  76. package/src/incident-board/Page.test.tsx +77 -0
  77. package/src/incident-board/Page.tsx +263 -0
  78. package/src/incident-board/data.ts +220 -0
  79. package/src/incident-board/template.meta.ts +12 -0
  80. package/src/incident-console/Page.test.tsx +49 -0
  81. package/src/incident-console/Page.tsx +274 -0
  82. package/src/incident-console/data.ts +176 -0
  83. package/src/incident-console/template.meta.ts +12 -0
  84. package/src/index.ts +250 -0
  85. package/src/keyboard-shortcuts/Page.test.tsx +45 -0
  86. package/src/keyboard-shortcuts/Page.tsx +94 -0
  87. package/src/keyboard-shortcuts/ShortcutSheet.tsx +80 -0
  88. package/src/keyboard-shortcuts/data.ts +84 -0
  89. package/src/keyboard-shortcuts/template.meta.ts +12 -0
  90. package/src/landing/Page.test.tsx +27 -0
  91. package/src/landing/Page.tsx +110 -0
  92. package/src/landing/data.ts +17 -0
  93. package/src/landing/template.meta.ts +11 -0
  94. package/src/live-event-stream/Page.test.tsx +251 -0
  95. package/src/live-event-stream/Page.tsx +307 -0
  96. package/src/live-event-stream/data.ts +44 -0
  97. package/src/live-event-stream/examples/Connecting.tsx +5 -0
  98. package/src/live-event-stream/examples/Error.tsx +5 -0
  99. package/src/live-event-stream/examples/LiveEmpty.tsx +5 -0
  100. package/src/live-event-stream/examples/LiveWithEvents.tsx +6 -0
  101. package/src/live-event-stream/examples/Stopped.tsx +6 -0
  102. package/src/live-event-stream/template.meta.ts +74 -0
  103. package/src/messaging/Page.test.tsx +34 -0
  104. package/src/messaging/Page.tsx +236 -0
  105. package/src/messaging/data.ts +118 -0
  106. package/src/messaging/template.meta.ts +12 -0
  107. package/src/meta.ts +24 -0
  108. package/src/mfa/Page.test.tsx +22 -0
  109. package/src/mfa/Page.tsx +45 -0
  110. package/src/mfa/data.ts +6 -0
  111. package/src/mfa/template.meta.ts +12 -0
  112. package/src/model-providers/Page.test.tsx +58 -0
  113. package/src/model-providers/Page.tsx +198 -0
  114. package/src/model-providers/data.ts +101 -0
  115. package/src/model-providers/template.meta.ts +12 -0
  116. package/src/notification-inbox/Page.test.tsx +49 -0
  117. package/src/notification-inbox/Page.tsx +237 -0
  118. package/src/notification-inbox/data.ts +129 -0
  119. package/src/notification-inbox/template.meta.ts +12 -0
  120. package/src/pending-activation/Page.test.tsx +390 -0
  121. package/src/pending-activation/Page.tsx +145 -0
  122. package/src/pending-activation/data.ts +25 -0
  123. package/src/pending-activation/template.meta.ts +12 -0
  124. package/src/pricing/ComparisonTable.tsx +75 -0
  125. package/src/pricing/Page.test.tsx +29 -0
  126. package/src/pricing/Page.tsx +83 -0
  127. package/src/pricing/PublicFrame.tsx +84 -0
  128. package/src/pricing/data.ts +104 -0
  129. package/src/pricing/template.meta.ts +12 -0
  130. package/src/prompt-playground/Page.test.tsx +72 -0
  131. package/src/prompt-playground/Page.tsx +254 -0
  132. package/src/prompt-playground/data.ts +68 -0
  133. package/src/prompt-playground/template.meta.ts +12 -0
  134. package/src/public-hub/Page.test.tsx +176 -0
  135. package/src/public-hub/Page.tsx +125 -0
  136. package/src/public-hub/Source.test.ts +54 -0
  137. package/src/public-hub/data.ts +24 -0
  138. package/src/public-hub/examples/Dark.tsx +11 -0
  139. package/src/public-hub/examples/Full.tsx +6 -0
  140. package/src/public-hub/examples/HighContrast.tsx +11 -0
  141. package/src/public-hub/examples/Light.tsx +11 -0
  142. package/src/public-hub/examples/Loading.tsx +6 -0
  143. package/src/public-hub/examples/Minimal.tsx +5 -0
  144. package/src/public-hub/examples/MinimalLoading.tsx +5 -0
  145. package/src/public-hub/template.meta.ts +60 -0
  146. package/src/reset-password/Page.test.tsx +27 -0
  147. package/src/reset-password/Page.tsx +46 -0
  148. package/src/reset-password/data.ts +5 -0
  149. package/src/reset-password/template.meta.ts +12 -0
  150. package/src/resource-detail/Page.test.tsx +260 -0
  151. package/src/resource-detail/Page.tsx +262 -0
  152. package/src/resource-detail/data.ts +55 -0
  153. package/src/resource-detail/examples/postgres-table-detail.tsx +28 -0
  154. package/src/resource-detail/examples/realtime-configuration.tsx +26 -0
  155. package/src/resource-detail/examples/runtime-with-transition.tsx +30 -0
  156. package/src/resource-detail/examples/runtime-without-transition.tsx +33 -0
  157. package/src/resource-detail/examples/shell.ts +12 -0
  158. package/src/resource-detail/template.meta.ts +21 -0
  159. package/src/resource-form/Page.test.tsx +40 -0
  160. package/src/resource-form/Page.tsx +186 -0
  161. package/src/resource-form/data.ts +63 -0
  162. package/src/resource-form/template.meta.ts +12 -0
  163. package/src/resource-gallery/Page.test.tsx +42 -0
  164. package/src/resource-gallery/Page.tsx +125 -0
  165. package/src/resource-gallery/data.ts +44 -0
  166. package/src/resource-gallery/template.meta.ts +20 -0
  167. package/src/resource-index/Page.test.tsx +120 -0
  168. package/src/resource-index/Page.tsx +216 -0
  169. package/src/resource-index/data.ts +84 -0
  170. package/src/resource-index/template.meta.ts +21 -0
  171. package/src/resource-tree/Page.test.tsx +31 -0
  172. package/src/resource-tree/Page.tsx +232 -0
  173. package/src/resource-tree/data.ts +180 -0
  174. package/src/resource-tree/template.meta.ts +12 -0
  175. package/src/run-trace/Page.test.tsx +56 -0
  176. package/src/run-trace/Page.tsx +242 -0
  177. package/src/run-trace/data.ts +171 -0
  178. package/src/run-trace/template.meta.ts +12 -0
  179. package/src/scorecard/Page.test.tsx +38 -0
  180. package/src/scorecard/Page.tsx +160 -0
  181. package/src/scorecard/TargetsTable.tsx +92 -0
  182. package/src/scorecard/data.ts +118 -0
  183. package/src/scorecard/template.meta.ts +12 -0
  184. package/src/search-results/Page.test.tsx +43 -0
  185. package/src/search-results/Page.tsx +243 -0
  186. package/src/search-results/data.ts +130 -0
  187. package/src/search-results/template.meta.ts +12 -0
  188. package/src/settings-api-keys/Page.test.tsx +39 -0
  189. package/src/settings-api-keys/Page.tsx +171 -0
  190. package/src/settings-api-keys/data.ts +58 -0
  191. package/src/settings-api-keys/template.meta.ts +12 -0
  192. package/src/settings-appearance/Page.test.tsx +44 -0
  193. package/src/settings-appearance/Page.tsx +92 -0
  194. package/src/settings-appearance/data.ts +21 -0
  195. package/src/settings-appearance/template.meta.ts +11 -0
  196. package/src/settings-billing/Page.test.tsx +31 -0
  197. package/src/settings-billing/Page.tsx +71 -0
  198. package/src/settings-billing/data.ts +15 -0
  199. package/src/settings-billing/template.meta.ts +19 -0
  200. package/src/settings-integrations/Page.test.tsx +31 -0
  201. package/src/settings-integrations/Page.tsx +185 -0
  202. package/src/settings-integrations/data.ts +116 -0
  203. package/src/settings-integrations/template.meta.ts +11 -0
  204. package/src/settings-members/Page.test.tsx +32 -0
  205. package/src/settings-members/Page.tsx +140 -0
  206. package/src/settings-members/data.ts +28 -0
  207. package/src/settings-members/template.meta.ts +12 -0
  208. package/src/settings-notifications/Page.test.tsx +31 -0
  209. package/src/settings-notifications/Page.tsx +71 -0
  210. package/src/settings-notifications/data.ts +12 -0
  211. package/src/settings-notifications/template.meta.ts +11 -0
  212. package/src/settings-profile/Baseline.test.tsx +9 -0
  213. package/src/settings-profile/Editable.test.tsx +21 -0
  214. package/src/settings-profile/Page.test.tsx +41 -0
  215. package/src/settings-profile/Page.tsx +133 -0
  216. package/src/settings-profile/ReadOnly.test.tsx +143 -0
  217. package/src/settings-profile/ReadOnlyProfile.tsx +85 -0
  218. package/src/settings-profile/SettingsFrame.test.tsx +55 -0
  219. package/src/settings-profile/SettingsFrame.tsx +88 -0
  220. package/src/settings-profile/__tests__/dom.ts +8 -0
  221. package/src/settings-profile/data.ts +41 -0
  222. package/src/settings-profile/examples/ReadOnly.tsx +31 -0
  223. package/src/settings-profile/fixtures/editable.snap +1 -0
  224. package/src/settings-profile/settings-nav.ts +98 -0
  225. package/src/settings-profile/template.meta.ts +21 -0
  226. package/src/settings-security/MfaSetupDialog.tsx +96 -0
  227. package/src/settings-security/Page.test.tsx +44 -0
  228. package/src/settings-security/Page.tsx +158 -0
  229. package/src/settings-security/data.ts +11 -0
  230. package/src/settings-security/template.meta.ts +11 -0
  231. package/src/settings-workspace/Page.test.tsx +44 -0
  232. package/src/settings-workspace/Page.tsx +120 -0
  233. package/src/settings-workspace/data.ts +44 -0
  234. package/src/settings-workspace/template.meta.ts +19 -0
  235. package/src/shared/ConsoleShell.test.tsx +81 -0
  236. package/src/shared/ConsoleShell.tsx +98 -0
  237. package/src/shell-sidebar/Page.test.tsx +21 -0
  238. package/src/shell-sidebar/Page.tsx +85 -0
  239. package/src/shell-sidebar/data.ts +24 -0
  240. package/src/shell-sidebar/template.meta.ts +12 -0
  241. package/src/shell-topnav/Page.test.tsx +25 -0
  242. package/src/shell-topnav/Page.tsx +94 -0
  243. package/src/shell-topnav/data.ts +25 -0
  244. package/src/shell-topnav/template.meta.ts +12 -0
  245. package/src/sign-in/BrandPanel.tsx +34 -0
  246. package/src/sign-in/Page.test.tsx +41 -0
  247. package/src/sign-in/Page.tsx +73 -0
  248. package/src/sign-in/data.ts +11 -0
  249. package/src/sign-in/template.meta.ts +12 -0
  250. package/src/sign-in-card/Page.test.tsx +182 -0
  251. package/src/sign-in-card/Page.tsx +82 -0
  252. package/src/sign-in-card/data.ts +8 -0
  253. package/src/sign-in-card/examples/Username.tsx +28 -0
  254. package/src/sign-in-card/template.meta.ts +12 -0
  255. package/src/sign-up/Page.test.tsx +33 -0
  256. package/src/sign-up/Page.tsx +70 -0
  257. package/src/sign-up/data.ts +15 -0
  258. package/src/sign-up/template.meta.ts +12 -0
  259. package/src/sso-login/Page.test.tsx +41 -0
  260. package/src/sso-login/Page.tsx +151 -0
  261. package/src/sso-login/data.ts +10 -0
  262. package/src/sso-login/template.meta.ts +12 -0
  263. package/src/status-403/Page.test.tsx +23 -0
  264. package/src/status-403/Page.tsx +92 -0
  265. package/src/status-403/data.ts +13 -0
  266. package/src/status-403/template.meta.ts +12 -0
  267. package/src/status-404/Page.test.tsx +23 -0
  268. package/src/status-404/Page.tsx +60 -0
  269. package/src/status-404/PageHeading.test.tsx +52 -0
  270. package/src/status-404/data.ts +12 -0
  271. package/src/status-404/examples/PageHeader.tsx +28 -0
  272. package/src/status-404/template.meta.ts +12 -0
  273. package/src/status-500/Page.test.tsx +24 -0
  274. package/src/status-500/Page.tsx +70 -0
  275. package/src/status-500/data.ts +20 -0
  276. package/src/status-500/template.meta.ts +12 -0
  277. package/src/status-maintenance/Page.test.tsx +24 -0
  278. package/src/status-maintenance/Page.tsx +84 -0
  279. package/src/status-maintenance/data.ts +14 -0
  280. package/src/status-maintenance/template.meta.ts +12 -0
  281. package/src/styles.css +11 -0
  282. package/src/test/a11y.ts +18 -0
  283. package/src/test/setup.ts +36 -0
  284. package/src/upgrade-required/Page.test.tsx +28 -0
  285. package/src/upgrade-required/Page.tsx +79 -0
  286. package/src/upgrade-required/data.ts +19 -0
  287. package/src/upgrade-required/template.meta.ts +12 -0
  288. package/src/usage-analytics/Page.test.tsx +83 -0
  289. package/src/usage-analytics/Page.tsx +184 -0
  290. package/src/usage-analytics/data.ts +161 -0
  291. package/src/usage-analytics/template.meta.ts +21 -0
  292. package/src/verify-email/Page.test.tsx +32 -0
  293. package/src/verify-email/Page.tsx +118 -0
  294. package/src/verify-email/data.ts +7 -0
  295. package/src/verify-email/template.meta.ts +12 -0
  296. package/src/workflow-builder/Page.test.tsx +97 -0
  297. package/src/workflow-builder/Page.tsx +163 -0
  298. package/src/workflow-builder/data.ts +13 -0
  299. package/src/workflow-builder/template.meta.ts +12 -0
  300. package/README.md +0 -3
@@ -0,0 +1,180 @@
1
+ import { Building2, Database, FolderKanban, HardDrive, ListOrdered, Server, type LucideIcon } from '@gntik-ai/icons';
2
+ import type { BreadcrumbItem, TreeNode } from '@gntik-ai/ui';
3
+
4
+ export type ResourceKind = 'service' | 'database' | 'bucket' | 'queue';
5
+ export type ResourceGroupBy = 'kind' | 'region' | 'team';
6
+
7
+ export interface TreeResource {
8
+ id: string;
9
+ name: string;
10
+ kind: ResourceKind;
11
+ team: string;
12
+ project: string;
13
+ region: string;
14
+ status: 'healthy' | 'degraded' | 'stopped';
15
+ tags: string[];
16
+ /** ISO date of the last change. */
17
+ updatedAt: string;
18
+ }
19
+
20
+ export const kindLabels: Record<ResourceKind, string> = {
21
+ service: 'Services',
22
+ database: 'Databases',
23
+ bucket: 'Buckets',
24
+ queue: 'Queues',
25
+ };
26
+ export const kindIcons: Record<ResourceKind, LucideIcon> = {
27
+ service: Server,
28
+ database: Database,
29
+ bucket: HardDrive,
30
+ queue: ListOrdered,
31
+ };
32
+ export const groupByLabels: Record<ResourceGroupBy, string> = { kind: 'Kind', region: 'Region', team: 'Team' };
33
+
34
+ export const resourceTreeBreadcrumbs: BreadcrumbItem[] = [
35
+ { label: 'Acme Industries', href: '/overview' },
36
+ { label: 'Resources' },
37
+ ];
38
+
39
+ export const treeResources: TreeResource[] = [
40
+ {
41
+ id: 'r1',
42
+ name: 'orders-api',
43
+ kind: 'service',
44
+ team: 'Commerce',
45
+ project: 'checkout',
46
+ region: 'eu-west-1',
47
+ status: 'healthy',
48
+ tags: ['production', 'pci', 'tier-1'],
49
+ updatedAt: '2026-09-30',
50
+ },
51
+ {
52
+ id: 'r2',
53
+ name: 'orders-db',
54
+ kind: 'database',
55
+ team: 'Commerce',
56
+ project: 'checkout',
57
+ region: 'eu-west-1',
58
+ status: 'healthy',
59
+ tags: ['production', 'pci', 'backups', 'encrypted'],
60
+ updatedAt: '2026-09-28',
61
+ },
62
+ {
63
+ id: 'r3',
64
+ name: 'payment-events',
65
+ kind: 'queue',
66
+ team: 'Commerce',
67
+ project: 'checkout',
68
+ region: 'eu-west-1',
69
+ status: 'degraded',
70
+ tags: ['production', 'on-call'],
71
+ updatedAt: '2026-10-02',
72
+ },
73
+ {
74
+ id: 'r4',
75
+ name: 'catalog-web',
76
+ kind: 'service',
77
+ team: 'Commerce',
78
+ project: 'catalog',
79
+ region: 'us-east-1',
80
+ status: 'healthy',
81
+ tags: ['production'],
82
+ updatedAt: '2026-09-21',
83
+ },
84
+ {
85
+ id: 'r5',
86
+ name: 'catalog-images',
87
+ kind: 'bucket',
88
+ team: 'Commerce',
89
+ project: 'catalog',
90
+ region: 'us-east-1',
91
+ status: 'healthy',
92
+ tags: ['public', 'cdn', 'production'],
93
+ updatedAt: '2026-09-12',
94
+ },
95
+ {
96
+ id: 'r6',
97
+ name: 'usage-collector',
98
+ kind: 'service',
99
+ team: 'Platform',
100
+ project: 'metering',
101
+ region: 'eu-west-1',
102
+ status: 'healthy',
103
+ tags: ['internal', 'tier-1'],
104
+ updatedAt: '2026-09-29',
105
+ },
106
+ {
107
+ id: 'r7',
108
+ name: 'usage-warehouse',
109
+ kind: 'database',
110
+ team: 'Platform',
111
+ project: 'metering',
112
+ region: 'eu-west-1',
113
+ status: 'stopped',
114
+ tags: ['analytics', 'staging'],
115
+ updatedAt: '2026-08-30',
116
+ },
117
+ {
118
+ id: 'r8',
119
+ name: 'audit-archive',
120
+ kind: 'bucket',
121
+ team: 'Platform',
122
+ project: 'compliance',
123
+ region: 'ap-south-1',
124
+ status: 'healthy',
125
+ tags: ['retention-7y', 'encrypted', 'worm', 'legal-hold'],
126
+ updatedAt: '2026-09-02',
127
+ },
128
+ {
129
+ id: 'r9',
130
+ name: 'export-jobs',
131
+ kind: 'queue',
132
+ team: 'Platform',
133
+ project: 'compliance',
134
+ region: 'ap-south-1',
135
+ status: 'healthy',
136
+ tags: ['internal'],
137
+ updatedAt: '2026-09-25',
138
+ },
139
+ ];
140
+
141
+ /** Team → project tree with resource counts. Node ids: `team:<name>` and `project:<name>`. */
142
+ export function buildResourceTree(rows: readonly TreeResource[]): TreeNode[] {
143
+ const teams = [...new Set(rows.map((r) => r.team))];
144
+ return teams.map((team) => {
145
+ const inTeam = rows.filter((r) => r.team === team);
146
+ const projects = [...new Set(inTeam.map((r) => r.project))];
147
+ return {
148
+ id: `team:${team}`,
149
+ label: team,
150
+ icon: Building2,
151
+ meta: String(inTeam.length),
152
+ children: projects.map((p) => ({
153
+ id: `project:${p}`,
154
+ label: p,
155
+ icon: FolderKanban,
156
+ meta: String(inTeam.filter((r) => r.project === p).length),
157
+ })),
158
+ };
159
+ });
160
+ }
161
+
162
+ /** Rows under a tree node id (`team:…`, `project:…`); `null` = all. */
163
+ export function rowsUnder(rows: readonly TreeResource[], nodeId: string | null): TreeResource[] {
164
+ if (!nodeId) return [...rows];
165
+ const [type, name] = nodeId.split(':');
166
+ return rows.filter((r) => (type === 'team' ? r.team === name : r.project === name));
167
+ }
168
+
169
+ /** Groups rows by a field, in first-seen order. */
170
+ export function groupRows(
171
+ rows: readonly TreeResource[],
172
+ by: ResourceGroupBy,
173
+ ): Array<{ key: string; label: string; rows: TreeResource[] }> {
174
+ const keys = [...new Set(rows.map((r) => r[by]))];
175
+ return keys.map((key) => ({
176
+ key,
177
+ label: by === 'kind' ? kindLabels[key as ResourceKind] : key,
178
+ rows: rows.filter((r) => r[by] === key),
179
+ }));
180
+ }
@@ -0,0 +1,12 @@
1
+ import type { TemplateMeta } from '../meta';
2
+
3
+ export const meta: TemplateMeta = {
4
+ name: 'Resource tree',
5
+ family: 'Resources',
6
+ priority: 'P3',
7
+ status: 'experimental',
8
+ description:
9
+ 'Resource index with a team → project TreeList (counts, single selection) beside grouped DataTables (group by kind, region or team; one SectionHeader + table per group), StatusTag status and OverflowList tags.',
10
+ layout: 'SidebarLayout · Page',
11
+ blocks: ['app-sidebar', 'app-topbar', 'page-header', 'section-header', 'data-table'],
12
+ };
@@ -0,0 +1,56 @@
1
+ import { render, screen, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { expectNoAxeViolations } from '../test/a11y';
4
+ import RunTracePage from './Page';
5
+
6
+ // Dock the inspector as on a large screen (jsdom matches no media query).
7
+ const originalMatchMedia = window.matchMedia;
8
+ beforeAll(() => {
9
+ window.matchMedia = (query: string) =>
10
+ ({ ...originalMatchMedia(query), matches: query.includes('min-width') }) as MediaQueryList;
11
+ });
12
+ afterAll(() => {
13
+ window.matchMedia = originalMatchMedia;
14
+ });
15
+
16
+ describe('RunTracePage', () => {
17
+ it('renders the run header, waterfall, cost, log and the span inspector', { timeout: 15000 }, async () => {
18
+ render(<RunTracePage />);
19
+ expect(screen.getByRole('main')).toBeInTheDocument();
20
+ expect(screen.getByRole('heading', { level: 1, name: 'Answer support question' })).toBeInTheDocument();
21
+ expect(screen.getByRole('heading', { level: 2, name: 'Spans' })).toBeInTheDocument();
22
+ expect(screen.getByRole('heading', { level: 2, name: 'Tokens and cost' })).toBeInTheDocument();
23
+ const panel = screen.getByRole('complementary', { name: 'Span inspector' });
24
+ // The first running child span is selected: live timer, input JSON, no output yet.
25
+ expect(within(panel).getByText('generate answer')).toBeInTheDocument();
26
+ expect(within(panel).getByRole('timer', { name: 'generate answer elapsed time' })).toBeInTheDocument();
27
+ expect(within(panel).getByRole('tree', { name: 'generate answer input' })).toBeInTheDocument();
28
+ expect(within(panel).getByText('Waiting for the span to finish…')).toBeInTheDocument();
29
+ await expectNoAxeViolations();
30
+ });
31
+
32
+ it('selecting a span shows its input and output', { timeout: 15000 }, async () => {
33
+ const onSelectSpan = vi.fn();
34
+ render(<RunTracePage onSelectSpan={onSelectSpan} />);
35
+ await userEvent.click(screen.getByRole('button', { name: /lookup_order \(retry\)/ }));
36
+ expect(onSelectSpan).toHaveBeenCalledWith(expect.objectContaining({ id: 'lookup-retry' }));
37
+ const panel = screen.getByRole('complementary', { name: 'Span inspector' });
38
+ expect(within(panel).getByRole('tree', { name: 'tool: lookup_order (retry) output' })).toHaveTextContent('shipped');
39
+ expect(within(panel).queryByRole('timer')).toBeNull();
40
+ });
41
+
42
+ it('closes the inspector and shows actions only when handlers are given', { timeout: 15000 }, async () => {
43
+ const onRerun = vi.fn();
44
+ render(
45
+ <RunTracePage
46
+ onRerun={onRerun}
47
+ run={{ id: 'run_1', name: 'Nightly eval', status: 'succeeded', model: 'm', environment: 'staging' }}
48
+ />,
49
+ );
50
+ expect(screen.queryByRole('button', { name: 'Cancel run' })).toBeNull();
51
+ await userEvent.click(screen.getAllByRole('button', { name: 'Re-run' })[0]!);
52
+ expect(onRerun).toHaveBeenCalledOnce();
53
+ await userEvent.click(screen.getByRole('button', { name: 'Toggle span inspector' }));
54
+ expect(screen.queryByRole('complementary', { name: 'Span inspector' })).toBeNull();
55
+ });
56
+ });
@@ -0,0 +1,242 @@
1
+ import {
2
+ LogViewer,
3
+ PageHeader,
4
+ TokenCostCard,
5
+ TraceWaterfall,
6
+ formatSpanDuration,
7
+ type LogLine,
8
+ type TokenUsagePeriod,
9
+ type TraceSpan,
10
+ } from '@gntik-ai/blocks';
11
+ import { RotateCcw, Square } from '@gntik-ai/icons';
12
+ import {
13
+ Badge,
14
+ Grid,
15
+ HStack,
16
+ InspectorLayout,
17
+ InspectorToggle,
18
+ JsonViewer,
19
+ Page,
20
+ Stack,
21
+ StatusTag,
22
+ Timer,
23
+ Timestamp,
24
+ type BreadcrumbItem,
25
+ } from '@gntik-ai/ui';
26
+ import { useState } from 'react';
27
+ import { ConsoleShell, type ConsoleShellProps } from '../shared/ConsoleShell';
28
+ import {
29
+ traceBreadcrumbs,
30
+ traceLogs,
31
+ traceRun,
32
+ traceSpanDetails,
33
+ traceSpans,
34
+ traceUsage,
35
+ type SpanDetail,
36
+ type TraceRun,
37
+ } from './data';
38
+
39
+ export interface RunTraceProps {
40
+ /** The run: name, status, start time, model, environment. Omit `startedAt` to start "now". */
41
+ run: Omit<TraceRun, 'startedAt'> & { startedAt?: string };
42
+ spans: TraceSpan[];
43
+ /** Inputs, outputs and running state per span id. */
44
+ spanDetails: Record<string, SpanDetail>;
45
+ logs: LogLine[];
46
+ /** Token usage and cost of the run (one period hides the period switch). */
47
+ usage: TokenUsagePeriod[];
48
+ currency: string;
49
+ /** Span selected initially (defaults to the first running span, else the root). */
50
+ defaultSelectedId: string;
51
+ onSelectSpan: (span: TraceSpan) => void;
52
+ /** Header actions; omitted ones are hidden. */
53
+ onCancel: () => void;
54
+ onRerun: () => void;
55
+ breadcrumbs: BreadcrumbItem[];
56
+ currentHref: string;
57
+ /** ConsoleShell props (app nav, user, workspaces, sidebar footer, topbar…). */
58
+ shell: Omit<ConsoleShellProps, 'children'>;
59
+ }
60
+
61
+ function SpanPanel({
62
+ span,
63
+ detail,
64
+ runStart,
65
+ }: {
66
+ span: TraceSpan | undefined;
67
+ detail: SpanDetail | undefined;
68
+ runStart: number;
69
+ }) {
70
+ if (!span) return <p className="text-[13px] text-muted-foreground">Select a span to see its inputs and outputs.</p>;
71
+ const running = detail?.running ?? false;
72
+ return (
73
+ <Stack gap={4}>
74
+ <div>
75
+ <h3 className="font-mono text-[13px] font-semibold text-foreground">{span.name}</h3>
76
+ <HStack gap={2} wrap className="mt-1.5">
77
+ <Badge size="sm" className="font-mono">
78
+ {span.kind}
79
+ </Badge>
80
+ <StatusTag size="sm" status={running ? 'running' : span.status === 'error' ? 'failed' : 'succeeded'} />
81
+ </HStack>
82
+ </div>
83
+ <dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-[12.5px]">
84
+ <dt className="text-muted-foreground">Started</dt>
85
+ <dd className="font-mono">+{formatSpanDuration(span.start)}</dd>
86
+ <dt className="text-muted-foreground">{running ? 'Elapsed' : 'Duration'}</dt>
87
+ <dd>
88
+ {running ? (
89
+ <Timer start={runStart + span.start} size="sm" aria-label={`${span.name} elapsed time`} />
90
+ ) : (
91
+ <span className="font-mono">{formatSpanDuration(span.duration)}</span>
92
+ )}
93
+ </dd>
94
+ {Object.entries(span.attributes ?? {}).map(([key, value]) => (
95
+ <div key={key} className="contents">
96
+ <dt className="font-mono text-muted-foreground">{key}</dt>
97
+ <dd className="min-w-0 truncate font-mono">{value}</dd>
98
+ </div>
99
+ ))}
100
+ </dl>
101
+ <section aria-label="Input">
102
+ <h3 className="mb-2 text-[12px] font-semibold tracking-wide text-muted-foreground uppercase">Input</h3>
103
+ {detail?.input === undefined ? (
104
+ <p className="text-[12.5px] text-muted-foreground">No input recorded.</p>
105
+ ) : (
106
+ <JsonViewer
107
+ key={`${span.id}-in`}
108
+ data={detail.input}
109
+ label={`${span.name} input`}
110
+ defaultExpandDepth={2}
111
+ maxHeight={240}
112
+ />
113
+ )}
114
+ </section>
115
+ <section aria-label="Output">
116
+ <h3 className="mb-2 text-[12px] font-semibold tracking-wide text-muted-foreground uppercase">Output</h3>
117
+ {detail?.output === undefined ? (
118
+ <p className="text-[12.5px] text-muted-foreground">
119
+ {running ? 'Waiting for the span to finish…' : 'No output recorded.'}
120
+ </p>
121
+ ) : (
122
+ <JsonViewer
123
+ key={`${span.id}-out`}
124
+ data={detail.output}
125
+ label={`${span.name} output`}
126
+ defaultExpandDepth={2}
127
+ maxHeight={240}
128
+ />
129
+ )}
130
+ </section>
131
+ </Stack>
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Run trace: ConsoleShell + InspectorLayout. The main area holds the run header, the
137
+ * TraceWaterfall, token cost and the run log; the inspector shows the selected span's
138
+ * attributes, a live Timer while it runs and its input / output in JsonViewers.
139
+ */
140
+ export default function RunTracePage(props: Partial<RunTraceProps>) {
141
+ const {
142
+ run = traceRun,
143
+ spans = traceSpans,
144
+ spanDetails = traceSpanDetails,
145
+ logs = traceLogs,
146
+ usage = traceUsage,
147
+ currency = 'USD',
148
+ defaultSelectedId,
149
+ onSelectSpan,
150
+ onCancel,
151
+ onRerun,
152
+ breadcrumbs = traceBreadcrumbs,
153
+ currentHref = '/runs',
154
+ shell,
155
+ } = props;
156
+ const extent = Math.max(0, ...spans.map((s) => s.start + s.duration));
157
+ // Without a start time the demo run "started" `extent` ms before mount, so timers read sensibly.
158
+ const [fallbackStart] = useState(() => Date.now() - extent);
159
+ const runStart = run.startedAt ? new Date(run.startedAt).getTime() : fallbackStart;
160
+ const firstRunning = spans.find((s) => spanDetails[s.id]?.running && s.parentId);
161
+ const [selectedId, setSelectedId] = useState(
162
+ defaultSelectedId ?? firstRunning?.id ?? spans.find((s) => !s.parentId)?.id,
163
+ );
164
+ const [inspectorOpen, setInspectorOpen] = useState(true);
165
+ const selected = spans.find((s) => s.id === selectedId);
166
+ const isRunning = run.status === 'running';
167
+
168
+ const actions = [
169
+ ...(onCancel && isRunning
170
+ ? [{ label: 'Cancel run', icon: Square, variant: 'secondary' as const, onClick: onCancel }]
171
+ : []),
172
+ ...(onRerun ? [{ label: 'Re-run', icon: RotateCcw, variant: 'primary' as const, onClick: onRerun }] : []),
173
+ ];
174
+
175
+ return (
176
+ <ConsoleShell breadcrumbs={breadcrumbs} currentHref={currentHref} {...shell}>
177
+ <InspectorLayout
178
+ panelTitle="Span inspector"
179
+ panelWidth={360}
180
+ open={inspectorOpen}
181
+ onOpenChange={setInspectorOpen}
182
+ panel={
183
+ <SpanPanel span={selected} detail={selectedId ? spanDetails[selectedId] : undefined} runStart={runStart} />
184
+ }
185
+ >
186
+ <Page
187
+ width="wide"
188
+ header={
189
+ <PageHeader
190
+ breadcrumbs={null}
191
+ title={run.name}
192
+ status={run.status}
193
+ meta={[
194
+ { label: 'Run', value: run.id, mono: true },
195
+ { label: 'Started', value: <Timestamp value={new Date(runStart)} /> },
196
+ {
197
+ label: isRunning ? 'Elapsed' : 'Duration',
198
+ value: isRunning ? (
199
+ <Timer start={runStart} format="compact" showStatus={false} aria-label="Run elapsed time" />
200
+ ) : (
201
+ formatSpanDuration(extent)
202
+ ),
203
+ },
204
+ { label: 'Model', value: run.model, mono: true },
205
+ { label: 'Environment', value: run.environment },
206
+ ]}
207
+ tabs={null}
208
+ actions={actions}
209
+ />
210
+ }
211
+ >
212
+ <TraceWaterfall
213
+ titleAs="h2"
214
+ title="Spans"
215
+ spans={spans}
216
+ defaultSelectedId={selectedId}
217
+ onSelect={(span) => {
218
+ setSelectedId(span.id);
219
+ setInspectorOpen(true);
220
+ onSelectSpan?.(span);
221
+ }}
222
+ />
223
+ <HStack justify="end">
224
+ <InspectorToggle label="Toggle span inspector" />
225
+ </HStack>
226
+ <Grid cols={{ base: 1, lg: 3 }} gap={4}>
227
+ <TokenCostCard
228
+ titleAs="h2"
229
+ title="Tokens and cost"
230
+ description={`${run.model} · this run`}
231
+ periods={usage}
232
+ currency={currency}
233
+ />
234
+ <div className="min-w-0 lg:col-span-2">
235
+ <LogViewer titleAs="h2" title="Run log" lines={logs} height={260} defaultFollow />
236
+ </div>
237
+ </Grid>
238
+ </Page>
239
+ </InspectorLayout>
240
+ </ConsoleShell>
241
+ );
242
+ }
@@ -0,0 +1,171 @@
1
+ import type { LogLine, TokenUsagePeriod, TraceSpan } from '@gntik-ai/blocks';
2
+ import type { BreadcrumbItem } from '@gntik-ai/ui';
3
+
4
+ /** Inputs, outputs and live state of one span, shown in the inspector. */
5
+ export interface SpanDetail {
6
+ /** JSON-serialisable input of the span (request, prompt, tool arguments…). */
7
+ input?: unknown;
8
+ /** JSON-serialisable output; omit while the span is still running. */
9
+ output?: unknown;
10
+ /** The span has not finished yet: the inspector shows a live Timer. */
11
+ running?: boolean;
12
+ }
13
+
14
+ /** Summary of the run the trace belongs to. */
15
+ export interface TraceRun {
16
+ id: string;
17
+ name: string;
18
+ /** StatusTag key: running, succeeded, failed… */
19
+ status: string;
20
+ /** When the run started (ISO). Running spans time themselves from `startedAt + span.start`. */
21
+ startedAt: string;
22
+ model: string;
23
+ environment: string;
24
+ }
25
+
26
+ export const traceRun: TraceRun = {
27
+ id: 'run_8c41f2',
28
+ name: 'Answer support question',
29
+ status: 'running',
30
+ startedAt: '2026-05-04T09:12:03.000Z',
31
+ model: 'general-large',
32
+ environment: 'production',
33
+ };
34
+
35
+ export const traceBreadcrumbs: BreadcrumbItem[] = [
36
+ { label: 'Runs', href: '/runs' },
37
+ { label: traceRun.id, mono: true },
38
+ ];
39
+
40
+ export const traceSpans: TraceSpan[] = [
41
+ {
42
+ id: 'root',
43
+ name: 'POST /v1/answer',
44
+ kind: 'http',
45
+ start: 0,
46
+ duration: 4200,
47
+ status: 'ok',
48
+ attributes: { 'http.method': 'POST', 'request.id': 'req_7f3a' },
49
+ },
50
+ {
51
+ id: 'retrieve',
52
+ parentId: 'root',
53
+ name: 'retrieve context',
54
+ kind: 'retrieval',
55
+ start: 40,
56
+ duration: 380,
57
+ status: 'ok',
58
+ attributes: { documents: 6 },
59
+ },
60
+ {
61
+ id: 'embed',
62
+ parentId: 'retrieve',
63
+ name: 'embed query',
64
+ kind: 'llm',
65
+ start: 45,
66
+ duration: 115,
67
+ status: 'ok',
68
+ attributes: { model: 'embed-small', 'tokens.input': 24 },
69
+ },
70
+ {
71
+ id: 'search',
72
+ parentId: 'retrieve',
73
+ name: 'vector search',
74
+ kind: 'retrieval',
75
+ start: 170,
76
+ duration: 240,
77
+ status: 'ok',
78
+ attributes: { index: 'docs-v3', topK: 6 },
79
+ },
80
+ {
81
+ id: 'lookup',
82
+ parentId: 'root',
83
+ name: 'tool: lookup_order',
84
+ kind: 'tool',
85
+ start: 440,
86
+ duration: 150,
87
+ status: 'error',
88
+ attributes: { error: 'timeout after 150ms' },
89
+ },
90
+ {
91
+ id: 'lookup-retry',
92
+ parentId: 'root',
93
+ name: 'tool: lookup_order (retry)',
94
+ kind: 'tool',
95
+ start: 600,
96
+ duration: 240,
97
+ status: 'ok',
98
+ attributes: { attempt: 2 },
99
+ },
100
+ {
101
+ id: 'generate',
102
+ parentId: 'root',
103
+ name: 'generate answer',
104
+ kind: 'llm',
105
+ start: 860,
106
+ duration: 3340,
107
+ status: 'ok',
108
+ attributes: { model: 'general-large', temperature: 0.2 },
109
+ },
110
+ ];
111
+
112
+ export const traceSpanDetails: Record<string, SpanDetail> = {
113
+ root: {
114
+ input: {
115
+ method: 'POST',
116
+ path: '/v1/answer',
117
+ body: { question: 'Where is my order 10482?', customerId: 'cus_2291' },
118
+ },
119
+ running: true,
120
+ },
121
+ retrieve: {
122
+ input: { query: 'Where is my order 10482?', topK: 6 },
123
+ output: { documents: ['shipping-policy.md', 'order-status.md', 'returns.md'], scores: [0.91, 0.88, 0.62] },
124
+ },
125
+ embed: { input: { model: 'embed-small', text: 'Where is my order 10482?' }, output: { dimensions: 768, tokens: 24 } },
126
+ search: { input: { index: 'docs-v3', topK: 6 }, output: { hits: 6, tookMs: 236 } },
127
+ lookup: {
128
+ input: { tool: 'lookup_order', arguments: { orderId: '10482' } },
129
+ output: { error: { code: 'timeout', message: 'timeout after 150ms' } },
130
+ },
131
+ 'lookup-retry': {
132
+ input: { tool: 'lookup_order', arguments: { orderId: '10482' }, attempt: 2 },
133
+ output: { orderId: '10482', status: 'shipped', carrier: 'Parcel Co', eta: '2026-05-06' },
134
+ },
135
+ generate: {
136
+ input: {
137
+ model: 'general-large',
138
+ temperature: 0.2,
139
+ messages: [
140
+ { role: 'system', content: 'Answer using the retrieved documents and tool results.' },
141
+ { role: 'user', content: 'Where is my order 10482?' },
142
+ ],
143
+ },
144
+ running: true,
145
+ },
146
+ };
147
+
148
+ export const traceUsage: TokenUsagePeriod[] = [
149
+ { id: 'run', label: 'This run', inputTokens: 4_180, outputTokens: 312, cost: 0.021, budget: 0.05 },
150
+ ];
151
+
152
+ const LOGS: Array<[LogLine['level'], string, string]> = [
153
+ ['info', 'gateway', 'POST /v1/answer accepted (req_7f3a)'],
154
+ ['debug', 'retriever', 'embedding query with embed-small (24 tokens)'],
155
+ ['info', 'retriever', 'vector search on docs-v3 returned 6 hits in 236ms'],
156
+ ['debug', 'agent', 'calling tool lookup_order {"orderId":"10482"}'],
157
+ ['error', 'tools', 'lookup_order timed out after 150ms'],
158
+ ['warn', 'agent', 'retrying lookup_order (attempt 2 of 3)'],
159
+ ['info', 'tools', 'lookup_order returned status=shipped'],
160
+ ['info', 'agent', 'generating answer with general-large (temperature 0.2)'],
161
+ ['debug', 'agent', 'streaming tokens…'],
162
+ ];
163
+
164
+ /** Run log, oldest first. */
165
+ export const traceLogs: LogLine[] = LOGS.map(([level, source, message], i) => ({
166
+ id: `log-${i}`,
167
+ timestamp: `09:12:0${3 + Math.floor(i / 3)}.${String(100 + i * 87).padStart(3, '0')}`,
168
+ level,
169
+ source,
170
+ message,
171
+ }));
@@ -0,0 +1,12 @@
1
+ import type { TemplateMeta } from '../meta';
2
+
3
+ export const meta: TemplateMeta = {
4
+ name: 'Run trace',
5
+ family: 'AI',
6
+ priority: 'P3',
7
+ status: 'experimental',
8
+ description:
9
+ 'Trace of one AI run inside the console: run header with a live elapsed Timer, TraceWaterfall of spans, token cost and the run log; a pinnable inspector shows the selected span with a live Timer while it runs and its input / output in JsonViewers.',
10
+ layout: 'InspectorLayout',
11
+ blocks: ['app-sidebar', 'app-topbar', 'page-header', 'trace-waterfall', 'token-cost-card', 'log-viewer'],
12
+ };