@getdom/studio 0.2.1 → 0.2.3

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 (30) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE.md +23 -9
  3. package/package.json +1 -1
  4. package/src/pages/_layout/componentCatalog.generated.js +115 -13
  5. package/src/pages/components/command-palette/DomCommandPalette.vue +6 -1
  6. package/src/pages/data/agent-chat/DomAgentChatShell.vue +52 -53
  7. package/src/pages/data/agent-chat/DomAgentConversation.vue +163 -154
  8. package/src/pages/data/agent-chat/DomAgentConversationList.vue +53 -57
  9. package/src/pages/data/month-calendar/DomMonthCalendar.vue +8 -9
  10. package/src/pages/forms/_shared/DomOptionStatus.vue +18 -0
  11. package/src/pages/forms/_shared/useOptionMenu.js +18 -2
  12. package/src/pages/forms/_shared/useOptionPaging.js +42 -0
  13. package/src/pages/forms/_shared/useSelectedOptions.js +25 -0
  14. package/src/pages/forms/autocomplete/DomAutocomplete.vue +36 -8
  15. package/src/pages/forms/combobox/DomCombobox.vue +44 -7
  16. package/src/pages/forms/font-picker/DomFontPicker.vue +416 -0
  17. package/src/pages/forms/font-picker/FontPreview.vue +39 -0
  18. package/src/pages/forms/font-picker/fontPicker.js +102 -0
  19. package/src/pages/forms/font-picker/googleFonts.js +1951 -0
  20. package/src/pages/forms/select/DomSelect.vue +118 -29
  21. package/src/pages/forms/tag-combobox/DomTagCombobox.vue +67 -15
  22. package/src/pages/lib/headless/autocomplete.js +5 -0
  23. package/src/pages/lib/headless/combobox.js +129 -25
  24. package/src/pages/lib/headless/dropdown.js +3 -2
  25. package/src/pages/lib/headless/listbox.d.ts +1 -0
  26. package/src/pages/lib/headless/listbox.js +41 -5
  27. package/src/pages/lib/vue/index.d.ts +136 -6
  28. package/src/pages/lib/vue/index.js +1 -0
  29. package/src/tailwind.css +5 -1
  30. package/src/theme.css +4 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,98 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3 - 2026-09-27
4
+
5
+ Searchable Google Fonts previews, shared paginated option lists, and more reliable
6
+ selection controls.
7
+
8
+ ### Added
9
+
10
+ - `DomFontPicker` with searchable family previews, incremental browsing and a
11
+ lightweight snapshot of 1,946 Google Fonts families. Font files are not bundled;
12
+ the mounted picker requests only selected and visible preview families, with
13
+ text subsetting and component-owned stylesheet cleanup.
14
+ - Multiple selection in `DomSelect`, with an array model, checked options and a
15
+ clear-all action. Selected labels are retained when searches replace remote pages.
16
+ - Shared incremental loading for Select, Combobox, Autocomplete and Tag combobox,
17
+ using `hasMore` and `load-more` for scroll, keyboard and explicit-button paging.
18
+ Documentation includes real HTTP examples with retry and stale-request handling.
19
+ - Query and selected-state slot data for custom option rendering, plus a matched-text
20
+ highlighting example that keeps HTML-like labels as plain text.
21
+
22
+ ### Improved
23
+
24
+ - Opening a picker shows the loaded choices without treating its committed value
25
+ as a permanent search filter. Selected checkmarks remain distinct from keyboard
26
+ navigation, and loaded single selections open in context.
27
+ - Select and Tag combobox documentation explains compact multi-selection versus
28
+ removable tokens, optional custom entries and paste support, alongside their
29
+ shared option, field-state and paging foundations.
30
+ - Public TypeScript declarations cover the font picker, multiple selection,
31
+ retained selected records and paginated option lists.
32
+
33
+ ### Fixed
34
+
35
+ - Dropdowns reopen reliably while an earlier close animation finishes, and late
36
+ option updates no longer reopen a dismissed combobox or autocomplete.
37
+ - Selected options remain visible and checked in combobox and tag suggestions.
38
+ Disabled and read-only controls do not commit selections through their action buttons.
39
+ - Select keeps its arrow at the right edge and centers the clear button to its
40
+ left. Long selections truncate within narrow containers, and clearing returns
41
+ keyboard focus to the trigger.
42
+
43
+ ### Upgrade notes
44
+
45
+ - Single-select models remain scalar values. Use an array model when enabling
46
+ `multiple`; Tag combobox continues to use an array of selected values.
47
+ - For server-filtered lists, set `filterOptions` to false and handle `query` and
48
+ `load-more` in the application. Supply `selectedOptions` for initial selected
49
+ records outside the loaded page. Seeking directly to a later remote page remains
50
+ application-owned; controls do not fetch or paginate an endpoint themselves.
51
+ - Google Fonts previews need no API key. Applications own loading the selected
52
+ font for content outside the picker. A live Developer API catalogue should be
53
+ fetched and cached server-side or at build time, with its key kept out of client code.
54
+
55
+ ## 0.2.2 - 2026-09-26
56
+
57
+ More flexible calendar highlights, refined agent chat, and updated website
58
+ licensing flows.
59
+
60
+ ### Added
61
+
62
+ - `DomMonthCalendar` now accepts `todayIndicator="both"` to show the cell outline
63
+ and date circle together. `todayDateClass` customises the circle in either
64
+ `circle` or `both` mode, with matching TypeScript declarations and an interactive
65
+ example.
66
+ - Website support for account-optional licence checkout, downloadable purchase
67
+ confirmations, and email-verified licence claiming. Checkout requires configured
68
+ transactional email delivery.
69
+
70
+ ### Improved
71
+
72
+ - Agent chat uses a shared centred width for messages and the composer, compact
73
+ conversation rows with avatars and unread counts, expandable tool details,
74
+ and clearer loading, empty, streaming, and agent-context displays.
75
+ - Default control backgrounds use the canvas colour. The command palette has
76
+ updated translucency and theme-aware shadows.
77
+ - The homepage includes interactive workspace, agent-chat, analytics, and designer
78
+ demos. Website pricing and checkout use consistent USD licence offers.
79
+
80
+ ### Fixed
81
+
82
+ - Calendar date numbers keep consistent spacing across all today-indicator modes,
83
+ and week numbers align with dates in the desktop grid.
84
+ - Website checkout validates the configured price and licence before redirecting
85
+ to payment. Delayed payment handling and repeat webhook processing preserve
86
+ the recorded licence and its fulfilment state.
87
+
88
+ ### Upgrade notes
89
+
90
+ - Existing calendar indicator values remain supported; `cell` is still the default.
91
+ - `DomAgentConversation.maxMessageWidth` now controls the centred message column
92
+ and composer together. Review custom conversation layouts after upgrading.
93
+ - Applications that supply their own theme can set `--skin-control-bg` to retain
94
+ a different control background.
95
+
3
96
  ## 0.2.1 - 2026-09-21
4
97
 
5
98
  Vue Router 5 support and a public home for DOM Studio release notes.
package/LICENSE.md CHANGED
@@ -28,11 +28,14 @@ the order confirmation.
28
28
  An Individual licence permits one named individual to:
29
29
 
30
30
  - use and modify the Software in unlimited personal or commercial products owned by that individual;
31
+ - incorporate the Software into bespoke products created for that individual's clients;
31
32
  - deploy and distribute those products to end users; and
32
33
  - maintain those products for their end users.
33
34
 
34
- The Individual licence does not permit sharing the Software or paid source
35
- access with colleagues, employees, contractors, or clients.
35
+ The Individual licence covers one named developer. It does not permit sharing
36
+ the component library or paid source access with colleagues, employees, or
37
+ contractors. A client may receive the Software as part of a bespoke end product
38
+ under the client-delivery terms below.
36
39
 
37
40
  ### Team
38
41
 
@@ -44,9 +47,14 @@ A Team licence permits the purchasing legal entity to:
44
47
  - incorporate the Software into bespoke products created for its clients.
45
48
 
46
49
  Contractors may use the Software only while working on behalf of the purchasing
47
- entity and must not retain or reuse it for unrelated work. A client may receive
48
- the Software only as part of a bespoke end product and may not extract or reuse
49
- it as a standalone component library unless the client obtains its own licence.
50
+ entity and must not retain or reuse it for unrelated work.
51
+
52
+ ### Client delivery
53
+
54
+ Under either commercial licence, a client may receive the Software only as part
55
+ of a bespoke end product. The client may operate and maintain that end product,
56
+ but may not extract or reuse the Software as a standalone component library or
57
+ in another product unless the client obtains its own licence.
50
58
 
51
59
  ## Permitted modifications and distribution
52
60
 
@@ -69,10 +77,16 @@ You must not:
69
77
 
70
78
  ## Updates
71
79
 
72
- A purchase described as "lifetime v1 access" includes releases published under
73
- major version 1 while DOM Studio makes them available. It does not include a
74
- right to future major versions, hosted services, consulting, or support unless
75
- the order confirmation says otherwise.
80
+ A commercial licence permits perpetual use of the versions obtained under it,
81
+ subject to the terms of this licence. Included updates cover releases published
82
+ under major version 1 while DOM Studio makes them available. Future major
83
+ upgrades are optional and may be sold separately; declining an upgrade does not
84
+ end the right to use the versions already obtained.
85
+
86
+ Hosted services, architecture reviews, consulting, and support are not included
87
+ unless the order confirmation says otherwise. Existing purchases, including
88
+ introductory £1 purchases and purchases described as "lifetime v1 access", retain
89
+ their purchased access and any additional rights or services promised at purchase.
76
90
 
77
91
  ## Ownership
78
92
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getdom/studio",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "A Vue 3 and Web Component interface system for application UI.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -1778,7 +1778,7 @@ export const componentCatalog = [
1778
1778
  },
1779
1779
  "name": "Agent conversation list",
1780
1780
  "tag": "<DomAgentConversationList>",
1781
- "description": "A reusable AI conversation navigation list with search, agent labels, unread counts, status pills, and active-thread selection.",
1781
+ "description": "A reusable AI conversation navigation list with search, agent labels, unread counts, status indicators, and active-thread selection.",
1782
1782
  "icon": "M5 6h14M5 12h14M5 18h8",
1783
1783
  "events": [
1784
1784
  {
@@ -3090,8 +3090,8 @@ export const componentCatalog = [
3090
3090
  "slots": [
3091
3091
  {
3092
3092
  "name": "item",
3093
- "payload": "{ item, index }",
3094
- "description": "Replaces the rendering of each suggestion."
3093
+ "payload": "{ item, index, selected, query }",
3094
+ "description": "Replaces each suggestion body without replacing the selection checkmark."
3095
3095
  }
3096
3096
  ],
3097
3097
  "events": [
@@ -3109,6 +3109,19 @@ export const componentCatalog = [
3109
3109
  "name": "commit",
3110
3110
  "payload": "({ value, label, custom })",
3111
3111
  "description": "Fired when the current text is committed with Enter."
3112
+ },
3113
+ {
3114
+ "name": "load-more",
3115
+ "payload": "{ query }",
3116
+ "description": "Request the next page when hasMore is true."
3117
+ },
3118
+ {
3119
+ "name": "open",
3120
+ "description": "Fired when suggestions open."
3121
+ },
3122
+ {
3123
+ "name": "close",
3124
+ "description": "Fired when suggestions close."
3112
3125
  }
3113
3126
  ]
3114
3127
  },
@@ -3308,8 +3321,8 @@ export const componentCatalog = [
3308
3321
  "slots": [
3309
3322
  {
3310
3323
  "name": "item",
3311
- "payload": "{ item, index }",
3312
- "description": "Replaces the rendering of each option."
3324
+ "payload": "{ item, index, selected, query }",
3325
+ "description": "Replaces each option body. The control keeps the selected checkmark."
3313
3326
  }
3314
3327
  ],
3315
3328
  "events": [
@@ -3322,6 +3335,24 @@ export const componentCatalog = [
3322
3335
  "name": "select",
3323
3336
  "payload": "({ item, value, label })",
3324
3337
  "description": "Fired when an option is selected. item is the original option object."
3338
+ },
3339
+ {
3340
+ "name": "list-scroll",
3341
+ "payload": "(event: Event)",
3342
+ "description": "Fired when the floating option list scrolls."
3343
+ },
3344
+ {
3345
+ "name": "load-more",
3346
+ "payload": "{ query }",
3347
+ "description": "Request the next page when hasMore is true. The parent owns fetching and appending options."
3348
+ },
3349
+ {
3350
+ "name": "open",
3351
+ "description": "Fired when the options open with an empty transient query."
3352
+ },
3353
+ {
3354
+ "name": "close",
3355
+ "description": "Fired when the options close."
3325
3356
  }
3326
3357
  ]
3327
3358
  },
@@ -3637,6 +3668,49 @@ export const componentCatalog = [
3637
3668
  },
3638
3669
  "studio": {}
3639
3670
  },
3671
+ {
3672
+ "id": "forms/font-picker",
3673
+ "path": "../forms/font-picker/DomFontPicker.vue",
3674
+ "section": "forms",
3675
+ "slug": "font-picker",
3676
+ "exportName": "DomFontPicker",
3677
+ "route": "/forms/font-picker",
3678
+ "doc": {
3679
+ "name": "Font picker",
3680
+ "tag": "<DomFontPicker>",
3681
+ "description": "Searchable Google Fonts picker that previews only visible families and keeps font network loading inside the mounted control.",
3682
+ "icon": "M5 5h14M12 5v14M8 19h8",
3683
+ "playground": {
3684
+ "initial": {
3685
+ "label": "Font family",
3686
+ "modelValue": "Lato",
3687
+ "placeholder": "Search Google Fonts",
3688
+ "category": "",
3689
+ "pageSize": 24,
3690
+ "previewText": "",
3691
+ "loadFonts": true
3692
+ }
3693
+ },
3694
+ "events": [
3695
+ {
3696
+ "name": "update:modelValue",
3697
+ "payload": "string",
3698
+ "description": "Fired with the selected font value."
3699
+ },
3700
+ {
3701
+ "name": "select",
3702
+ "payload": "{ item, value, label }",
3703
+ "description": "Fired when a font family is selected."
3704
+ },
3705
+ {
3706
+ "name": "query",
3707
+ "payload": "string",
3708
+ "description": "Fired as the user searches the catalog."
3709
+ }
3710
+ ]
3711
+ },
3712
+ "studio": {}
3713
+ },
3640
3714
  {
3641
3715
  "id": "forms/form",
3642
3716
  "path": "../forms/form/DomForm.vue",
@@ -4053,13 +4127,13 @@ export const componentCatalog = [
4053
4127
  "slots": [
4054
4128
  {
4055
4129
  "name": "value",
4056
- "payload": "{ option, value, label, placeholder }",
4057
- "description": "Custom selected-value markup inside the trigger."
4130
+ "payload": "{ option, options, value, label, placeholder }",
4131
+ "description": "Custom selected-value markup inside the trigger. options contains all selected records in multiple mode."
4058
4132
  },
4059
4133
  {
4060
4134
  "name": "option",
4061
- "payload": "{ option, index, selected, optionId }",
4062
- "description": "Custom option markup inside the floating list. optionId identifies the focusable option button for external floating UI."
4135
+ "payload": "{ option, index, selected, query, optionId }",
4136
+ "description": "Custom option markup with the transient query for safe text highlighting. optionId identifies the focusable row."
4063
4137
  },
4064
4138
  {
4065
4139
  "name": "group",
@@ -4070,13 +4144,36 @@ export const componentCatalog = [
4070
4144
  "events": [
4071
4145
  {
4072
4146
  "name": "update:modelValue",
4073
- "payload": "(value: string)",
4074
- "description": "Emitted when the selected option changes."
4147
+ "payload": "string | number | Array<string | number>",
4148
+ "description": "Emitted with one committed value or an array in multiple mode."
4075
4149
  },
4076
4150
  {
4077
4151
  "name": "select",
4078
4152
  "payload": "({ option, value, label })",
4079
4153
  "description": "Emitted with the selected option record."
4154
+ },
4155
+ {
4156
+ "name": "query",
4157
+ "payload": "string",
4158
+ "description": "Emitted on open with an empty query and as the user types in the separate search input."
4159
+ },
4160
+ {
4161
+ "name": "load-more",
4162
+ "payload": "{ query }",
4163
+ "description": "Request another page when hasMore is true. The application owns cursors, requests, errors and appending options."
4164
+ },
4165
+ {
4166
+ "name": "list-scroll",
4167
+ "payload": "Event",
4168
+ "description": "Scroll event for optional visibility-based previews."
4169
+ },
4170
+ {
4171
+ "name": "open",
4172
+ "description": "Emitted when the panel opens."
4173
+ },
4174
+ {
4175
+ "name": "close",
4176
+ "description": "Emitted when the panel closes."
4080
4177
  }
4081
4178
  ],
4082
4179
  "keyboard": [
@@ -4129,8 +4226,8 @@ export const componentCatalog = [
4129
4226
  "slots": [
4130
4227
  {
4131
4228
  "name": "item",
4132
- "payload": "{ item, index, active, custom }",
4133
- "description": "Replaces each suggestion row."
4229
+ "payload": "{ item, index, active, selected, query, custom }",
4230
+ "description": "Replaces each suggestion row while retaining the selected checkmark."
4134
4231
  },
4135
4232
  {
4136
4233
  "name": "tag",
@@ -4163,6 +4260,11 @@ export const componentCatalog = [
4163
4260
  "name": "change",
4164
4261
  "payload": "Array",
4165
4262
  "description": "Fired after the selected values array changes."
4263
+ },
4264
+ {
4265
+ "name": "load-more",
4266
+ "payload": "{ query }",
4267
+ "description": "Request the next server page when hasMore is true."
4166
4268
  }
4167
4269
  ]
4168
4270
  },
@@ -439,12 +439,17 @@ function matchesShortcut(event, shortcut) {
439
439
  .command-panel-blur {
440
440
  --skin-overlay-bg: var(
441
441
  --dom-command-palette-background,
442
- color-mix(in oklch, var(--canvas) 82%, transparent)
442
+ color-mix(in oklch, var(--canvas) 68%, transparent)
443
443
  );
444
444
  --skin-overlay-backdrop: var(
445
445
  --dom-command-palette-backdrop,
446
446
  blur(20px) saturate(1.12)
447
447
  );
448
+ --skin-overlay-shadow: var(
449
+ --dom-command-palette-shadow,
450
+ 0 40px 100px -24px rgb(15 23 42 / 0.3),
451
+ 0 16px 40px -20px rgb(15 23 42 / 0.2)
452
+ );
448
453
  }
449
454
 
450
455
  .command-panel-entering {
@@ -103,6 +103,8 @@ export const agentChatShellProps = {
103
103
  import { computed, useId } from 'vue';
104
104
  import DomIconButton from '../../components/icon-button/DomIconButton.vue';
105
105
  import DomSplitterPanel from '../../components/splitter-panel/DomSplitterPanel.vue';
106
+ import DomAvatar from '../../visual/avatar/DomAvatar.vue';
107
+ import DomBadge from '../../visual/badge/DomBadge.vue';
106
108
  import DomStatusPill from '../../visual/status-pill/DomStatusPill.vue';
107
109
  import DomAgentConversation from './DomAgentConversation.vue';
108
110
  import DomAgentConversationList from './DomAgentConversationList.vue';
@@ -364,7 +366,7 @@ function conversationStatusTone() {
364
366
  <template>
365
367
  <section
366
368
  :id="shellId"
367
- class="h-full min-h-0 w-full min-w-0 overflow-hidden rounded-lg border border-border bg-canvas text-canvas-fg shadow-sm"
369
+ class="h-full min-h-0 w-full min-w-0 overflow-hidden rounded-xl border border-border bg-canvas text-canvas-fg shadow-sm"
368
370
  :aria-label="title"
369
371
  >
370
372
  <div class="hidden h-full min-h-0 md:block">
@@ -376,7 +378,7 @@ function conversationStatusTone() {
376
378
  :min-main="minConversation"
377
379
  :min-end="asideMinimum"
378
380
  :handle-size="8"
379
- handle-class="transition hover:bg-border/70 data-[dragging=true]:bg-ring/30"
381
+ handle-class="relative before:pointer-events-none before:absolute before:inset-y-0 before:left-1/2 before:w-px before:-translate-x-1/2 before:bg-border/70 before:transition hover:before:bg-ring/60 data-[dragging=true]:before:bg-ring"
380
382
  @update:start-size="updateSidebarSize"
381
383
  @update:end-size="updateAsideSize"
382
384
  >
@@ -440,70 +442,67 @@ function conversationStatusTone() {
440
442
  <template v-if="asideOpen" #end>
441
443
  <slot name="aside" :conversation="conversation" :active-agent="activeAgent" :tool-parts="toolParts">
442
444
  <aside class="flex h-full min-h-0 flex-col bg-canvas">
443
- <header class="shrink-0 border-b border-border bg-secondary/25 p-4">
444
- <div class="flex items-start justify-between gap-3">
445
+ <header class="flex h-14 shrink-0 items-center justify-between gap-3 border-b border-border/70 px-4">
446
+ <div class="flex min-w-0 items-center gap-2.5">
447
+ <DomAvatar size="sm" shape="rounded" :src="activeAgent?.avatarUrl || ''" :name="activeAgentLabel()" />
445
448
  <div class="min-w-0">
446
- <p class="text-xs font-semibold uppercase text-muted-fg">Agent context</p>
447
- <h2 class="mt-1 truncate text-sm font-semibold text-canvas-fg">{{ activeAgentLabel() }}</h2>
449
+ <h2 class="truncate text-sm font-semibold tracking-tight text-canvas-fg">{{ activeAgentLabel() }}</h2>
450
+ <p class="truncate font-mono text-[11px] text-muted-fg">{{ activeModelLabel() }}</p>
448
451
  </div>
449
- <DomIconButton label="Close context panel" size="sm" variant="ghost" icon="M6 6l12 12M18 6 6 18" @click="setAsideOpen(false)" />
450
452
  </div>
453
+ <DomIconButton label="Close context panel" size="sm" variant="ghost" icon="M6 6l12 12M18 6 6 18" @click="setAsideOpen(false)" />
451
454
  </header>
452
- <div class="min-h-0 flex-1 overflow-y-auto p-4">
453
- <div class="rounded-lg border border-border bg-secondary/25 p-4">
454
- <p class="text-xs font-semibold text-muted-fg">Runtime</p>
455
- <p class="mt-1 text-sm font-semibold text-canvas-fg">{{ activeModelLabel() }}</p>
456
- <p class="mt-2 text-sm leading-6 text-muted-fg">{{ activeAgent?.role || 'Connect an agent to show instructions, tools, and memory context here.' }}</p>
457
- </div>
458
-
459
- <div class="mt-4 rounded-lg border border-border p-4">
460
- <div class="flex items-center justify-between gap-3">
461
- <p class="text-xs font-semibold uppercase text-muted-fg">Conversation</p>
462
- <DomStatusPill size="sm" :tone="conversationStatusTone()">{{ streaming ? 'streaming' : (conversation?.status || 'idle') }}</DomStatusPill>
463
- </div>
464
- <dl class="mt-3 grid grid-cols-2 gap-3 text-sm">
465
- <div>
466
- <dt class="text-xs text-muted-fg">Messages</dt>
467
- <dd class="font-semibold text-canvas-fg">{{ messages.length }}</dd>
455
+ <div class="min-h-0 flex-1 divide-y divide-border/70 overflow-y-auto">
456
+ <section class="p-4">
457
+ <p class="text-sm leading-6 text-muted-fg">{{ activeAgent?.role || 'Connect an agent to show instructions, tools, and memory context here.' }}</p>
458
+ <dl class="mt-4 grid grid-cols-3 gap-2">
459
+ <div class="rounded-lg bg-secondary/60 px-3 py-2">
460
+ <dt class="text-[11px] text-muted-fg">Status</dt>
461
+ <dd class="mt-1">
462
+ <DomStatusPill class="capitalize" size="sm" :tone="conversationStatusTone()" :pulse="streaming">{{ streaming ? 'streaming' : (conversation?.status || 'idle') }}</DomStatusPill>
463
+ </dd>
468
464
  </div>
469
- <div>
470
- <dt class="text-xs text-muted-fg">Tools</dt>
471
- <dd class="font-semibold text-canvas-fg">{{ toolParts.length }}</dd>
465
+ <div class="rounded-lg bg-secondary/60 px-3 py-2">
466
+ <dt class="text-[11px] text-muted-fg">Messages</dt>
467
+ <dd class="mt-0.5 text-base font-semibold tabular-nums text-canvas-fg">{{ messages.length }}</dd>
468
+ </div>
469
+ <div class="rounded-lg bg-secondary/60 px-3 py-2">
470
+ <dt class="text-[11px] text-muted-fg">Tools</dt>
471
+ <dd class="mt-0.5 text-base font-semibold tabular-nums text-canvas-fg">{{ toolParts.length }}</dd>
472
472
  </div>
473
473
  </dl>
474
- </div>
474
+ </section>
475
475
 
476
- <div class="mt-4 rounded-lg border border-border p-4">
477
- <p class="text-xs font-semibold uppercase text-muted-fg">Tool calls</p>
478
- <div v-if="toolParts.length" class="mt-3 space-y-2">
479
- <div
476
+ <section class="p-4">
477
+ <h3 class="text-xs font-medium uppercase tracking-wide text-muted-fg">Tool calls</h3>
478
+ <ul v-if="toolParts.length" class="mt-2 -mx-2">
479
+ <li
480
480
  v-for="tool in toolParts"
481
481
  :key="`${tool.messageId}-${tool.id || tool.name}`"
482
- class="rounded-md border border-border bg-secondary/25 p-3"
482
+ class="flex items-center gap-3 rounded-lg px-2 py-2 transition hover:bg-secondary/50"
483
483
  >
484
- <div class="flex items-start justify-between gap-2">
485
- <p class="min-w-0 truncate text-sm font-semibold text-canvas-fg">{{ tool.label || tool.name }}</p>
486
- <span class="shrink-0 text-xs text-muted-fg">{{ tool.status || 'ready' }}</span>
487
- </div>
488
- <p class="mt-1 truncate text-xs text-muted-fg">{{ tool.name }}</p>
489
- </div>
490
- </div>
491
- <p v-else class="mt-3 text-sm leading-6 text-muted-fg">Tool activity appears here as the agent works.</p>
492
- </div>
484
+ <span class="grid size-7 shrink-0 place-items-center rounded-lg bg-secondary text-muted-fg">
485
+ <svg viewBox="0 0 24 24" class="size-3.5" fill="none" aria-hidden="true">
486
+ <path d="M14.7 6.3a4 4 0 0 0-5.4 5.2L4 16.8V20h3.2l5.3-5.3a4 4 0 0 0 5.2-5.4l-2.5 2.5-2.3-.7-.7-2.3 2.5-2.5Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round" />
487
+ </svg>
488
+ </span>
489
+ <span class="min-w-0 flex-1">
490
+ <span class="block truncate text-sm font-medium text-canvas-fg">{{ tool.label || tool.name }}</span>
491
+ <span class="block truncate font-mono text-[11px] text-muted-fg">{{ tool.name }}</span>
492
+ </span>
493
+ <span class="shrink-0 text-xs capitalize text-muted-fg">{{ tool.status || 'ready' }}</span>
494
+ </li>
495
+ </ul>
496
+ <p v-else class="mt-2 text-sm leading-6 text-muted-fg">Tool activity appears here as the agent works.</p>
497
+ </section>
493
498
 
494
- <div class="mt-4 rounded-lg border border-border p-4">
495
- <p class="text-xs font-semibold uppercase text-muted-fg">Available tools</p>
496
- <div v-if="activeAgent?.tools?.length" class="mt-3 flex flex-wrap gap-2">
497
- <span
498
- v-for="tool in activeAgent.tools"
499
- :key="tool"
500
- class="rounded-full border border-border bg-secondary px-2.5 py-1 text-xs font-medium text-muted-fg"
501
- >
502
- {{ tool }}
503
- </span>
499
+ <section class="p-4">
500
+ <h3 class="text-xs font-medium uppercase tracking-wide text-muted-fg">Available tools</h3>
501
+ <div v-if="activeAgent?.tools?.length" class="mt-3 flex flex-wrap gap-1.5">
502
+ <DomBadge v-for="tool in activeAgent.tools" :key="tool" class="font-mono" size="sm">{{ tool }}</DomBadge>
504
503
  </div>
505
- <p v-else class="mt-3 text-sm leading-6 text-muted-fg">No tool manifest supplied.</p>
506
- </div>
504
+ <p v-else class="mt-2 text-sm leading-6 text-muted-fg">No tool manifest supplied.</p>
505
+ </section>
507
506
  </div>
508
507
  </aside>
509
508
  </slot>