@corbits/react-ui 0.1.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 (255) hide show
  1. package/LICENSE +501 -0
  2. package/README.md +125 -0
  3. package/dist/blocks/login/auth-layout.d.ts +38 -0
  4. package/dist/blocks/login/auth-layout.js +52 -0
  5. package/dist/blocks/login/login-form.d.ts +60 -0
  6. package/dist/blocks/login/login-form.js +151 -0
  7. package/dist/hooks/use-command-palette-navigation.d.ts +25 -0
  8. package/dist/hooks/use-command-palette-navigation.js +52 -0
  9. package/dist/hooks/use-controllable-state.d.ts +28 -0
  10. package/dist/hooks/use-controllable-state.js +44 -0
  11. package/dist/hooks/use-delayed-autofocus.d.ts +9 -0
  12. package/dist/hooks/use-delayed-autofocus.js +20 -0
  13. package/dist/hooks/use-dismissable-popover.d.ts +37 -0
  14. package/dist/hooks/use-dismissable-popover.js +64 -0
  15. package/dist/hooks/use-list-selection.d.ts +66 -0
  16. package/dist/hooks/use-list-selection.js +147 -0
  17. package/dist/hooks/use-prefers-reduced-motion.d.ts +13 -0
  18. package/dist/hooks/use-prefers-reduced-motion.js +28 -0
  19. package/dist/hooks/use-scroll-current-into-view.d.ts +19 -0
  20. package/dist/hooks/use-scroll-current-into-view.js +33 -0
  21. package/dist/index.d.ts +122 -0
  22. package/dist/index.js +131 -0
  23. package/dist/lib/artifact.d.ts +72 -0
  24. package/dist/lib/artifact.js +67 -0
  25. package/dist/lib/chart-geometry.d.ts +62 -0
  26. package/dist/lib/chart-geometry.js +86 -0
  27. package/dist/lib/chart-palette.d.ts +76 -0
  28. package/dist/lib/chart-palette.js +92 -0
  29. package/dist/lib/chat-message.d.ts +115 -0
  30. package/dist/lib/chat-message.js +41 -0
  31. package/dist/lib/chat-parts.d.ts +64 -0
  32. package/dist/lib/chat-parts.js +16 -0
  33. package/dist/lib/csv.d.ts +60 -0
  34. package/dist/lib/csv.js +108 -0
  35. package/dist/lib/metrics.d.ts +70 -0
  36. package/dist/lib/metrics.js +101 -0
  37. package/dist/lib/now-item.d.ts +65 -0
  38. package/dist/lib/now-item.js +59 -0
  39. package/dist/lib/relative-time.d.ts +9 -0
  40. package/dist/lib/relative-time.js +47 -0
  41. package/dist/lib/theme.d.ts +30 -0
  42. package/dist/lib/theme.js +77 -0
  43. package/dist/lib/url.d.ts +26 -0
  44. package/dist/lib/url.js +37 -0
  45. package/dist/lib/utils.d.ts +2 -0
  46. package/dist/lib/utils.js +5 -0
  47. package/dist/lib/workflow-display-flow.d.ts +83 -0
  48. package/dist/lib/workflow-display-flow.js +104 -0
  49. package/dist/lib/workflow-registry.d.ts +90 -0
  50. package/dist/lib/workflow-registry.js +56 -0
  51. package/dist/lib/workflow-run-error.d.ts +31 -0
  52. package/dist/lib/workflow-run-error.js +105 -0
  53. package/dist/lib/workflow-run-progress.d.ts +97 -0
  54. package/dist/lib/workflow-run-progress.js +135 -0
  55. package/dist/lib/workflow-run.d.ts +43 -0
  56. package/dist/lib/workflow-run.js +56 -0
  57. package/dist/styles.css +3080 -0
  58. package/dist/test/render-hook.d.ts +14 -0
  59. package/dist/test/render-hook.js +38 -0
  60. package/dist/theme.css +628 -0
  61. package/dist/ui/agent-turn.d.ts +26 -0
  62. package/dist/ui/agent-turn.js +60 -0
  63. package/dist/ui/animated-number.d.ts +25 -0
  64. package/dist/ui/animated-number.js +63 -0
  65. package/dist/ui/approval-card.d.ts +46 -0
  66. package/dist/ui/approval-card.js +116 -0
  67. package/dist/ui/artifact-body.d.ts +26 -0
  68. package/dist/ui/artifact-body.js +170 -0
  69. package/dist/ui/artifact-notice.d.ts +21 -0
  70. package/dist/ui/artifact-notice.js +36 -0
  71. package/dist/ui/avatar.d.ts +37 -0
  72. package/dist/ui/avatar.js +73 -0
  73. package/dist/ui/badge.d.ts +10 -0
  74. package/dist/ui/badge.js +35 -0
  75. package/dist/ui/bar-chart.d.ts +38 -0
  76. package/dist/ui/bar-chart.js +118 -0
  77. package/dist/ui/block-card.d.ts +29 -0
  78. package/dist/ui/block-card.js +66 -0
  79. package/dist/ui/boot-screen.d.ts +23 -0
  80. package/dist/ui/boot-screen.js +49 -0
  81. package/dist/ui/bulk-action-bar.d.ts +37 -0
  82. package/dist/ui/bulk-action-bar.js +64 -0
  83. package/dist/ui/button.d.ts +12 -0
  84. package/dist/ui/button.js +73 -0
  85. package/dist/ui/card.d.ts +6 -0
  86. package/dist/ui/card.js +37 -0
  87. package/dist/ui/category-bars.d.ts +20 -0
  88. package/dist/ui/category-bars.js +69 -0
  89. package/dist/ui/chart-frame.d.ts +54 -0
  90. package/dist/ui/chart-frame.js +108 -0
  91. package/dist/ui/chat-composer.d.ts +24 -0
  92. package/dist/ui/chat-composer.js +32 -0
  93. package/dist/ui/chat-dock-timing.d.ts +15 -0
  94. package/dist/ui/chat-dock-timing.js +11 -0
  95. package/dist/ui/chat-input.d.ts +38 -0
  96. package/dist/ui/chat-input.js +138 -0
  97. package/dist/ui/chat-panel.d.ts +20 -0
  98. package/dist/ui/chat-panel.js +56 -0
  99. package/dist/ui/chat-thread.d.ts +28 -0
  100. package/dist/ui/chat-thread.js +61 -0
  101. package/dist/ui/checkbox.d.ts +25 -0
  102. package/dist/ui/checkbox.js +63 -0
  103. package/dist/ui/command-palette.d.ts +100 -0
  104. package/dist/ui/command-palette.js +341 -0
  105. package/dist/ui/compare-body.d.ts +52 -0
  106. package/dist/ui/compare-body.js +140 -0
  107. package/dist/ui/confirm-button.d.ts +29 -0
  108. package/dist/ui/confirm-button.js +41 -0
  109. package/dist/ui/corbits-mark.d.ts +29 -0
  110. package/dist/ui/corbits-mark.js +33 -0
  111. package/dist/ui/csv-table.d.ts +22 -0
  112. package/dist/ui/csv-table.js +85 -0
  113. package/dist/ui/dialog.d.ts +37 -0
  114. package/dist/ui/dialog.js +105 -0
  115. package/dist/ui/dictation-status-line.d.ts +20 -0
  116. package/dist/ui/dictation-status-line.js +38 -0
  117. package/dist/ui/dither-canvas.d.ts +31 -0
  118. package/dist/ui/dither-canvas.js +169 -0
  119. package/dist/ui/embed-body.d.ts +30 -0
  120. package/dist/ui/embed-body.js +92 -0
  121. package/dist/ui/empty-state.d.ts +26 -0
  122. package/dist/ui/empty-state.js +42 -0
  123. package/dist/ui/file-input.d.ts +25 -0
  124. package/dist/ui/file-input.js +66 -0
  125. package/dist/ui/filter-bar.d.ts +42 -0
  126. package/dist/ui/filter-bar.js +95 -0
  127. package/dist/ui/filter-chip.d.ts +14 -0
  128. package/dist/ui/filter-chip.js +23 -0
  129. package/dist/ui/gate-block.d.ts +16 -0
  130. package/dist/ui/gate-block.js +37 -0
  131. package/dist/ui/horizontal-stepper.d.ts +13 -0
  132. package/dist/ui/horizontal-stepper.js +65 -0
  133. package/dist/ui/input.d.ts +2 -0
  134. package/dist/ui/input.js +14 -0
  135. package/dist/ui/inspector-shell.d.ts +47 -0
  136. package/dist/ui/inspector-shell.js +98 -0
  137. package/dist/ui/intake-form.d.ts +39 -0
  138. package/dist/ui/intake-form.js +119 -0
  139. package/dist/ui/kind-card-grid.d.ts +23 -0
  140. package/dist/ui/kind-card-grid.js +43 -0
  141. package/dist/ui/library-page-header.d.ts +20 -0
  142. package/dist/ui/library-page-header.js +43 -0
  143. package/dist/ui/list-detail.d.ts +28 -0
  144. package/dist/ui/list-detail.js +55 -0
  145. package/dist/ui/live-run-banner.d.ts +13 -0
  146. package/dist/ui/live-run-banner.js +23 -0
  147. package/dist/ui/live-status-line.d.ts +22 -0
  148. package/dist/ui/live-status-line.js +37 -0
  149. package/dist/ui/menu.d.ts +18 -0
  150. package/dist/ui/menu.js +45 -0
  151. package/dist/ui/message-bubble.d.ts +24 -0
  152. package/dist/ui/message-bubble.js +64 -0
  153. package/dist/ui/mic-button.d.ts +35 -0
  154. package/dist/ui/mic-button.js +61 -0
  155. package/dist/ui/mic-permission-dialog.d.ts +20 -0
  156. package/dist/ui/mic-permission-dialog.js +50 -0
  157. package/dist/ui/notifications-bell.d.ts +28 -0
  158. package/dist/ui/notifications-bell.js +69 -0
  159. package/dist/ui/now-cards.d.ts +37 -0
  160. package/dist/ui/now-cards.js +116 -0
  161. package/dist/ui/page-panel.d.ts +37 -0
  162. package/dist/ui/page-panel.js +23 -0
  163. package/dist/ui/page-shell.d.ts +21 -0
  164. package/dist/ui/page-shell.js +26 -0
  165. package/dist/ui/parts-renderer.d.ts +22 -0
  166. package/dist/ui/parts-renderer.js +234 -0
  167. package/dist/ui/profile-card.d.ts +39 -0
  168. package/dist/ui/profile-card.js +124 -0
  169. package/dist/ui/progress-checklist.d.ts +25 -0
  170. package/dist/ui/progress-checklist.js +70 -0
  171. package/dist/ui/provider-mark.d.ts +32 -0
  172. package/dist/ui/provider-mark.js +31 -0
  173. package/dist/ui/quick-reply-chips.d.ts +14 -0
  174. package/dist/ui/quick-reply-chips.js +24 -0
  175. package/dist/ui/quote-card.d.ts +39 -0
  176. package/dist/ui/quote-card.js +66 -0
  177. package/dist/ui/reasoning-block.d.ts +35 -0
  178. package/dist/ui/reasoning-block.js +60 -0
  179. package/dist/ui/research-body.d.ts +70 -0
  180. package/dist/ui/research-body.js +270 -0
  181. package/dist/ui/rich-empty-state.d.ts +35 -0
  182. package/dist/ui/rich-empty-state.js +65 -0
  183. package/dist/ui/run-now-button.d.ts +23 -0
  184. package/dist/ui/run-now-button.js +42 -0
  185. package/dist/ui/section.d.ts +20 -0
  186. package/dist/ui/section.js +48 -0
  187. package/dist/ui/select.d.ts +2 -0
  188. package/dist/ui/select.js +22 -0
  189. package/dist/ui/selection-checkbox.d.ts +42 -0
  190. package/dist/ui/selection-checkbox.js +54 -0
  191. package/dist/ui/settings-panel.d.ts +35 -0
  192. package/dist/ui/settings-panel.js +68 -0
  193. package/dist/ui/shimmer-text.d.ts +19 -0
  194. package/dist/ui/shimmer-text.js +20 -0
  195. package/dist/ui/sidebar-item-row.d.ts +40 -0
  196. package/dist/ui/sidebar-item-row.js +64 -0
  197. package/dist/ui/sidebar-panel.d.ts +25 -0
  198. package/dist/ui/sidebar-panel.js +56 -0
  199. package/dist/ui/sidebar.d.ts +46 -0
  200. package/dist/ui/sidebar.js +111 -0
  201. package/dist/ui/skeleton.d.ts +13 -0
  202. package/dist/ui/skeleton.js +20 -0
  203. package/dist/ui/sortable-table.d.ts +43 -0
  204. package/dist/ui/sortable-table.js +103 -0
  205. package/dist/ui/sparkline.d.ts +27 -0
  206. package/dist/ui/sparkline.js +68 -0
  207. package/dist/ui/stat-grid.d.ts +55 -0
  208. package/dist/ui/stat-grid.js +87 -0
  209. package/dist/ui/status-dot.d.ts +13 -0
  210. package/dist/ui/status-dot.js +51 -0
  211. package/dist/ui/step-list.d.ts +21 -0
  212. package/dist/ui/step-list.js +77 -0
  213. package/dist/ui/switch.d.ts +23 -0
  214. package/dist/ui/switch.js +29 -0
  215. package/dist/ui/table.d.ts +8 -0
  216. package/dist/ui/table.js +48 -0
  217. package/dist/ui/tabs.d.ts +44 -0
  218. package/dist/ui/tabs.js +101 -0
  219. package/dist/ui/textarea.d.ts +8 -0
  220. package/dist/ui/textarea.js +32 -0
  221. package/dist/ui/theme-provider.d.ts +36 -0
  222. package/dist/ui/theme-provider.js +126 -0
  223. package/dist/ui/theme-toggle.d.ts +11 -0
  224. package/dist/ui/theme-toggle.js +32 -0
  225. package/dist/ui/thinking-indicator.d.ts +7 -0
  226. package/dist/ui/thinking-indicator.js +19 -0
  227. package/dist/ui/thinking-label.d.ts +19 -0
  228. package/dist/ui/thinking-label.js +54 -0
  229. package/dist/ui/thinking-mark.d.ts +10 -0
  230. package/dist/ui/thinking-mark.js +153 -0
  231. package/dist/ui/time-series-chart.d.ts +55 -0
  232. package/dist/ui/time-series-chart.js +249 -0
  233. package/dist/ui/toast.d.ts +16 -0
  234. package/dist/ui/toast.js +41 -0
  235. package/dist/ui/token-mosaic.d.ts +22 -0
  236. package/dist/ui/token-mosaic.js +65 -0
  237. package/dist/ui/tool-block.d.ts +54 -0
  238. package/dist/ui/tool-block.js +170 -0
  239. package/dist/ui/tool-narrative.d.ts +22 -0
  240. package/dist/ui/tool-narrative.js +77 -0
  241. package/dist/ui/tool-picker.d.ts +44 -0
  242. package/dist/ui/tool-picker.js +200 -0
  243. package/dist/ui/tooltip.d.ts +21 -0
  244. package/dist/ui/tooltip.js +48 -0
  245. package/dist/ui/top-bar.d.ts +53 -0
  246. package/dist/ui/top-bar.js +98 -0
  247. package/dist/ui/trace-waterfall.d.ts +38 -0
  248. package/dist/ui/trace-waterfall.js +121 -0
  249. package/dist/ui/typing-indicator.d.ts +9 -0
  250. package/dist/ui/typing-indicator.js +25 -0
  251. package/dist/ui/view-toggle.d.ts +17 -0
  252. package/dist/ui/view-toggle.js +46 -0
  253. package/dist/ui/voice-waveform.d.ts +16 -0
  254. package/dist/ui/voice-waveform.js +23 -0
  255. package/package.json +583 -0
@@ -0,0 +1,116 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ import { formatRelativeTime } from "../lib/relative-time.js";
4
+ import { sortNowItems } from "../lib/now-item.js";
5
+ import { Badge } from "./badge.js";
6
+ /**
7
+ * The attention band at the top of the inbox. Its own container rather than the
8
+ * generic `Section` because it says something `Section` cannot: an eyebrow that
9
+ * marks urgency, a count in words, and a collapsed mode.
10
+ *
11
+ * Collapsing keeps the body mounted (so it can animate, and so anchors into it
12
+ * still resolve) but marks it `inert` — `aria-hidden` alone would hide it from
13
+ * a screen reader while leaving every card in the tab order.
14
+ */ export function NowSection({ count, collapsed = false, children, className }) {
15
+ const countLabel = count === 1 ? "1 thing needs you" : `${count} things need you`;
16
+ return /*#__PURE__*/ _jsxs("section", {
17
+ "data-slot": "now-section",
18
+ "aria-label": "Now",
19
+ className: cn("border-b border-border bg-card px-4 py-3 text-card-foreground", className),
20
+ children: [
21
+ /*#__PURE__*/ _jsx("div", {
22
+ className: "flex items-end justify-between gap-2",
23
+ children: /*#__PURE__*/ _jsxs("div", {
24
+ className: "min-w-0",
25
+ children: [
26
+ /*#__PURE__*/ _jsx("p", {
27
+ className: "text-xs font-semibold tracking-[0.14em] text-primary-emphasis uppercase",
28
+ children: "Now"
29
+ }),
30
+ /*#__PURE__*/ _jsx("p", {
31
+ className: "text-sm font-semibold tracking-tight",
32
+ children: count === 0 ? "Clear" : countLabel
33
+ })
34
+ ]
35
+ })
36
+ }),
37
+ /*#__PURE__*/ _jsx("div", {
38
+ className: cn("grid transition-[grid-template-rows,opacity] duration-300", collapsed ? "grid-rows-[0fr] opacity-0" : "mt-3 grid-rows-[1fr] opacity-100"),
39
+ ...collapsed ? {
40
+ inert: true
41
+ } : {},
42
+ children: /*#__PURE__*/ _jsx("div", {
43
+ className: "min-h-0 overflow-hidden",
44
+ children: children
45
+ })
46
+ })
47
+ ]
48
+ });
49
+ }
50
+ /**
51
+ * The highest-attention items as cards. Ordering is not the caller's problem —
52
+ * the list is sorted here by priority then recency, so two surfaces fed the
53
+ * same items always agree on what is most urgent.
54
+ */ export function NowCards({ items, limit = 3, selectedId = null, now, className }) {
55
+ const shown = sortNowItems(items).slice(0, limit);
56
+ if (shown.length === 0) return null;
57
+ return /*#__PURE__*/ _jsx("ul", {
58
+ "aria-label": "Needs you now",
59
+ className: cn("grid gap-2.5 sm:grid-cols-2 lg:grid-cols-3", className),
60
+ children: shown.map((item, index)=>{
61
+ const selected = item.id === selectedId;
62
+ const body = /*#__PURE__*/ _jsxs(_Fragment, {
63
+ children: [
64
+ /*#__PURE__*/ _jsxs("div", {
65
+ className: "flex items-center gap-2",
66
+ children: [
67
+ /*#__PURE__*/ _jsx("span", {
68
+ className: "grid size-5 shrink-0 place-items-center rounded-full bg-primary/15 text-xs font-bold text-primary-emphasis",
69
+ children: index + 1
70
+ }),
71
+ /*#__PURE__*/ _jsx(Badge, {
72
+ tone: item.priority === "now" ? "accent" : "neutral",
73
+ children: item.classification
74
+ }),
75
+ item.type === "mail" && !item.read ? /*#__PURE__*/ _jsx("span", {
76
+ className: "size-2 shrink-0 rounded-full bg-primary",
77
+ "aria-label": "Unread",
78
+ role: "img"
79
+ }) : null,
80
+ /*#__PURE__*/ _jsx("time", {
81
+ dateTime: item.when,
82
+ className: "ml-auto shrink-0 text-xs text-muted-foreground",
83
+ children: formatRelativeTime(item.when, now)
84
+ })
85
+ ]
86
+ }),
87
+ /*#__PURE__*/ _jsx("p", {
88
+ className: "mt-2 line-clamp-2 text-sm leading-snug font-semibold",
89
+ children: item.title
90
+ }),
91
+ item.summary === undefined ? null : /*#__PURE__*/ _jsx("p", {
92
+ className: "mt-1 line-clamp-2 text-xs leading-relaxed text-muted-foreground",
93
+ children: item.summary
94
+ }),
95
+ /*#__PURE__*/ _jsx("p", {
96
+ className: "mt-2.5 text-xs font-medium text-primary-emphasis",
97
+ children: item.type === "gate" ? `${item.action} →` : `From ${item.from}`
98
+ })
99
+ ]
100
+ });
101
+ const shell = cn("block h-full w-full rounded-lg border p-3.5 text-left transition-colors", selected ? "border-primary-emphasis bg-primary/10" : "border-border bg-background hover:bg-muted");
102
+ return /*#__PURE__*/ _jsx("li", {
103
+ children: item.href === undefined ? /*#__PURE__*/ _jsx("div", {
104
+ "aria-current": selected ? "true" : undefined,
105
+ className: shell,
106
+ children: body
107
+ }) : /*#__PURE__*/ _jsx("a", {
108
+ href: item.href,
109
+ "aria-current": selected ? "true" : undefined,
110
+ className: shell,
111
+ children: body
112
+ })
113
+ }, `${item.type}:${item.id}`);
114
+ })
115
+ });
116
+ }
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from "react";
2
+ export type PagePanelProps = {
3
+ readonly children: ReactNode;
4
+ /**
5
+ * When true the inner panel scrolls vertically; when false it clips overflow
6
+ * and leaves scrolling to its own children (e.g. a header + scroll body).
7
+ */
8
+ readonly scroll?: boolean;
9
+ /** Drop the panel's shadow — some surfaces render flatter. */
10
+ readonly flat?: boolean;
11
+ /**
12
+ * Size the panel to its content instead of filling the viewport. Short pages
13
+ * then leave no dead band below their last section. The framing border is
14
+ * dropped in this mode so a content-height panel has no floating edge where
15
+ * it stops — it blends into the identical fill below it. Still capped at the
16
+ * viewport, so overflowing content scrolls as normal.
17
+ */
18
+ readonly fitContent?: boolean;
19
+ /**
20
+ * Fill token for the outer bleed and the panel itself. `background` (the
21
+ * default) is for top-level routes hosted directly under the app shell; pass
22
+ * `card` when the panel is nested inside a surface that already supplies its
23
+ * own ground, so the panel blends in instead of reading as a stray card.
24
+ */
25
+ readonly surface?: "background" | "card";
26
+ readonly className?: string;
27
+ };
28
+ /**
29
+ * The standard page-content frame: a full-height outer fill plus a bordered
30
+ * panel that owns the page's vertical scroll. Shared by the top-level pages so
31
+ * margins, border and background are decided once instead of per page.
32
+ *
33
+ * It is not `PageShell` — that caps and centres a measure of content — and not
34
+ * `Card`, which is a surface within a page. This is the boundary between the
35
+ * app shell and whatever the route renders.
36
+ */
37
+ export declare function PagePanel({ children, scroll, flat, fitContent, surface, className, }: PagePanelProps): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ /**
4
+ * The standard page-content frame: a full-height outer fill plus a bordered
5
+ * panel that owns the page's vertical scroll. Shared by the top-level pages so
6
+ * margins, border and background are decided once instead of per page.
7
+ *
8
+ * It is not `PageShell` — that caps and centres a measure of content — and not
9
+ * `Card`, which is a surface within a page. This is the boundary between the
10
+ * app shell and whatever the route renders.
11
+ */ export function PagePanel({ children, scroll = true, flat = false, fitContent = false, surface = "background", className }) {
12
+ const sizing = fitContent ? "w-full max-h-full self-start" : "flex-1 border border-border";
13
+ const overflow = scroll ? fitContent ? "overflow-y-auto" : "min-h-full overflow-y-auto" : "overflow-hidden";
14
+ const fill = surface === "card" ? "bg-card" : "bg-background";
15
+ return /*#__PURE__*/ _jsx("div", {
16
+ className: cn("flex h-full overflow-hidden", fill),
17
+ children: /*#__PURE__*/ _jsx("section", {
18
+ "data-slot": "page-panel",
19
+ className: cn("flex flex-col", fill, sizing, overflow, !flat && "shadow-sm", className),
20
+ children: children
21
+ })
22
+ });
23
+ }
@@ -0,0 +1,21 @@
1
+ import type * as React from "react";
2
+ export type PageShellProps = React.ComponentProps<"div"> & {
3
+ /**
4
+ * The shell owns the vertical scroll. Turn it off when the page has its own
5
+ * scrolling body under a sticky header — two nested scroll containers is how
6
+ * a header ends up scrolling away with the content under it.
7
+ */
8
+ readonly scroll?: boolean;
9
+ /** Caps and centres the content. Omit for full-bleed pages like a dashboard. */
10
+ readonly width?: "prose" | "wide" | "full";
11
+ };
12
+ /**
13
+ * The frame a top-level page sits in: full height, one scroll container, and a
14
+ * consistent measure.
15
+ *
16
+ * It exists so margins, max-width and scroll ownership are decided once instead
17
+ * of by whichever page was written most recently. It is not `Section` — that is
18
+ * a titled block *within* a page — and it is not `Card`, which is a surface.
19
+ * This is the page's outer boundary and it deliberately draws nothing.
20
+ */
21
+ export declare function PageShell({ scroll, width, className, children, ...props }: PageShellProps): React.JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ const WIDTH_CLASS = {
4
+ prose: "mx-auto w-full max-w-3xl",
5
+ wide: "mx-auto w-full max-w-5xl",
6
+ full: "w-full"
7
+ };
8
+ /**
9
+ * The frame a top-level page sits in: full height, one scroll container, and a
10
+ * consistent measure.
11
+ *
12
+ * It exists so margins, max-width and scroll ownership are decided once instead
13
+ * of by whichever page was written most recently. It is not `Section` — that is
14
+ * a titled block *within* a page — and it is not `Card`, which is a surface.
15
+ * This is the page's outer boundary and it deliberately draws nothing.
16
+ */ export function PageShell({ scroll = true, width = "wide", className, children, ...props }) {
17
+ return /*#__PURE__*/ _jsx("div", {
18
+ "data-slot": "page-shell",
19
+ className: cn("flex h-full flex-col", scroll ? "overflow-y-auto" : "overflow-hidden", className),
20
+ ...props,
21
+ children: /*#__PURE__*/ _jsx("div", {
22
+ className: cn("flex min-h-0 flex-1 flex-col gap-6 px-4 py-6", WIDTH_CLASS[width]),
23
+ children: children
24
+ })
25
+ });
26
+ }
@@ -0,0 +1,22 @@
1
+ import type { Part, PartToolTrace } from "../lib/chat-parts.js";
2
+ import { type ToolBlockState } from "./tool-block.js";
3
+ export type PartsRendererProps = {
4
+ readonly parts: readonly Part[];
5
+ readonly className?: string;
6
+ };
7
+ /** Maps the wire's four-outcome status onto `ToolBlock`'s render-side state. */
8
+ export declare function toolTraceToBlockState(part: PartToolTrace): ToolBlockState;
9
+ /**
10
+ * Renders an ordered `Part[]` in the order the model produced it: text as
11
+ * prose, reasoning as a closed disclosure, tool calls through `ToolBlock`,
12
+ * files as an attachment chip, events as an inline system line, any block
13
+ * part as a labeled fallback card, and any `kind` outside the `Part` union
14
+ * itself as a neutral unknown-part fallback — the host's own generative-UI
15
+ * registry, if it has one, renders blocks by intercepting `kind: "block"`
16
+ * before it reaches this component.
17
+ *
18
+ * An empty `parts` array renders nothing — no placeholder, no empty-state
19
+ * chrome. A message with no parts is not a message a transcript should show
20
+ * anything for.
21
+ */
22
+ export declare function PartsRenderer({ parts, className }: PartsRendererProps): import("react").JSX.Element | null;
@@ -0,0 +1,234 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { ChevronRight, Paperclip } from "lucide-react";
3
+ import { useState } from "react";
4
+ import { cn } from "../lib/utils.js";
5
+ import { BlockCard } from "./block-card.js";
6
+ import { ToolBlock } from "./tool-block.js";
7
+ /** `1.2s`, `45s`, `3m 05s` — never a raw millisecond count. */ function formatDuration(ms) {
8
+ const totalSeconds = ms / 1000;
9
+ if (totalSeconds < 60) {
10
+ return `${totalSeconds < 10 ? totalSeconds.toFixed(1) : Math.round(totalSeconds)}s`;
11
+ }
12
+ const minutes = Math.floor(totalSeconds / 60);
13
+ const seconds = Math.round(totalSeconds % 60);
14
+ return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
15
+ }
16
+ /** Maps the wire's four-outcome status onto `ToolBlock`'s render-side state. */ export function toolTraceToBlockState(part) {
17
+ switch(part.status){
18
+ case "pending":
19
+ return {
20
+ status: "pending"
21
+ };
22
+ case "running":
23
+ return {
24
+ status: "running"
25
+ };
26
+ case "output-available":
27
+ return {
28
+ status: "output-available",
29
+ output: stringifyOutput(part.output)
30
+ };
31
+ case "error":
32
+ return {
33
+ status: "error",
34
+ message: stringifyOutput(part.output)
35
+ };
36
+ case "approval-requested":
37
+ return {
38
+ status: "approval-requested",
39
+ reason: stringifyOutput(part.output)
40
+ };
41
+ case "output-denied":
42
+ return {
43
+ status: "output-denied",
44
+ reason: stringifyOutput(part.output)
45
+ };
46
+ }
47
+ }
48
+ function stringifyOutput(output) {
49
+ if (output === undefined) return "";
50
+ if (typeof output === "string") return output;
51
+ return JSON.stringify(output, null, 2);
52
+ }
53
+ function TextPartView({ text }) {
54
+ if (text.trim().length === 0) return null;
55
+ return /*#__PURE__*/ _jsx("p", {
56
+ className: "whitespace-pre-wrap text-sm leading-relaxed text-foreground",
57
+ children: text
58
+ });
59
+ }
60
+ /**
61
+ * The agent's thinking, folded away, with how long it took once known.
62
+ *
63
+ * Closed by default, including while streaming: a block that expands itself
64
+ * mid-stream makes the transcript jump under the reader. A native
65
+ * `<details>`, not a div with state — it gets disclosure semantics, keyboard
66
+ * behaviour and find-in-page expansion from the browser for free.
67
+ */ function ReasoningPartView({ text, durationMs }) {
68
+ const [open, setOpen] = useState(false);
69
+ if (text.trim().length === 0) return null;
70
+ return /*#__PURE__*/ _jsxs("details", {
71
+ "data-slot": "reasoning-part",
72
+ open: open,
73
+ onToggle: (event)=>setOpen(event.currentTarget.open),
74
+ className: "text-xs",
75
+ children: [
76
+ /*#__PURE__*/ _jsxs("summary", {
77
+ className: cn(// `relative` plus the `::after` pseudo-element extends the
78
+ // effective hit area to 40px tall without changing the row's own
79
+ // padding/density — see design-engineering.md's hit-area pattern.
80
+ "relative flex cursor-pointer list-none items-center gap-1.5 rounded-md px-2 py-1.5 text-muted-foreground transition-colors after:absolute after:inset-x-0 after:top-1/2 after:h-10 after:-translate-y-1/2 hover:bg-muted hover:text-foreground", "[&::-webkit-details-marker]:hidden"),
81
+ children: [
82
+ /*#__PURE__*/ _jsx(ChevronRight, {
83
+ className: cn("size-3.5 shrink-0 transition-transform duration-200 ease-out", open && "rotate-90"),
84
+ "aria-hidden": true
85
+ }),
86
+ /*#__PURE__*/ _jsxs("span", {
87
+ children: [
88
+ "Thought",
89
+ durationMs === undefined ? "" : ` for ${formatDuration(durationMs)}`
90
+ ]
91
+ })
92
+ ]
93
+ }),
94
+ /*#__PURE__*/ _jsx("p", {
95
+ className: "mt-1 ml-4 border-l border-border pl-3 leading-relaxed whitespace-pre-wrap text-muted-foreground [animation:corbits-rail-block-in_200ms_var(--ease-out)_both]",
96
+ children: text
97
+ }, open ? "open" : "closed")
98
+ ]
99
+ });
100
+ }
101
+ function FilePartView({ name, mediaType, url }) {
102
+ const content = /*#__PURE__*/ _jsxs(_Fragment, {
103
+ children: [
104
+ /*#__PURE__*/ _jsx(Paperclip, {
105
+ className: "size-3.5 shrink-0",
106
+ "aria-hidden": true
107
+ }),
108
+ /*#__PURE__*/ _jsx("span", {
109
+ className: "min-w-0 flex-1 truncate",
110
+ children: name
111
+ }),
112
+ /*#__PURE__*/ _jsx("span", {
113
+ className: "shrink-0 text-muted-foreground",
114
+ children: mediaType
115
+ })
116
+ ]
117
+ });
118
+ if (url === undefined) {
119
+ return /*#__PURE__*/ _jsx("span", {
120
+ "data-slot": "file-part",
121
+ className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted/40 px-2.5 py-1.5 text-xs text-foreground",
122
+ children: content
123
+ });
124
+ }
125
+ return /*#__PURE__*/ _jsx("a", {
126
+ "data-slot": "file-part",
127
+ href: url,
128
+ target: "_blank",
129
+ rel: "noreferrer",
130
+ className: "inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted/40 px-2.5 py-1.5 text-xs text-foreground transition-colors hover:bg-muted",
131
+ children: content
132
+ });
133
+ }
134
+ function EventPartView({ event }) {
135
+ const label = event.replace(/[.-]+/g, " ");
136
+ return /*#__PURE__*/ _jsx("div", {
137
+ "data-slot": "event-part",
138
+ className: "flex items-center gap-2 py-1 text-xs text-muted-foreground italic",
139
+ children: /*#__PURE__*/ _jsx("span", {
140
+ children: label
141
+ })
142
+ });
143
+ }
144
+ /**
145
+ * A block part this renderer has no host-specific registry for. Phase 1
146
+ * ships no generative-UI JSON-spec renderer, so a block always falls back to
147
+ * its type name and raw payload rather than going unrendered.
148
+ */ function BlockPartFallback({ type, data }) {
149
+ return /*#__PURE__*/ _jsx(BlockCard, {
150
+ title: type,
151
+ children: /*#__PURE__*/ _jsx("pre", {
152
+ className: "max-h-64 overflow-auto font-mono text-[11px] whitespace-pre-wrap text-muted-foreground",
153
+ children: JSON.stringify(data, null, 2)
154
+ })
155
+ });
156
+ }
157
+ /**
158
+ * A `Part` whose `kind` this renderer has never heard of — a wire value
159
+ * outside `Part`'s own union, which can only ever arrive because this
160
+ * package validates nothing of its own (see `chat-parts.ts`). Renders a
161
+ * neutral labeled card naming the unrecognized kind rather than rendering
162
+ * nothing, so a producer/consumer version skew is visible in the transcript
163
+ * instead of silently swallowing content.
164
+ */ function UnknownPartFallback({ kind }) {
165
+ return /*#__PURE__*/ _jsxs("div", {
166
+ "data-slot": "unknown-part",
167
+ className: "rounded-md border border-dashed border-border bg-muted/30 px-2.5 py-1.5 text-xs text-muted-foreground",
168
+ children: [
169
+ "Unsupported part: ",
170
+ /*#__PURE__*/ _jsx("span", {
171
+ className: "font-mono",
172
+ children: kind
173
+ })
174
+ ]
175
+ });
176
+ }
177
+ /**
178
+ * Renders an ordered `Part[]` in the order the model produced it: text as
179
+ * prose, reasoning as a closed disclosure, tool calls through `ToolBlock`,
180
+ * files as an attachment chip, events as an inline system line, any block
181
+ * part as a labeled fallback card, and any `kind` outside the `Part` union
182
+ * itself as a neutral unknown-part fallback — the host's own generative-UI
183
+ * registry, if it has one, renders blocks by intercepting `kind: "block"`
184
+ * before it reaches this component.
185
+ *
186
+ * An empty `parts` array renders nothing — no placeholder, no empty-state
187
+ * chrome. A message with no parts is not a message a transcript should show
188
+ * anything for.
189
+ */ export function PartsRenderer({ parts, className }) {
190
+ if (parts.length === 0) return null;
191
+ return /*#__PURE__*/ _jsx("div", {
192
+ "data-slot": "parts-renderer",
193
+ className: cn("flex flex-col gap-1.5", className),
194
+ children: parts.map((part, index)=>{
195
+ const key = `${part.kind}-${index}`;
196
+ switch(part.kind){
197
+ case "text":
198
+ return /*#__PURE__*/ _jsx(TextPartView, {
199
+ text: part.text
200
+ }, key);
201
+ case "reasoning":
202
+ return /*#__PURE__*/ _jsx(ReasoningPartView, {
203
+ text: part.text,
204
+ durationMs: part.durationMs
205
+ }, key);
206
+ case "tool-trace":
207
+ return /*#__PURE__*/ _jsx(ToolBlock, {
208
+ name: part.name,
209
+ input: part.input,
210
+ state: toolTraceToBlockState(part)
211
+ }, key);
212
+ case "file":
213
+ return /*#__PURE__*/ _jsx(FilePartView, {
214
+ name: part.name,
215
+ mediaType: part.mediaType,
216
+ url: part.url
217
+ }, key);
218
+ case "event":
219
+ return /*#__PURE__*/ _jsx(EventPartView, {
220
+ event: part.event
221
+ }, key);
222
+ case "block":
223
+ return /*#__PURE__*/ _jsx(BlockPartFallback, {
224
+ type: part.block.type,
225
+ data: part.block.data
226
+ }, key);
227
+ default:
228
+ return /*#__PURE__*/ _jsx(UnknownPartFallback, {
229
+ kind: part.kind
230
+ }, key);
231
+ }
232
+ })
233
+ });
234
+ }
@@ -0,0 +1,39 @@
1
+ import type { ReactNode } from "react";
2
+ import { type AvatarTone } from "./avatar.js";
3
+ import { type BadgeTone } from "./badge.js";
4
+ export type ProfileCardAction = {
5
+ readonly id: string;
6
+ readonly label: string;
7
+ readonly onClick?: () => void;
8
+ readonly tone?: "primary" | "outline";
9
+ };
10
+ export type ProfileCardChannel = {
11
+ readonly id: string;
12
+ readonly name: string;
13
+ readonly href?: string;
14
+ };
15
+ export type ProfileCardSkill = {
16
+ readonly id: string;
17
+ readonly name: string;
18
+ readonly href?: string;
19
+ };
20
+ export type ProfileCardProps = {
21
+ readonly name: string;
22
+ readonly initials: string;
23
+ readonly subtitle?: string;
24
+ readonly statusLabel?: string;
25
+ readonly statusTone?: BadgeTone;
26
+ readonly avatarTone?: AvatarTone;
27
+ readonly tenantMonogram?: string;
28
+ readonly actions?: readonly ProfileCardAction[];
29
+ readonly sharedChannels?: readonly ProfileCardChannel[];
30
+ readonly pinnedSkills?: readonly ProfileCardSkill[];
31
+ readonly footer?: ReactNode;
32
+ readonly className?: string;
33
+ };
34
+ /**
35
+ * Canvas profile card: hero identity, status, action chips, shared
36
+ * channels, and pinned skills. Deep-links are plain hrefs so the host
37
+ * router owns navigation.
38
+ */
39
+ export declare function ProfileCard({ name, initials, subtitle, statusLabel, statusTone, avatarTone, tenantMonogram, actions, sharedChannels, pinnedSkills, footer, className, }: ProfileCardProps): import("react").JSX.Element;
@@ -0,0 +1,124 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { toSafeHref } from "../lib/url.js";
3
+ import { cn } from "../lib/utils.js";
4
+ import { Avatar } from "./avatar.js";
5
+ import { Badge } from "./badge.js";
6
+ import { Button } from "./button.js";
7
+ /**
8
+ * Canvas profile card: hero identity, status, action chips, shared
9
+ * channels, and pinned skills. Deep-links are plain hrefs so the host
10
+ * router owns navigation.
11
+ */ export function ProfileCard({ name, initials, subtitle, statusLabel, statusTone = "neutral", avatarTone = "neutral", tenantMonogram, actions = [], sharedChannels = [], pinnedSkills = [], footer, className }) {
12
+ return /*#__PURE__*/ _jsxs("article", {
13
+ "data-slot": "profile-card",
14
+ className: cn("flex flex-col gap-4 border border-border bg-card p-4", className),
15
+ children: [
16
+ /*#__PURE__*/ _jsxs("header", {
17
+ className: "flex items-start gap-3",
18
+ children: [
19
+ /*#__PURE__*/ _jsx(Avatar, {
20
+ initials: initials,
21
+ label: name,
22
+ tone: avatarTone,
23
+ size: "lg",
24
+ ...tenantMonogram === undefined ? {} : {
25
+ tenantMonogram
26
+ }
27
+ }),
28
+ /*#__PURE__*/ _jsxs("div", {
29
+ className: "min-w-0 flex-1",
30
+ children: [
31
+ /*#__PURE__*/ _jsxs("div", {
32
+ className: "flex flex-wrap items-center gap-2",
33
+ children: [
34
+ /*#__PURE__*/ _jsx("h2", {
35
+ className: "truncate text-base font-semibold",
36
+ children: name
37
+ }),
38
+ statusLabel === undefined ? null : /*#__PURE__*/ _jsx(Badge, {
39
+ tone: statusTone,
40
+ children: statusLabel
41
+ })
42
+ ]
43
+ }),
44
+ subtitle === undefined ? null : /*#__PURE__*/ _jsx("p", {
45
+ className: "truncate text-xs text-muted-foreground",
46
+ children: subtitle
47
+ })
48
+ ]
49
+ })
50
+ ]
51
+ }),
52
+ actions.length === 0 ? null : /*#__PURE__*/ _jsx("div", {
53
+ className: "flex flex-wrap gap-1.5",
54
+ children: actions.map((action)=>/*#__PURE__*/ _jsx(Button, {
55
+ type: "button",
56
+ size: "sm",
57
+ variant: action.tone === "primary" ? "primary" : "outline",
58
+ onClick: action.onClick,
59
+ children: action.label
60
+ }, action.id))
61
+ }),
62
+ sharedChannels.length === 0 ? null : /*#__PURE__*/ _jsxs("section", {
63
+ className: "flex flex-col gap-1.5",
64
+ children: [
65
+ /*#__PURE__*/ _jsx("h3", {
66
+ className: "text-[10.5px] font-bold uppercase tracking-[0.06em] text-muted-foreground",
67
+ children: "Shared channels"
68
+ }),
69
+ /*#__PURE__*/ _jsx("ul", {
70
+ className: "flex flex-col gap-1",
71
+ children: sharedChannels.map((channel)=>{
72
+ const href = toSafeHref(channel.href);
73
+ return /*#__PURE__*/ _jsx("li", {
74
+ children: href === undefined ? /*#__PURE__*/ _jsxs("span", {
75
+ className: "text-sm",
76
+ children: [
77
+ "#",
78
+ channel.name
79
+ ]
80
+ }) : /*#__PURE__*/ _jsxs("a", {
81
+ href: href,
82
+ className: "text-sm text-foreground hover:underline",
83
+ children: [
84
+ "#",
85
+ channel.name
86
+ ]
87
+ })
88
+ }, channel.id);
89
+ })
90
+ })
91
+ ]
92
+ }),
93
+ pinnedSkills.length === 0 ? null : /*#__PURE__*/ _jsxs("section", {
94
+ className: "flex flex-col gap-1.5",
95
+ children: [
96
+ /*#__PURE__*/ _jsx("h3", {
97
+ className: "text-[10.5px] font-bold uppercase tracking-[0.06em] text-muted-foreground",
98
+ children: "Pinned skills"
99
+ }),
100
+ /*#__PURE__*/ _jsx("ul", {
101
+ className: "flex flex-wrap gap-1.5",
102
+ children: pinnedSkills.map((skill)=>{
103
+ const href = toSafeHref(skill.href);
104
+ return href === undefined ? /*#__PURE__*/ _jsx(Badge, {
105
+ tone: "neutral",
106
+ children: skill.name
107
+ }, skill.id) : /*#__PURE__*/ _jsx("a", {
108
+ href: href,
109
+ children: /*#__PURE__*/ _jsx(Badge, {
110
+ tone: "neutral",
111
+ children: skill.name
112
+ })
113
+ }, skill.id);
114
+ })
115
+ })
116
+ ]
117
+ }),
118
+ footer === undefined ? null : /*#__PURE__*/ _jsx("footer", {
119
+ className: "border-t border-border pt-3",
120
+ children: footer
121
+ })
122
+ ]
123
+ });
124
+ }