@getdom/studio 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 (213) hide show
  1. package/LICENSE.md +109 -0
  2. package/README.md +44 -0
  3. package/SKILL.md +83 -0
  4. package/package.json +73 -0
  5. package/public/llms.txt +57 -0
  6. package/public/skills.sh +11 -0
  7. package/src/pages/_layout/componentCatalog.generated.js +4514 -0
  8. package/src/pages/_layout/componentInspector.js +60 -0
  9. package/src/pages/_layout/componentManager.js +192 -0
  10. package/src/pages/_layout/componentRuntimeRecords.js +49 -0
  11. package/src/pages/_layout/inspector/componentRegistry.js +639 -0
  12. package/src/pages/_layout/inspector/serialize.js +134 -0
  13. package/src/pages/_layout/inspector/useInspector.js +293 -0
  14. package/src/pages/components/accordion/DomAccordion.vue +39 -0
  15. package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
  16. package/src/pages/components/button/DomButton.vue +132 -0
  17. package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
  18. package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
  19. package/src/pages/components/dialog/DomDialog.vue +271 -0
  20. package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
  21. package/src/pages/components/dialog/DomDialogStack.vue +289 -0
  22. package/src/pages/components/dialog/useDialogs.js +253 -0
  23. package/src/pages/components/drawer/DomDrawer.vue +159 -0
  24. package/src/pages/components/dropdown/DomDropdown.vue +261 -0
  25. package/src/pages/components/icon-button/DomIconButton.vue +155 -0
  26. package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
  27. package/src/pages/components/menu/DomMenu.vue +359 -0
  28. package/src/pages/components/menu/DomMenuItem.vue +233 -0
  29. package/src/pages/components/menu/DomMenuLabel.vue +19 -0
  30. package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
  31. package/src/pages/components/menu/menuContext.js +11 -0
  32. package/src/pages/components/menu/menuPlacement.js +26 -0
  33. package/src/pages/components/menu/submenu.js +58 -0
  34. package/src/pages/components/pagination/DomPagination.vue +265 -0
  35. package/src/pages/components/playground/DomRenderer.vue +48 -0
  36. package/src/pages/components/popover/DomPopover.vue +244 -0
  37. package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
  38. package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
  39. package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
  40. package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
  41. package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
  42. package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
  43. package/src/pages/components/stepper/DomStepper.vue +386 -0
  44. package/src/pages/components/tabs/DomTabs.vue +149 -0
  45. package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
  46. package/src/pages/components/toast/DomToastItem.vue +52 -0
  47. package/src/pages/components/toast/DomToastStack.vue +768 -0
  48. package/src/pages/components/toast/useToasts.js +237 -0
  49. package/src/pages/components/toolbar/DomToolbar.vue +158 -0
  50. package/src/pages/components/tooltip/DomTooltip.vue +69 -0
  51. package/src/pages/components/tooltip/vDomTooltip.js +106 -0
  52. package/src/pages/components/tree-view/DomTreeView.vue +961 -0
  53. package/src/pages/components/tree-view/treeSchemas.js +129 -0
  54. package/src/pages/components/tree-view/treeUtils.js +228 -0
  55. package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
  56. package/src/pages/data/_shared/resourceRecord.js +29 -0
  57. package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
  58. package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
  59. package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
  60. package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
  61. package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
  62. package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
  63. package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
  64. package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
  65. package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
  66. package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
  67. package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
  68. package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
  69. package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
  70. package/src/pages/data/kanban/DomKanban.vue +1703 -0
  71. package/src/pages/data/kanban/kanbanApi.js +80 -0
  72. package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
  73. package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
  74. package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
  75. package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
  76. package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
  77. package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
  78. package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
  79. package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
  80. package/src/pages/forms/_shared/options.js +61 -0
  81. package/src/pages/forms/_shared/useOptionMenu.js +59 -0
  82. package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
  83. package/src/pages/forms/calendar/DomCalendar.vue +442 -0
  84. package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
  85. package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
  86. package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
  87. package/src/pages/forms/color-input/DomColorInput.vue +118 -0
  88. package/src/pages/forms/combobox/DomCombobox.vue +237 -0
  89. package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
  90. package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
  91. package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
  92. package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
  93. package/src/pages/forms/field/DomField.vue +267 -0
  94. package/src/pages/forms/field/fieldLayout.js +1 -0
  95. package/src/pages/forms/field/fieldProps.js +114 -0
  96. package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
  97. package/src/pages/forms/field/useField.js +343 -0
  98. package/src/pages/forms/field/validators.js +364 -0
  99. package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
  100. package/src/pages/forms/form/DomForm.vue +985 -0
  101. package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
  102. package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
  103. package/src/pages/forms/form/adapters/shared.js +53 -0
  104. package/src/pages/forms/form/adapters/zod.js +235 -0
  105. package/src/pages/forms/form/formApi.js +38 -0
  106. package/src/pages/forms/form/formDefinition.js +392 -0
  107. package/src/pages/forms/form/schemaAdapter.js +54 -0
  108. package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
  109. package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
  110. package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
  111. package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
  112. package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
  113. package/src/pages/forms/listbox/DomListbox.vue +105 -0
  114. package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
  115. package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
  116. package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
  117. package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
  118. package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
  119. package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
  120. package/src/pages/forms/password-strength/passwordStrength.js +92 -0
  121. package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
  122. package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
  123. package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
  124. package/src/pages/forms/select/DomSelect.vue +479 -0
  125. package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
  126. package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
  127. package/src/pages/forms/text-input/DomTextInput.vue +48 -0
  128. package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
  129. package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
  130. package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
  131. package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
  132. package/src/pages/forms/time-selector/timeUtils.js +194 -0
  133. package/src/pages/forms/toggle/DomToggle.vue +158 -0
  134. package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
  135. package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
  136. package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
  137. package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
  138. package/src/pages/lib/css/color.js +245 -0
  139. package/src/pages/lib/headless/accordion.d.ts +7 -0
  140. package/src/pages/lib/headless/accordion.js +246 -0
  141. package/src/pages/lib/headless/autocomplete.d.ts +4 -0
  142. package/src/pages/lib/headless/autocomplete.js +33 -0
  143. package/src/pages/lib/headless/base.d.ts +25 -0
  144. package/src/pages/lib/headless/base.js +127 -0
  145. package/src/pages/lib/headless/checkbox.d.ts +19 -0
  146. package/src/pages/lib/headless/checkbox.js +99 -0
  147. package/src/pages/lib/headless/combobox.d.ts +7 -0
  148. package/src/pages/lib/headless/combobox.js +488 -0
  149. package/src/pages/lib/headless/dialog.d.ts +23 -0
  150. package/src/pages/lib/headless/dialog.js +384 -0
  151. package/src/pages/lib/headless/drawer.d.ts +21 -0
  152. package/src/pages/lib/headless/drawer.js +682 -0
  153. package/src/pages/lib/headless/dropdown.d.ts +19 -0
  154. package/src/pages/lib/headless/dropdown.js +412 -0
  155. package/src/pages/lib/headless/floating-effect.js +118 -0
  156. package/src/pages/lib/headless/floating.d.ts +34 -0
  157. package/src/pages/lib/headless/floating.js +382 -0
  158. package/src/pages/lib/headless/index.d.ts +19 -0
  159. package/src/pages/lib/headless/index.js +19 -0
  160. package/src/pages/lib/headless/listbox.d.ts +19 -0
  161. package/src/pages/lib/headless/listbox.js +126 -0
  162. package/src/pages/lib/headless/menu.d.ts +18 -0
  163. package/src/pages/lib/headless/menu.js +117 -0
  164. package/src/pages/lib/headless/popover-panel.d.ts +18 -0
  165. package/src/pages/lib/headless/popover-panel.js +309 -0
  166. package/src/pages/lib/headless/popover.d.ts +19 -0
  167. package/src/pages/lib/headless/popover.js +391 -0
  168. package/src/pages/lib/headless/radio-group.d.ts +19 -0
  169. package/src/pages/lib/headless/radio-group.js +100 -0
  170. package/src/pages/lib/headless/tabs.d.ts +18 -0
  171. package/src/pages/lib/headless/tabs.js +119 -0
  172. package/src/pages/lib/headless/toast.d.ts +13 -0
  173. package/src/pages/lib/headless/toast.js +65 -0
  174. package/src/pages/lib/headless/toggle.d.ts +19 -0
  175. package/src/pages/lib/headless/toggle.js +162 -0
  176. package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
  177. package/src/pages/lib/headless/tooltip-controller.js +549 -0
  178. package/src/pages/lib/headless/tooltip.d.ts +19 -0
  179. package/src/pages/lib/headless/tooltip.js +153 -0
  180. package/src/pages/lib/headless/transitions.d.ts +5 -0
  181. package/src/pages/lib/headless/transitions.js +89 -0
  182. package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
  183. package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
  184. package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
  185. package/src/pages/lib/vue/codeHighlighter.js +132 -0
  186. package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
  187. package/src/pages/lib/vue/index.d.ts +6806 -0
  188. package/src/pages/lib/vue/index.js +218 -0
  189. package/src/pages/lib/vue/useTheme.js +116 -0
  190. package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
  191. package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
  192. package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
  193. package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
  194. package/src/pages/mobile/shell/DomAppShell.vue +51 -0
  195. package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
  196. package/src/pages/mobile/stack/DomAppStack.vue +711 -0
  197. package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
  198. package/src/pages/visual/alert/DomAlert.vue +363 -0
  199. package/src/pages/visual/avatar/DomAvatar.vue +110 -0
  200. package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
  201. package/src/pages/visual/badge/DomBadge.vue +105 -0
  202. package/src/pages/visual/card/DomCard.vue +65 -0
  203. package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
  204. package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
  205. package/src/pages/visual/progress/DomProgress.vue +212 -0
  206. package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
  207. package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
  208. package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
  209. package/src/pages/visual/spinner/DomSpinner.vue +122 -0
  210. package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
  211. package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
  212. package/src/pages/visual/text-diff/textDiff.js +300 -0
  213. package/src/style.css +446 -0
@@ -0,0 +1,319 @@
1
+ <script>
2
+ export const agentConversationListProps = {
3
+ conversations: {
4
+ type: Array,
5
+ default: () => [],
6
+ _edit: { description: 'Conversation summaries shown in the navigation list.' },
7
+ },
8
+ agents: {
9
+ type: Array,
10
+ default: () => [],
11
+ _edit: { description: 'Agent records used to label each conversation.' },
12
+ },
13
+ activeConversationId: {
14
+ type: String,
15
+ default: '',
16
+ _edit: { description: 'Selected conversation id.' },
17
+ },
18
+ search: {
19
+ type: String,
20
+ default: '',
21
+ _edit: { description: 'Controlled conversation search value.' },
22
+ },
23
+ loading: {
24
+ type: Boolean,
25
+ default: false,
26
+ _edit: { description: 'Show loading rows while conversations are fetched.' },
27
+ },
28
+ title: {
29
+ type: String,
30
+ default: 'Conversations',
31
+ _edit: { description: 'Sidebar heading.' },
32
+ },
33
+ showNewButton: {
34
+ type: Boolean,
35
+ default: true,
36
+ _edit: { description: 'Show the built-in new conversation button.' },
37
+ },
38
+ emptyText: {
39
+ type: String,
40
+ default: 'No conversations match this view.',
41
+ _edit: { description: 'Message shown when the filtered list is empty.' },
42
+ },
43
+ };
44
+ </script>
45
+
46
+ <script setup>
47
+ import { computed, useId } from 'vue';
48
+ import DomIconButton from '../../components/icon-button/DomIconButton.vue';
49
+ import DomStatusPill from '../../visual/status-pill/DomStatusPill.vue';
50
+
51
+ defineOptions({
52
+ __doc: {
53
+ name: 'Agent conversation list',
54
+ tag: '<DomAgentConversationList>',
55
+ description: 'A reusable AI conversation navigation list with search, agent labels, unread counts, status pills, and active-thread selection.',
56
+ icon: 'M5 6h14M5 12h14M5 18h8',
57
+ events: [
58
+ { name: 'update:search', payload: 'string', description: 'Fired when the sidebar search changes.' },
59
+ { name: 'select', payload: 'conversation', description: 'Fired when a conversation row is selected.' },
60
+ { name: 'new', payload: 'undefined', description: 'Fired when the new conversation action is activated.' },
61
+ ],
62
+ slots: [
63
+ { name: 'actions', payload: '{ conversations }', description: 'Extra controls beside the heading.' },
64
+ { name: 'conversation', payload: '{ conversation, active, agent }', description: 'Replace a conversation row.' },
65
+ { name: 'empty', description: 'Custom empty state.' },
66
+ ],
67
+ },
68
+ });
69
+
70
+ const props = defineProps(agentConversationListProps);
71
+ const emit = defineEmits(['update:search', 'select', 'new']);
72
+ const searchId = useId();
73
+
74
+ const filteredConversations = computed(() => {
75
+ const query = props.search.trim().toLowerCase();
76
+ if (!query) return props.conversations;
77
+ return props.conversations.filter((conversation) => conversationMatchesQuery(conversation, query));
78
+ });
79
+
80
+ /**
81
+ * Emits a search value update.
82
+ *
83
+ * @param {Event} event Native input event.
84
+ * @returns {void}
85
+ */
86
+ function updateSearch(event) {
87
+ emit('update:search', event.target.value);
88
+ }
89
+
90
+ /**
91
+ * Emits a conversation selection event.
92
+ *
93
+ * @param {Record<string, unknown>} conversation Conversation summary.
94
+ * @returns {void}
95
+ */
96
+ function selectConversation(conversation) {
97
+ emit('select', conversation);
98
+ }
99
+
100
+ /**
101
+ * Emits a new conversation request.
102
+ *
103
+ * @returns {void}
104
+ */
105
+ function createConversation() {
106
+ emit('new');
107
+ }
108
+
109
+ /**
110
+ * Checks whether a conversation is currently active.
111
+ *
112
+ * @param {Record<string, unknown>} conversation Conversation summary.
113
+ * @returns {boolean} True when active.
114
+ */
115
+ function isActiveConversation(conversation) {
116
+ return conversation.id === props.activeConversationId;
117
+ }
118
+
119
+ /**
120
+ * Finds the agent associated with a conversation.
121
+ *
122
+ * @param {Record<string, unknown>} conversation Conversation summary.
123
+ * @returns {Record<string, unknown>|null} Matching agent.
124
+ */
125
+ function agentForConversation(conversation) {
126
+ return props.agents.find((agent) => agent.id === conversation.agentId) || null;
127
+ }
128
+
129
+ /**
130
+ * Returns a fallback title for a conversation.
131
+ *
132
+ * @param {Record<string, unknown>} conversation Conversation summary.
133
+ * @returns {string} Display title.
134
+ */
135
+ function conversationTitle(conversation) {
136
+ return conversation.title || 'Untitled conversation';
137
+ }
138
+
139
+ /**
140
+ * Returns summary text for a conversation row.
141
+ *
142
+ * @param {Record<string, unknown>} conversation Conversation summary.
143
+ * @returns {string} Summary text.
144
+ */
145
+ function conversationSummary(conversation) {
146
+ return conversation.summary || agentForConversation(conversation)?.role || 'No summary yet';
147
+ }
148
+
149
+ /**
150
+ * Returns a compact label for the agent avatar.
151
+ *
152
+ * @param {Record<string, unknown>} conversation Conversation summary.
153
+ * @returns {string} Initials.
154
+ */
155
+ function agentInitials(conversation) {
156
+ const name = agentForConversation(conversation)?.name || 'AI';
157
+ return name
158
+ .split(/\s+/)
159
+ .filter(Boolean)
160
+ .slice(0, 2)
161
+ .map(firstCharacter)
162
+ .join('')
163
+ .toUpperCase() || 'AI';
164
+ }
165
+
166
+ /**
167
+ * Formats the conversation update time.
168
+ *
169
+ * @param {Record<string, unknown>} conversation Conversation summary.
170
+ * @returns {string} Short date or time label.
171
+ */
172
+ function updatedLabel(conversation) {
173
+ if (!conversation.updatedAt) return '';
174
+ const date = new Date(conversation.updatedAt);
175
+ if (Number.isNaN(date.getTime())) return '';
176
+ const today = new Date();
177
+ if (date.toDateString() === today.toDateString()) {
178
+ return new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(date);
179
+ }
180
+ return new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric' }).format(date);
181
+ }
182
+
183
+ /**
184
+ * Resolves a status pill tone for a conversation.
185
+ *
186
+ * @param {Record<string, unknown>} conversation Conversation summary.
187
+ * @returns {'neutral'|'info'|'success'|'warning'|'danger'} Status tone.
188
+ */
189
+ function statusTone(conversation) {
190
+ return {
191
+ streaming: 'info',
192
+ waiting: 'warning',
193
+ error: 'danger',
194
+ complete: 'success',
195
+ }[conversation.status] || 'neutral';
196
+ }
197
+
198
+ /**
199
+ * Checks whether a conversation matches the lowercase search query.
200
+ *
201
+ * @param {Record<string, unknown>} conversation Conversation summary.
202
+ * @param {string} query Lowercase search query.
203
+ * @returns {boolean} True when the row should be visible.
204
+ */
205
+ function conversationMatchesQuery(conversation, query) {
206
+ const agent = agentForConversation(conversation);
207
+ return [
208
+ conversation.title,
209
+ conversation.summary,
210
+ conversation.status,
211
+ agent?.name,
212
+ agent?.role,
213
+ ...(Array.isArray(conversation.labels) ? conversation.labels : []),
214
+ ]
215
+ .filter(Boolean)
216
+ .join(' ')
217
+ .toLowerCase()
218
+ .includes(query);
219
+ }
220
+
221
+ /**
222
+ * Returns the first character from a word.
223
+ *
224
+ * @param {string} value Source word.
225
+ * @returns {string} First character.
226
+ */
227
+ function firstCharacter(value) {
228
+ return value.charAt(0);
229
+ }
230
+ </script>
231
+
232
+ <template>
233
+ <aside class="flex h-full min-h-0 w-full min-w-0 flex-col bg-canvas text-canvas-fg">
234
+ <header class="shrink-0 border-b border-border bg-secondary/25 p-3">
235
+ <div class="flex items-center justify-between gap-3">
236
+ <h2 class="truncate text-sm font-semibold text-canvas-fg">{{ title }}</h2>
237
+ <div class="flex shrink-0 items-center gap-1">
238
+ <slot name="actions" :conversations="conversations" />
239
+ <DomIconButton
240
+ v-if="showNewButton"
241
+ label="New conversation"
242
+ size="sm"
243
+ variant="secondary"
244
+ icon="M12 5v14M5 12h14"
245
+ @click="createConversation"
246
+ />
247
+ </div>
248
+ </div>
249
+ <label :for="searchId" class="sr-only">Search conversations</label>
250
+ <div class="mt-3 flex items-center gap-2 rounded-md border border-border bg-canvas px-3 py-2">
251
+ <svg viewBox="0 0 24 24" class="size-4 shrink-0 text-muted-fg" fill="none" aria-hidden="true">
252
+ <path d="M11 5a6 6 0 1 0 0 12 6 6 0 0 0 0-12Zm4.5 10.5L20 20" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
253
+ </svg>
254
+ <input
255
+ :id="searchId"
256
+ :value="search"
257
+ type="search"
258
+ placeholder="Search threads"
259
+ class="min-w-0 flex-1 bg-transparent text-sm text-canvas-fg outline-none placeholder:text-muted-fg"
260
+ @input="updateSearch"
261
+ />
262
+ </div>
263
+ </header>
264
+
265
+ <div class="min-h-0 flex-1 overflow-y-auto p-2">
266
+ <div v-if="loading" class="space-y-2">
267
+ <div v-for="index in 5" :key="index" class="h-20 animate-pulse rounded-lg border border-border bg-secondary/35"></div>
268
+ </div>
269
+
270
+ <slot v-else-if="!filteredConversations.length" name="empty">
271
+ <div class="grid min-h-40 place-items-center rounded-lg border border-dashed border-border p-4 text-center">
272
+ <p class="text-sm leading-6 text-muted-fg">{{ emptyText }}</p>
273
+ </div>
274
+ </slot>
275
+
276
+ <div v-else class="space-y-1">
277
+ <slot
278
+ v-for="conversation in filteredConversations"
279
+ :key="conversation.id"
280
+ name="conversation"
281
+ :conversation="conversation"
282
+ :active="isActiveConversation(conversation)"
283
+ :agent="agentForConversation(conversation)"
284
+ >
285
+ <button
286
+ type="button"
287
+ class="w-full rounded-lg border p-3 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
288
+ :class="isActiveConversation(conversation)
289
+ ? 'border-primary/40 bg-primary/10 shadow-sm'
290
+ : 'border-transparent hover:border-border hover:bg-secondary/45'"
291
+ @click="selectConversation(conversation)"
292
+ >
293
+ <div class="flex items-start gap-3">
294
+ <div class="grid size-9 shrink-0 place-items-center rounded-full border border-border bg-canvas text-xs font-semibold text-muted-fg">
295
+ {{ agentInitials(conversation) }}
296
+ </div>
297
+ <div class="min-w-0 flex-1">
298
+ <div class="flex min-w-0 items-start justify-between gap-2">
299
+ <p class="truncate text-sm font-semibold text-canvas-fg">{{ conversationTitle(conversation) }}</p>
300
+ <span class="shrink-0 text-[11px] text-muted-fg">{{ updatedLabel(conversation) }}</span>
301
+ </div>
302
+ <p class="mt-1 line-clamp-2 text-xs leading-5 text-muted-fg">{{ conversationSummary(conversation) }}</p>
303
+ <div class="mt-2 flex items-center justify-between gap-2">
304
+ <DomStatusPill size="sm" :tone="statusTone(conversation)" variant="soft">{{ conversation.status || 'idle' }}</DomStatusPill>
305
+ <span
306
+ v-if="conversation.unreadCount"
307
+ class="grid min-w-5 place-items-center rounded-full bg-primary px-1.5 py-0.5 text-[11px] font-semibold text-primary-fg"
308
+ >
309
+ {{ conversation.unreadCount }}
310
+ </span>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ </button>
315
+ </slot>
316
+ </div>
317
+ </div>
318
+ </aside>
319
+ </template>