@flowdrop/flowdrop 1.12.0 → 1.13.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 (47) hide show
  1. package/dist/components/ConfigForm.svelte +1 -0
  2. package/dist/components/SchemaForm.svelte +1 -0
  3. package/dist/components/form/FormAutocomplete.svelte +67 -10
  4. package/dist/components/form/FormField.svelte +21 -0
  5. package/dist/components/form/FormFieldLight.svelte +1 -0
  6. package/dist/components/interrupt/InterruptBubble.svelte +76 -17
  7. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +11 -0
  8. package/dist/components/playground/ChatBubble.svelte +289 -0
  9. package/dist/components/playground/ChatBubble.svelte.d.ts +10 -0
  10. package/dist/components/playground/HierarchyTrail.svelte +88 -0
  11. package/dist/components/playground/HierarchyTrail.svelte.d.ts +7 -0
  12. package/dist/components/playground/LogRow.svelte +178 -0
  13. package/dist/components/playground/LogRow.svelte.d.ts +8 -0
  14. package/dist/components/playground/MessageBubble.stories.svelte +89 -0
  15. package/dist/components/playground/MessageBubble.svelte +25 -737
  16. package/dist/components/playground/MessageBubble.svelte.d.ts +3 -11
  17. package/dist/components/playground/MessageCard.svelte +106 -0
  18. package/dist/components/playground/MessageCard.svelte.d.ts +10 -0
  19. package/dist/components/playground/MessageMarkdown.svelte +160 -0
  20. package/dist/components/playground/MessageMarkdown.svelte.d.ts +7 -0
  21. package/dist/components/playground/MessageNotice.svelte +120 -0
  22. package/dist/components/playground/MessageNotice.svelte.d.ts +9 -0
  23. package/dist/components/playground/MessageStream.svelte +85 -1
  24. package/dist/components/playground/MessageTagChip.svelte +99 -0
  25. package/dist/components/playground/MessageTagChip.svelte.d.ts +7 -0
  26. package/dist/components/playground/MessageTagStrip.svelte +37 -0
  27. package/dist/components/playground/MessageTagStrip.svelte.d.ts +7 -0
  28. package/dist/components/playground/PlaygroundStudio.svelte +78 -0
  29. package/dist/components/playground/messageDisplay.d.ts +19 -0
  30. package/dist/components/playground/messageDisplay.js +62 -0
  31. package/dist/form/autocomplete.d.ts +1 -0
  32. package/dist/form/autocomplete.js +1 -0
  33. package/dist/form/index.d.ts +17 -0
  34. package/dist/form/index.js +19 -0
  35. package/dist/messages/defaults.d.ts +5 -0
  36. package/dist/messages/defaults.js +6 -0
  37. package/dist/schemas/v1/workflow.schema.json +10 -1
  38. package/dist/services/categoriesApi.d.ts +2 -1
  39. package/dist/services/categoriesApi.js +5 -3
  40. package/dist/services/portConfigApi.d.ts +2 -1
  41. package/dist/services/portConfigApi.js +5 -3
  42. package/dist/svelte-app.d.ts +1 -0
  43. package/dist/svelte-app.js +5 -5
  44. package/dist/types/index.d.ts +13 -0
  45. package/dist/types/playground.d.ts +76 -0
  46. package/dist/types/playground.js +14 -0
  47. package/package.json +6 -1
@@ -1,21 +1,13 @@
1
- import type { PlaygroundMessage } from '../../types/playground.js';
2
- /**
3
- * Component props
4
- */
1
+ import { type PlaygroundMessage } from '../../types/playground.js';
5
2
  interface Props {
6
- /** The message to display */
7
3
  message: PlaygroundMessage;
8
- /** Whether to show the timestamp */
9
4
  showTimestamp?: boolean;
10
- /** Whether this is the last message (affects styling) */
11
5
  isLast?: boolean;
12
- /** Whether to render markdown content */
13
6
  enableMarkdown?: boolean;
14
7
  /**
15
8
  * Use compact display mode for system messages.
16
- * When true, system messages are rendered as minimal inline text
17
- * instead of full chat bubbles to reduce visual noise.
18
- * @default true
9
+ * When true (default), system messages without an explicit `display`
10
+ * default to the 'notice' layout instead of 'bubble'.
19
11
  */
20
12
  compactSystemMessages?: boolean;
21
13
  }
@@ -0,0 +1,106 @@
1
+ <!--
2
+ MessageCard — vertical card layout: hierarchy (top) · body (middle) · tags (bottom).
3
+ Picks up shared markdown typography via <MessageMarkdown>.
4
+ -->
5
+
6
+ <script lang="ts">
7
+ import type { PlaygroundMessage } from '../../types/playground.js';
8
+ import HierarchyTrail from './HierarchyTrail.svelte';
9
+ import MessageTagStrip from './MessageTagStrip.svelte';
10
+ import MessageMarkdown from './MessageMarkdown.svelte';
11
+ import { formatTimestamp, getRoleLabel } from './messageDisplay.js';
12
+ import { m } from '../../messages/index.js';
13
+
14
+ interface Props {
15
+ message: PlaygroundMessage;
16
+ showTimestamp?: boolean;
17
+ isLast?: boolean;
18
+ enableMarkdown?: boolean;
19
+ }
20
+
21
+ let { message, showTimestamp = true, isLast = false, enableMarkdown = true }: Props = $props();
22
+
23
+ const level = $derived(message.metadata?.level);
24
+ const hierarchy = $derived(message.hierarchy ?? []);
25
+ const tags = $derived(message.tags ?? []);
26
+ const roleLabel = $derived(getRoleLabel(message, m().playground.roles));
27
+ // Logs render as plain text; everything else respects enableMarkdown.
28
+ const markdown = $derived(enableMarkdown && message.role !== 'log');
29
+ </script>
30
+
31
+ <article
32
+ class="message-card"
33
+ class:message-card--last={isLast}
34
+ class:message-card--error={level === 'error'}
35
+ class:message-card--warning={level === 'warning'}
36
+ aria-label={roleLabel}
37
+ >
38
+ {#if hierarchy.length > 0 || showTimestamp}
39
+ <header class="message-card__header">
40
+ <HierarchyTrail items={hierarchy} />
41
+ {#if showTimestamp}
42
+ <time
43
+ class="message-card__timestamp"
44
+ datetime={message.timestamp}
45
+ aria-label="sent at {formatTimestamp(message.timestamp)}"
46
+ >{formatTimestamp(message.timestamp)}</time>
47
+ {/if}
48
+ </header>
49
+ {/if}
50
+ <MessageMarkdown content={message.content} enableMarkdown={markdown} />
51
+ <MessageTagStrip {tags} />
52
+ </article>
53
+
54
+ <style>
55
+ .message-card {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: var(--fd-space-xs);
59
+ margin: var(--fd-space-3xs) var(--fd-space-xl);
60
+ padding: var(--fd-space-sm) var(--fd-space-md);
61
+ border-radius: var(--fd-radius-lg);
62
+ background-color: var(--fd-card);
63
+ border: 1px solid var(--fd-border);
64
+ color: var(--fd-card-foreground);
65
+ font-size: var(--fd-text-sm);
66
+ line-height: var(--fd-leading-normal);
67
+ /* fd-fade-in + reduced-motion guard live in MessageStream.svelte */
68
+ animation: fd-fade-in 0.18s ease-out;
69
+ }
70
+
71
+ .message-card--last {
72
+ margin-bottom: var(--fd-space-xl);
73
+ }
74
+
75
+ .message-card--error {
76
+ border-color: var(--fd-error);
77
+ background-color: color-mix(in srgb, var(--fd-error) 6%, var(--fd-card));
78
+ }
79
+
80
+ .message-card--warning {
81
+ border-color: var(--fd-warning);
82
+ background-color: color-mix(in srgb, var(--fd-warning) 6%, var(--fd-card));
83
+ }
84
+
85
+ .message-card__header {
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: space-between;
89
+ gap: var(--fd-space-sm);
90
+ min-width: 0;
91
+ }
92
+
93
+ .message-card__timestamp {
94
+ flex-shrink: 0;
95
+ font-size: 0.625rem;
96
+ font-family: var(--fd-font-mono);
97
+ color: var(--fd-muted-foreground);
98
+ }
99
+
100
+ @media (max-width: 640px) {
101
+ .message-card {
102
+ margin: var(--fd-space-3xs) var(--fd-space-md);
103
+ padding: var(--fd-space-xs) var(--fd-space-sm);
104
+ }
105
+ }
106
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { PlaygroundMessage } from '../../types/playground.js';
2
+ interface Props {
3
+ message: PlaygroundMessage;
4
+ showTimestamp?: boolean;
5
+ isLast?: boolean;
6
+ enableMarkdown?: boolean;
7
+ }
8
+ declare const MessageCard: import("svelte").Component<Props, {}, "">;
9
+ type MessageCard = ReturnType<typeof MessageCard>;
10
+ export default MessageCard;
@@ -0,0 +1,160 @@
1
+ <!--
2
+ MessageMarkdown Component
3
+
4
+ Renders message content. Wraps marked + sanitizeHtml and applies the
5
+ shared markdown typography. Used by ChatBubble (assistant/system/user)
6
+ and MessageCard so they share one set of typography rules.
7
+
8
+ Consumers scope overrides via :global(.message-markdown ...) from their
9
+ own component CSS — see ChatBubble's user-bubble rules.
10
+
11
+ When `enableMarkdown` is false (or the role is 'log'), the content is
12
+ rendered as plain text.
13
+ -->
14
+
15
+ <script lang="ts">
16
+ import { marked } from 'marked';
17
+ import { sanitizeHtml } from '../../utils/sanitize.js';
18
+
19
+ interface Props {
20
+ content: string;
21
+ enableMarkdown?: boolean;
22
+ }
23
+
24
+ let { content, enableMarkdown = true }: Props = $props();
25
+
26
+ const rendered = $derived(
27
+ enableMarkdown ? sanitizeHtml(marked.parse(content || '') as string) : null
28
+ );
29
+ </script>
30
+
31
+ <div class="message-markdown">
32
+ {#if rendered !== null}
33
+ <!-- Markdown sanitized via DOMPurify in sanitizeHtml. -->
34
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
35
+ {@html rendered}
36
+ {:else}
37
+ {content}
38
+ {/if}
39
+ </div>
40
+
41
+ <style>
42
+ .message-markdown {
43
+ line-height: var(--fd-leading-relaxed);
44
+ word-break: break-word;
45
+ }
46
+
47
+ .message-markdown :global(p) {
48
+ margin: 0 0 var(--fd-space-md) 0;
49
+ }
50
+
51
+ .message-markdown :global(p:last-child) {
52
+ margin-bottom: 0;
53
+ }
54
+
55
+ .message-markdown :global(h1),
56
+ .message-markdown :global(h2),
57
+ .message-markdown :global(h3),
58
+ .message-markdown :global(h4),
59
+ .message-markdown :global(h5),
60
+ .message-markdown :global(h6) {
61
+ margin: var(--fd-space-xl) 0 var(--fd-space-xs) 0;
62
+ font-weight: 600;
63
+ line-height: 1.3;
64
+ }
65
+
66
+ .message-markdown :global(h1:first-child),
67
+ .message-markdown :global(h2:first-child),
68
+ .message-markdown :global(h3:first-child),
69
+ .message-markdown :global(h4:first-child),
70
+ .message-markdown :global(h5:first-child),
71
+ .message-markdown :global(h6:first-child) {
72
+ margin-top: 0;
73
+ }
74
+
75
+ .message-markdown :global(h1) { font-size: var(--fd-text-xl); }
76
+ .message-markdown :global(h2) { font-size: var(--fd-text-lg); }
77
+ .message-markdown :global(h3) { font-size: var(--fd-text-base); }
78
+
79
+ .message-markdown :global(ul),
80
+ .message-markdown :global(ol) {
81
+ margin: var(--fd-space-xs) 0;
82
+ padding-left: var(--fd-space-3xl);
83
+ }
84
+
85
+ .message-markdown :global(li) {
86
+ margin: var(--fd-space-3xs) 0;
87
+ }
88
+
89
+ .message-markdown :global(code) {
90
+ background-color: var(--fd-secondary);
91
+ padding: 0.125rem var(--fd-space-3xs);
92
+ border-radius: var(--fd-radius-sm);
93
+ font-family: var(--fd-font-mono);
94
+ font-size: 0.875em;
95
+ }
96
+
97
+ .message-markdown :global(pre) {
98
+ background-color: var(--fd-foreground);
99
+ color: var(--fd-background);
100
+ padding: var(--fd-space-md) var(--fd-space-xl);
101
+ border-radius: var(--fd-radius-lg);
102
+ overflow-x: auto;
103
+ margin: var(--fd-space-md) 0;
104
+ font-size: var(--fd-text-sm);
105
+ line-height: var(--fd-leading-normal);
106
+ }
107
+
108
+ .message-markdown :global(pre code) {
109
+ background-color: transparent;
110
+ padding: 0;
111
+ border-radius: 0;
112
+ color: inherit;
113
+ font-size: inherit;
114
+ }
115
+
116
+ .message-markdown :global(blockquote) {
117
+ border-left: 3px solid var(--fd-border-strong);
118
+ padding-left: var(--fd-space-xl);
119
+ margin: var(--fd-space-md) 0;
120
+ color: var(--fd-muted-foreground);
121
+ font-style: italic;
122
+ }
123
+
124
+ .message-markdown :global(a) {
125
+ color: var(--fd-primary);
126
+ text-decoration: none;
127
+ }
128
+
129
+ .message-markdown :global(a:hover) {
130
+ text-decoration: underline;
131
+ }
132
+
133
+ .message-markdown :global(hr) {
134
+ border: none;
135
+ border-top: 1px solid var(--fd-border);
136
+ margin: var(--fd-space-xl) 0;
137
+ }
138
+
139
+ .message-markdown :global(table) {
140
+ border-collapse: collapse;
141
+ width: 100%;
142
+ margin: var(--fd-space-md) 0;
143
+ font-size: var(--fd-text-sm);
144
+ }
145
+
146
+ .message-markdown :global(th),
147
+ .message-markdown :global(td) {
148
+ border: 1px solid var(--fd-border);
149
+ padding: var(--fd-space-xs) var(--fd-space-md);
150
+ text-align: left;
151
+ }
152
+
153
+ .message-markdown :global(th) {
154
+ background-color: var(--fd-muted);
155
+ font-weight: 600;
156
+ }
157
+
158
+ .message-markdown :global(strong) { font-weight: 600; }
159
+ .message-markdown :global(em) { font-style: italic; }
160
+ </style>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ content: string;
3
+ enableMarkdown?: boolean;
4
+ }
5
+ declare const MessageMarkdown: import("svelte").Component<Props, {}, "">;
6
+ type MessageMarkdown = ReturnType<typeof MessageMarkdown>;
7
+ export default MessageMarkdown;
@@ -0,0 +1,120 @@
1
+ <!--
2
+ MessageNotice — compact centered inline notice for system messages.
3
+ No role="status" — the parent <div role="log"> already announces additions.
4
+ -->
5
+
6
+ <script lang="ts">
7
+ import Icon from '@iconify/svelte';
8
+ import type { PlaygroundMessage } from '../../types/playground.js';
9
+ import HierarchyTrail from './HierarchyTrail.svelte';
10
+ import MessageTagStrip from './MessageTagStrip.svelte';
11
+ import { formatTimestamp, getLogLevelIcon } from './messageDisplay.js';
12
+
13
+ interface Props {
14
+ message: PlaygroundMessage;
15
+ showTimestamp?: boolean;
16
+ isLast?: boolean;
17
+ }
18
+
19
+ let { message, showTimestamp = true, isLast = false }: Props = $props();
20
+
21
+ const level = $derived(message.metadata?.level);
22
+ const hierarchy = $derived(message.hierarchy ?? []);
23
+ const tags = $derived(message.tags ?? []);
24
+ </script>
25
+
26
+ <div
27
+ class="system-notice"
28
+ class:system-notice--last={isLast}
29
+ class:system-notice--warning={level === 'warning'}
30
+ class:system-notice--error={level === 'error'}
31
+ class:system-notice--debug={level === 'debug'}
32
+ >
33
+ <Icon icon={getLogLevelIcon(level)} class="system-notice__icon" aria-hidden="true" />
34
+ {#if message.metadata?.source}
35
+ <span class="system-notice__source">{message.metadata.source}</span>
36
+ {/if}
37
+ <HierarchyTrail items={hierarchy} />
38
+ <span class="system-notice__text">{message.content}</span>
39
+ <MessageTagStrip {tags} />
40
+ {#if showTimestamp}
41
+ <time
42
+ class="system-notice__timestamp"
43
+ datetime={message.timestamp}
44
+ aria-label="sent at {formatTimestamp(message.timestamp)}"
45
+ >{formatTimestamp(message.timestamp)}</time>
46
+ {/if}
47
+ </div>
48
+
49
+ <style>
50
+ .system-notice {
51
+ display: flex;
52
+ flex-wrap: wrap;
53
+ align-items: center;
54
+ justify-content: center;
55
+ gap: var(--fd-space-3xs);
56
+ padding: var(--fd-space-3xs) var(--fd-space-md);
57
+ margin: var(--fd-space-3xs) 0;
58
+ font-size: var(--fd-text-xs);
59
+ color: var(--fd-muted-foreground);
60
+ text-align: center;
61
+ min-width: 0;
62
+ }
63
+
64
+ .system-notice--last {
65
+ margin-bottom: var(--fd-space-md);
66
+ }
67
+
68
+ .system-notice :global(.system-notice__icon) {
69
+ flex-shrink: 0;
70
+ font-size: var(--fd-text-sm);
71
+ color: var(--fd-border-strong);
72
+ }
73
+
74
+ .system-notice__source {
75
+ flex-shrink: 0;
76
+ font-size: 0.6rem;
77
+ text-transform: uppercase;
78
+ letter-spacing: 0.06em;
79
+ color: var(--fd-muted-foreground);
80
+ background-color: var(--fd-muted);
81
+ border: 1px solid var(--fd-border);
82
+ border-radius: var(--fd-radius-sm);
83
+ padding: 0 0.25rem;
84
+ line-height: 1.4;
85
+ }
86
+
87
+ .system-notice__text {
88
+ min-width: 0;
89
+ overflow-wrap: anywhere;
90
+ line-height: var(--fd-leading-tight);
91
+ }
92
+
93
+ .system-notice--warning,
94
+ .system-notice--warning :global(.system-notice__icon) {
95
+ color: var(--fd-warning);
96
+ }
97
+
98
+ .system-notice--error,
99
+ .system-notice--error :global(.system-notice__icon) {
100
+ color: var(--fd-error);
101
+ }
102
+
103
+ .system-notice--debug {
104
+ color: var(--fd-border-strong);
105
+ opacity: 0.6;
106
+ }
107
+
108
+ .system-notice__timestamp {
109
+ flex-shrink: 0;
110
+ font-size: 0.625rem;
111
+ color: var(--fd-border-strong);
112
+ font-family: var(--fd-font-mono);
113
+ }
114
+
115
+ @media (max-width: 640px) {
116
+ .system-notice__timestamp {
117
+ display: none;
118
+ }
119
+ }
120
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { PlaygroundMessage } from '../../types/playground.js';
2
+ interface Props {
3
+ message: PlaygroundMessage;
4
+ showTimestamp?: boolean;
5
+ isLast?: boolean;
6
+ }
7
+ declare const MessageNotice: import("svelte").Component<Props, {}, "">;
8
+ type MessageNotice = ReturnType<typeof MessageNotice>;
9
+ export default MessageNotice;
@@ -189,6 +189,8 @@
189
189
  {interrupt}
190
190
  showTimestamp={showTimestamps}
191
191
  onResolved={onInterruptResolved}
192
+ hierarchy={message.hierarchy}
193
+ tags={message.tags}
192
194
  />
193
195
  {/if}
194
196
  {:else}
@@ -221,6 +223,88 @@
221
223
  min-height: 0;
222
224
  overflow-y: auto;
223
225
  padding: var(--fd-space-3xl);
226
+
227
+ /* Establish a containment context so message rows can adapt to the
228
+ stream's actual width (not the viewport's). The matching @container
229
+ queries (for .log-row) live below in the same <style> block, so
230
+ renaming the container only requires editing this file. */
231
+ container-type: inline-size;
232
+ container-name: fd-message-stream;
233
+ }
234
+
235
+ /* Shared fade-in for newly-appended message rows. `-global-` so
236
+ ChatBubble.svelte / MessageCard.svelte can reference it without
237
+ redeclaring. Honour reduced-motion in the same place. */
238
+ @keyframes -global-fd-fade-in {
239
+ from {
240
+ opacity: 0;
241
+ transform: translateY(6px);
242
+ }
243
+ to {
244
+ opacity: 1;
245
+ transform: translateY(0);
246
+ }
247
+ }
248
+
249
+ @media (prefers-reduced-motion: reduce) {
250
+ :global(.message-bubble),
251
+ :global(.message-card) {
252
+ animation: none;
253
+ }
254
+ }
255
+
256
+ /* Container-query reshaping for log rows. Lives next to the
257
+ container-name declaration so the coupling is local — selectors are
258
+ :global because .log-row is a sibling component's class.
259
+
260
+ Tier 1 (≤720px): two rows — level/body, then tags/timestamp.
261
+ Tier 2 (≤480px): collapse further; body forces internal line break. */
262
+ @container fd-message-stream (max-width: 720px) {
263
+ :global(.log-row) {
264
+ display: grid;
265
+ grid-template-columns: auto 1fr auto;
266
+ grid-template-areas:
267
+ "level body body"
268
+ ". tags timestamp";
269
+ align-items: baseline;
270
+ row-gap: var(--fd-space-2xs);
271
+ column-gap: var(--fd-space-sm);
272
+ }
273
+ :global(.log-row__level) { grid-area: level; }
274
+ :global(.log-row__body) { grid-area: body; min-width: 0; }
275
+ :global(.log-row__tags) { grid-area: tags; justify-self: start; }
276
+ :global(.log-row__timestamp) { grid-area: timestamp; justify-self: end; }
277
+ }
278
+
279
+ @container fd-message-stream (max-width: 480px) {
280
+ :global(.log-row) {
281
+ grid-template-columns: auto 1fr;
282
+ grid-template-areas:
283
+ "level body"
284
+ ". tags";
285
+ }
286
+ :global(.log-row__text) {
287
+ flex-basis: 100%;
288
+ min-width: 0;
289
+ }
290
+ :global(.log-row__timestamp) {
291
+ display: none;
292
+ }
293
+ /* Drop the source + node chips: source is implied by the level
294
+ colour, node duplicates the hierarchy trail's last entry. Keeping
295
+ them at this width forced each chip onto its own line and made
296
+ log rows 5–6 lines tall. */
297
+ :global(.log-row__source),
298
+ :global(.log-row__node) {
299
+ display: none;
300
+ }
301
+ /* Reclaim horizontal room by tightening the row's own padding —
302
+ can't shrink the stream's padding from inside its own
303
+ container query. */
304
+ :global(.log-row) {
305
+ padding-left: var(--fd-space-xs);
306
+ padding-right: var(--fd-space-xs);
307
+ }
224
308
  }
225
309
 
226
310
  .message-stream__typing {
@@ -277,7 +361,7 @@
277
361
 
278
362
  @media (max-width: 640px) {
279
363
  .message-stream {
280
- padding: var(--fd-space-xl);
364
+ padding: var(--fd-space-md) 0;
281
365
  }
282
366
  }
283
367
  </style>
@@ -0,0 +1,99 @@
1
+ <!--
2
+ MessageTagChip Component
3
+
4
+ Renders a single server-emitted MessageTag as a compact chip. Semantic
5
+ color comes from tag.color, visual emphasis from tag.variant. Used by
6
+ MessageBubble and InterruptBubble.
7
+
8
+ Styling: a single base rule reads from CSS custom properties; one rule
9
+ per color sets --chip-c, one rule per variant sets bg/fg/border in terms
10
+ of --chip-c. Adding a color is one line.
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import Icon from '@iconify/svelte';
15
+ import type { MessageTag } from '../../types/playground.js';
16
+
17
+ interface Props {
18
+ tag: MessageTag;
19
+ }
20
+
21
+ let { tag }: Props = $props();
22
+
23
+ const color = $derived(tag.color ?? 'muted');
24
+ const variant = $derived(tag.variant ?? 'subtle');
25
+ </script>
26
+
27
+ <span
28
+ class="message-tag-chip"
29
+ data-color={color}
30
+ data-variant={variant}
31
+ aria-label={tag.type ? `${tag.type}: ${tag.label}` : undefined}
32
+ >
33
+ {#if tag.icon}
34
+ <Icon icon={tag.icon} class="message-tag-chip__icon" aria-hidden="true" />
35
+ {/if}
36
+ <span class="message-tag-chip__label">{tag.label}</span>
37
+ </span>
38
+
39
+ <style>
40
+ .message-tag-chip {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: var(--fd-space-3xs);
44
+ padding: 0 var(--fd-space-3xs);
45
+ border-radius: var(--fd-radius-sm);
46
+ font-family: var(--fd-font-mono);
47
+ font-size: var(--fd-text-2xs);
48
+ line-height: 1.4;
49
+ white-space: nowrap;
50
+ min-width: 0;
51
+ max-width: 100%;
52
+ background-color: var(--chip-bg);
53
+ color: var(--chip-fg);
54
+ border: 1px solid var(--chip-border, transparent);
55
+ }
56
+
57
+ .message-tag-chip__label {
58
+ overflow: hidden;
59
+ text-overflow: ellipsis;
60
+ }
61
+
62
+ .message-tag-chip :global(.message-tag-chip__icon) {
63
+ flex-shrink: 0;
64
+ font-size: 0.875em;
65
+ opacity: 0.8;
66
+ }
67
+
68
+ /* Color hooks — one line per color. To add a color, add a row here. */
69
+ .message-tag-chip[data-color='muted'] { --chip-c: var(--fd-muted-foreground); --chip-c-on: var(--fd-background); }
70
+ .message-tag-chip[data-color='primary'] { --chip-c: var(--fd-primary); --chip-c-on: var(--fd-primary-foreground); }
71
+ .message-tag-chip[data-color='success'] { --chip-c: var(--fd-success, oklch(55% 0.15 145)); --chip-c-on: white; }
72
+ .message-tag-chip[data-color='warning'] { --chip-c: var(--fd-warning); --chip-c-on: var(--fd-background); }
73
+ .message-tag-chip[data-color='error'] { --chip-c: var(--fd-error); --chip-c-on: white; }
74
+ .message-tag-chip[data-color='info'] { --chip-c: var(--fd-info); --chip-c-on: var(--fd-background); }
75
+
76
+ /* Variants — derive bg/fg/border from --chip-c. */
77
+ .message-tag-chip[data-variant='subtle'] {
78
+ --chip-bg: color-mix(in srgb, var(--chip-c) 14%, transparent);
79
+ --chip-fg: var(--chip-c);
80
+ }
81
+ .message-tag-chip[data-variant='subtle'][data-color='muted'] {
82
+ /* Muted is the only color we render against the design's --fd-muted
83
+ surface for legibility; the color-mix path would lose contrast. */
84
+ --chip-bg: var(--fd-muted);
85
+ --chip-fg: var(--fd-muted-foreground);
86
+ }
87
+ .message-tag-chip[data-variant='solid'] {
88
+ --chip-bg: var(--chip-c);
89
+ --chip-fg: var(--chip-c-on);
90
+ }
91
+ .message-tag-chip[data-variant='outline'] {
92
+ --chip-bg: transparent;
93
+ --chip-fg: var(--chip-c);
94
+ --chip-border: var(--chip-c);
95
+ }
96
+ .message-tag-chip[data-variant='outline'][data-color='muted'] {
97
+ --chip-border: var(--fd-border);
98
+ }
99
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { MessageTag } from '../../types/playground.js';
2
+ interface Props {
3
+ tag: MessageTag;
4
+ }
5
+ declare const MessageTagChip: import("svelte").Component<Props, {}, "">;
6
+ type MessageTagChip = ReturnType<typeof MessageTagChip>;
7
+ export default MessageTagChip;
@@ -0,0 +1,37 @@
1
+ <!--
2
+ MessageTagStrip Component
3
+
4
+ A flex-wrapped row of MessageTagChips. Encapsulates the layout the four
5
+ message variants used to copy individually.
6
+ -->
7
+
8
+ <script lang="ts">
9
+ import type { MessageTag } from '../../types/playground.js';
10
+ import MessageTagChip from './MessageTagChip.svelte';
11
+
12
+ interface Props {
13
+ tags: MessageTag[];
14
+ }
15
+
16
+ let { tags }: Props = $props();
17
+ </script>
18
+
19
+ {#if tags.length > 0}
20
+ <!-- No role/label — each chip is already labelled (via tag.type when set)
21
+ and a one-element group adds nothing for AT users. -->
22
+ <span class="message-tag-strip">
23
+ {#each tags as tag (tag.id)}
24
+ <MessageTagChip {tag} />
25
+ {/each}
26
+ </span>
27
+ {/if}
28
+
29
+ <style>
30
+ .message-tag-strip {
31
+ display: inline-flex;
32
+ flex-wrap: wrap;
33
+ align-items: center;
34
+ gap: var(--fd-space-2xs);
35
+ min-width: 0;
36
+ }
37
+ </style>