@urbicon-ui/blocks 6.34.0 → 6.36.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 (127) hide show
  1. package/README.md +11 -1
  2. package/dist/components/Calendar/CalendarHeader.svelte +32 -26
  3. package/dist/components/Calendar/CalendarMiniMonth.svelte +10 -10
  4. package/dist/components/Calendar/calendar.variants.js +19 -5
  5. package/dist/components/Chat/Chat/Chat.svelte +53 -0
  6. package/dist/components/Chat/Chat/Chat.svelte.d.ts +4 -0
  7. package/dist/components/Chat/Chat/chat.variants.d.ts +20 -0
  8. package/dist/components/Chat/Chat/chat.variants.js +15 -0
  9. package/dist/components/Chat/Chat/index.d.ts +47 -0
  10. package/dist/components/Chat/Chat/index.js +2 -0
  11. package/dist/components/Chat/ChatMessage/ChatMessage.svelte +342 -0
  12. package/dist/components/Chat/ChatMessage/ChatMessage.svelte.d.ts +4 -0
  13. package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +138 -0
  14. package/dist/components/Chat/ChatMessage/chat-message.variants.js +118 -0
  15. package/dist/components/Chat/ChatMessage/index.d.ts +102 -0
  16. package/dist/components/Chat/ChatMessage/index.js +2 -0
  17. package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte +301 -0
  18. package/dist/components/Chat/ChatMessageList/ChatMessageList.svelte.d.ts +6 -0
  19. package/dist/components/Chat/ChatMessageList/chat-message-list.variants.d.ts +23 -0
  20. package/dist/components/Chat/ChatMessageList/chat-message-list.variants.js +32 -0
  21. package/dist/components/Chat/ChatMessageList/chat-scroll.d.ts +45 -0
  22. package/dist/components/Chat/ChatMessageList/chat-scroll.js +52 -0
  23. package/dist/components/Chat/ChatMessageList/index.d.ts +93 -0
  24. package/dist/components/Chat/ChatMessageList/index.js +3 -0
  25. package/dist/components/Chat/CitationChip/CitationChip.svelte +107 -0
  26. package/dist/components/Chat/CitationChip/CitationChip.svelte.d.ts +4 -0
  27. package/dist/components/Chat/CitationChip/citation-chip.variants.d.ts +40 -0
  28. package/dist/components/Chat/CitationChip/citation-chip.variants.js +42 -0
  29. package/dist/components/Chat/CitationChip/index.d.ts +69 -0
  30. package/dist/components/Chat/CitationChip/index.js +2 -0
  31. package/dist/components/Chat/CodeBlock/CodeBlock.svelte +120 -0
  32. package/dist/components/Chat/CodeBlock/CodeBlock.svelte.d.ts +4 -0
  33. package/dist/components/Chat/CodeBlock/code-block.variants.d.ts +40 -0
  34. package/dist/components/Chat/CodeBlock/code-block.variants.js +51 -0
  35. package/dist/components/Chat/CodeBlock/index.d.ts +55 -0
  36. package/dist/components/Chat/CodeBlock/index.js +2 -0
  37. package/dist/components/Chat/PromptInput/PromptInput.svelte +432 -0
  38. package/dist/components/Chat/PromptInput/PromptInput.svelte.d.ts +4 -0
  39. package/dist/components/Chat/PromptInput/index.d.ts +165 -0
  40. package/dist/components/Chat/PromptInput/index.js +2 -0
  41. package/dist/components/Chat/PromptInput/prompt-input.variants.d.ts +90 -0
  42. package/dist/components/Chat/PromptInput/prompt-input.variants.js +102 -0
  43. package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte +107 -0
  44. package/dist/components/Chat/ReasoningDisclosure/ReasoningDisclosure.svelte.d.ts +4 -0
  45. package/dist/components/Chat/ReasoningDisclosure/index.d.ts +85 -0
  46. package/dist/components/Chat/ReasoningDisclosure/index.js +2 -0
  47. package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.d.ts +20 -0
  48. package/dist/components/Chat/ReasoningDisclosure/reasoning-disclosure.variants.js +31 -0
  49. package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte +99 -0
  50. package/dist/components/Chat/StreamingMarkdown/MdBlock.svelte.d.ts +9 -0
  51. package/dist/components/Chat/StreamingMarkdown/MdInline.svelte +82 -0
  52. package/dist/components/Chat/StreamingMarkdown/MdInline.svelte.d.ts +9 -0
  53. package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte +118 -0
  54. package/dist/components/Chat/StreamingMarkdown/StreamingMarkdown.svelte.d.ts +4 -0
  55. package/dist/components/Chat/StreamingMarkdown/index.d.ts +86 -0
  56. package/dist/components/Chat/StreamingMarkdown/index.js +2 -0
  57. package/dist/components/Chat/StreamingMarkdown/md-context.d.ts +58 -0
  58. package/dist/components/Chat/StreamingMarkdown/md-context.js +1 -0
  59. package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.d.ts +145 -0
  60. package/dist/components/Chat/StreamingMarkdown/streaming-markdown.variants.js +73 -0
  61. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte +174 -0
  62. package/dist/components/Chat/ToolCallCard/ToolCallCard.svelte.d.ts +4 -0
  63. package/dist/components/Chat/ToolCallCard/index.d.ts +84 -0
  64. package/dist/components/Chat/ToolCallCard/index.js +2 -0
  65. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.d.ts +38 -0
  66. package/dist/components/Chat/ToolCallCard/tool-call-card.variants.js +38 -0
  67. package/dist/components/Chat/chat.types.d.ts +82 -0
  68. package/dist/components/Chat/chat.types.js +7 -0
  69. package/dist/components/Chat/index.d.ts +22 -0
  70. package/dist/components/Chat/index.js +14 -0
  71. package/dist/components/Chat/markdown/blocks.d.ts +54 -0
  72. package/dist/components/Chat/markdown/blocks.js +462 -0
  73. package/dist/components/Chat/markdown/inline.d.ts +31 -0
  74. package/dist/components/Chat/markdown/inline.js +446 -0
  75. package/dist/components/Chat/markdown/repair.d.ts +23 -0
  76. package/dist/components/Chat/markdown/repair.js +337 -0
  77. package/dist/components/Chat/markdown/streaming-harness.d.ts +23 -0
  78. package/dist/components/Chat/markdown/streaming-harness.js +62 -0
  79. package/dist/components/Chat/markdown/types.d.ts +139 -0
  80. package/dist/components/Chat/markdown/types.js +13 -0
  81. package/dist/components/Chat/markdown/url-policy.d.ts +33 -0
  82. package/dist/components/Chat/markdown/url-policy.js +109 -0
  83. package/dist/components/FileUpload/FileUpload.svelte +52 -125
  84. package/dist/components/FileUpload/FileUpload.svelte.d.ts +1 -1
  85. package/dist/components/FileUpload/index.d.ts +2 -28
  86. package/dist/components/Planner/PlannerHeader.svelte +16 -13
  87. package/dist/components/Planner/planner.variants.js +14 -3
  88. package/dist/components/index.d.ts +1 -0
  89. package/dist/components/index.js +1 -0
  90. package/dist/i18n/index.d.ts +268 -382
  91. package/dist/i18n/index.js +83 -9
  92. package/dist/internal/core/CoreIconButton.svelte +66 -0
  93. package/dist/internal/core/CoreIconButton.svelte.d.ts +13 -0
  94. package/dist/internal/core/CoreSpinner.svelte +49 -0
  95. package/dist/internal/core/CoreSpinner.svelte.d.ts +8 -0
  96. package/dist/internal/core/spinner-geometry.d.ts +6 -0
  97. package/dist/internal/core/spinner-geometry.js +6 -0
  98. package/dist/mint/registry.d.ts +20 -2
  99. package/dist/mint/registry.js +20 -2
  100. package/dist/primitives/Badge/Badge.svelte +5 -5
  101. package/dist/primitives/Badge/badge.variants.js +17 -1
  102. package/dist/primitives/Button/Button.svelte +4 -2
  103. package/dist/primitives/Collapsible/Collapsible.svelte +5 -0
  104. package/dist/primitives/Dialog/Dialog.svelte +7 -5
  105. package/dist/primitives/Dialog/dialog.variants.d.ts +7 -0
  106. package/dist/primitives/Dialog/dialog.variants.js +17 -0
  107. package/dist/primitives/Drawer/Drawer.svelte +7 -5
  108. package/dist/primitives/Drawer/drawer.variants.d.ts +8 -0
  109. package/dist/primitives/Drawer/drawer.variants.js +17 -0
  110. package/dist/primitives/Spinner/Spinner.svelte +3 -1
  111. package/dist/primitives/SplitPane/SplitPane.svelte +293 -0
  112. package/dist/primitives/SplitPane/SplitPane.svelte.d.ts +4 -0
  113. package/dist/primitives/SplitPane/index.d.ts +88 -0
  114. package/dist/primitives/SplitPane/index.js +2 -0
  115. package/dist/primitives/SplitPane/split-pane.utils.d.ts +73 -0
  116. package/dist/primitives/SplitPane/split-pane.utils.js +94 -0
  117. package/dist/primitives/SplitPane/split-pane.variants.d.ts +40 -0
  118. package/dist/primitives/SplitPane/split-pane.variants.js +56 -0
  119. package/dist/primitives/Toast/Toaster.svelte +17 -2
  120. package/dist/primitives/index.d.ts +2 -0
  121. package/dist/primitives/index.js +1 -0
  122. package/dist/utils/file-intake.d.ts +78 -0
  123. package/dist/utils/file-intake.js +144 -0
  124. package/dist/utils/index.d.ts +1 -0
  125. package/dist/utils/index.js +1 -0
  126. package/dist/utils/variants.js +44 -10
  127. package/package.json +4 -6
@@ -0,0 +1,109 @@
1
+ /**
2
+ * URL policy enforcement (decision A4 — strict by default).
3
+ *
4
+ * LLM output is untrusted input: prompt-injected markdown links/images are a
5
+ * proven data-exfiltration channel (query-param payloads to attacker hosts)
6
+ * even without any script execution. Policy therefore runs on every URL the
7
+ * parser emits, and blocked URLs never reach the produced node tree — a
8
+ * blocked link/image carries `blocked: true` and an empty href/src.
9
+ *
10
+ * Checks run against the *normalized* absolute URL (WHATWG `URL`), so scheme
11
+ * tricks (`JaVaScRiPt:`, embedded tabs/newlines, `https:\\host`, `/../`
12
+ * path escapes against a prefix) are neutralized before matching.
13
+ */
14
+ export const DEFAULT_LINK_PROTOCOLS = ['http:', 'https:', 'mailto:', 'tel:'];
15
+ /** Placeholder base — only used to detect whether a URL is relative. */
16
+ const RELATIVE_PROBE_BASE = 'https://relative-probe.invalid/';
17
+ /**
18
+ * Strip ASCII tab/newline/CR and C0 controls anywhere in the string (the URL
19
+ * standard removes tab/NL/CR during parsing, so leaving them in would let
20
+ * `java\tscript:` sneak past a naive scheme check while browsers still
21
+ * execute it) and trim outer whitespace.
22
+ */
23
+ function cleanRawUrl(raw) {
24
+ let out = '';
25
+ for (const ch of raw.trim()) {
26
+ const code = ch.charCodeAt(0);
27
+ if (code > 0x1f)
28
+ out += ch;
29
+ }
30
+ return out;
31
+ }
32
+ function resolve(raw) {
33
+ const cleaned = cleanRawUrl(raw);
34
+ if (cleaned.length === 0)
35
+ return { invalid: true };
36
+ // Protocol-relative URLs (`//host/…`) are absolute in effect; resolving
37
+ // them against the probe base would otherwise mislabel them as relative.
38
+ const protocolRelative = cleaned.startsWith('//');
39
+ try {
40
+ const url = new URL(protocolRelative ? `https:${cleaned}` : cleaned);
41
+ return { absolute: true, url };
42
+ }
43
+ catch {
44
+ // Not parseable standalone → candidate for a relative reference.
45
+ }
46
+ try {
47
+ new URL(cleaned, RELATIVE_PROBE_BASE);
48
+ return { absolute: false, cleaned };
49
+ }
50
+ catch {
51
+ return { invalid: true };
52
+ }
53
+ }
54
+ const BLOCKED = { ok: false };
55
+ /**
56
+ * Check a link destination. Relative references are always allowed; absolute
57
+ * URLs must carry an allowed protocol.
58
+ */
59
+ export function checkLinkUrl(raw, policy) {
60
+ const resolved = resolve(raw);
61
+ if ('invalid' in resolved && resolved.invalid)
62
+ return blocked('link', raw, policy);
63
+ if (!resolved.absolute)
64
+ return { ok: true, href: resolved.cleaned };
65
+ const allowed = (policy?.allowedLinkProtocols ?? DEFAULT_LINK_PROTOCOLS).map((p) => p.toLowerCase());
66
+ if (allowed.includes(resolved.url.protocol))
67
+ return { ok: true, href: resolved.url.href };
68
+ return blocked('link', raw, policy);
69
+ }
70
+ /**
71
+ * Check an image source. Relative references are allowed (same-origin is not
72
+ * an exfiltration sink); absolute URLs must match an allowlisted prefix on
73
+ * the normalized href. The default empty allowlist blocks every external
74
+ * image — the CamoLeak lesson, and the posture ChatGPT/Copilot converged on.
75
+ */
76
+ export function checkImageUrl(raw, policy) {
77
+ const resolved = resolve(raw);
78
+ if ('invalid' in resolved && resolved.invalid)
79
+ return blocked('image', raw, policy);
80
+ if (!resolved.absolute)
81
+ return { ok: true, href: resolved.cleaned };
82
+ const prefixes = policy?.allowedImagePrefixes ?? [];
83
+ const href = resolved.url.href;
84
+ for (const prefix of prefixes) {
85
+ if (href.startsWith(normalizePrefix(prefix)))
86
+ return { ok: true, href };
87
+ }
88
+ return blocked('image', raw, policy);
89
+ }
90
+ /**
91
+ * Normalize an allowlist prefix the same way hrefs are normalized (lowercased
92
+ * scheme/host, resolved path) so `HTTPS://Example.com/img/` still matches.
93
+ * A bare-origin prefix gains its trailing slash (`https://cdn.example.com` →
94
+ * `…com/`), which pins it to that origin — without it, `startsWith` would
95
+ * also match `https://cdn.example.com.evil.com/…`. Prefixes that are not
96
+ * full URLs (e.g. `data:`) are kept verbatim.
97
+ */
98
+ function normalizePrefix(prefix) {
99
+ try {
100
+ return new URL(prefix).href;
101
+ }
102
+ catch {
103
+ return prefix;
104
+ }
105
+ }
106
+ function blocked(kind, raw, policy) {
107
+ policy?.onBlocked?.(kind, raw);
108
+ return BLOCKED;
109
+ }
@@ -1,12 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useBlocksI18n } from '../..';
3
- import type {
4
- FileUploadProps,
5
- FileUploadFile,
6
- FileRejection,
7
- FileUploadError,
8
- FileUploadSlotName
9
- } from './index';
3
+ import type { FileUploadProps, FileUploadFile, FileUploadSlotName } from './index';
10
4
  import { fileUploadVariants, type FileUploadVariants } from './fileUpload.variants';
11
5
  import { getBlocksConfig, resolveSlotClasses } from '../../provider';
12
6
  import { resolveIcon } from '../../icons';
@@ -17,10 +11,20 @@
17
11
  import DangerCircleIconDefault from '../../icons/DangerCircleIcon.svelte';
18
12
  import { mintRegistry } from '../../mint';
19
13
  import { Progress } from '../../primitives/Progress';
20
- import { Spinner } from '../../primitives/Spinner';
14
+ // internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
15
+ import CoreSpinner from '../../internal/core/CoreSpinner.svelte';
21
16
  import { fly } from 'svelte/transition';
22
17
  import { quintOut } from 'svelte/easing';
23
18
  import { onDestroy } from 'svelte';
19
+ import {
20
+ createIntakeEntry,
21
+ dragItemsMatchAccept,
22
+ formatFileSize,
23
+ partitionIntake,
24
+ revokeIntakePreviews,
25
+ type FileIntakeConstraints,
26
+ type FileIntakeMessages
27
+ } from '../../utils/file-intake';
24
28
 
25
29
  const bt = useBlocksI18n();
26
30
 
@@ -115,54 +119,27 @@
115
119
 
116
120
  // ── Validation ─────────────────────────────────────────────────────────────
117
121
 
118
- function validateFile(file: File): FileUploadError[] {
119
- const errors: FileUploadError[] = [];
120
-
121
- if (accept) {
122
- const types = Array.isArray(accept) ? accept : accept.split(',').map((t) => t.trim());
123
- const matches = types.some((type) => {
124
- if (type.startsWith('.')) return file.name.toLowerCase().endsWith(type.toLowerCase());
125
- if (type.endsWith('/*')) return file.type.startsWith(type.replace('/*', '/'));
126
- return file.type === type;
127
- });
128
- if (!matches) {
129
- errors.push({
130
- code: 'FILE_INVALID_TYPE',
131
- message: bt('fileUpload.invalidType', { type: file.type || 'unknown' })
132
- });
133
- }
134
- }
135
-
136
- if (file.size > maxFileSize) {
137
- errors.push({
138
- code: 'FILE_TOO_LARGE',
139
- message: bt('fileUpload.tooLarge', { size: formatFileSize(maxFileSize) })
140
- });
141
- }
142
-
143
- if (file.size < minFileSize) {
144
- errors.push({
145
- code: 'FILE_TOO_SMALL',
146
- message: bt('fileUpload.tooSmall', { size: formatFileSize(minFileSize) })
147
- });
148
- }
149
-
150
- if (files.some((f) => f.file.name === file.name && f.file.size === file.size)) {
151
- errors.push({ code: 'FILE_EXISTS', message: bt('fileUpload.exists') });
152
- }
153
-
154
- if (validate) {
155
- const custom = validate(file);
156
- if (custom) errors.push(...custom);
157
- }
158
-
159
- return errors;
160
- }
122
+ // Error texts wired from the component's i18n source; injected into the
123
+ // shared file-intake core so its pure functions stay label-agnostic. Texts
124
+ // are identical to the pre-refactor inline `bt('fileUpload.*')` calls.
125
+ const messages: FileIntakeMessages = {
126
+ invalidType: (type) => bt('fileUpload.invalidType', { type }),
127
+ tooLarge: (size) => bt('fileUpload.tooLarge', { size }),
128
+ tooSmall: (size) => bt('fileUpload.tooSmall', { size }),
129
+ exists: () => bt('fileUpload.exists'),
130
+ tooMany: (count) => bt('fileUpload.tooMany', { count: String(count) })
131
+ };
132
+
133
+ const constraints = $derived<FileIntakeConstraints>({
134
+ accept,
135
+ maxFiles,
136
+ maxFileSize,
137
+ minFileSize,
138
+ validate
139
+ });
161
140
 
162
141
  // ── File Processing ────────────────────────────────────────────────────────
163
142
 
164
- let idCounter = 0;
165
-
166
143
  // Two-way sync between `files[0]` and the convenience `file` binding.
167
144
  // `lastSyncedFile` is the snapshot we last wrote to `file` — comparing
168
145
  // against it lets us tell whether the change came from `files` (internal,
@@ -193,9 +170,7 @@
193
170
  if (file === lastSyncedFile) return;
194
171
 
195
172
  if (file === null) {
196
- for (const entry of files) {
197
- if (entry.preview) URL.revokeObjectURL(entry.preview);
198
- }
173
+ revokeIntakePreviews(files);
199
174
  files = [];
200
175
  lastSyncedFile = null;
201
176
  onFilesChange?.(files);
@@ -203,15 +178,8 @@
203
178
  }
204
179
 
205
180
  const old = files[0];
206
- if (old?.preview) URL.revokeObjectURL(old.preview);
207
- const replacement: FileUploadFile = {
208
- id: `file-${Date.now()}-${++idCounter}`,
209
- file,
210
- status: 'pending',
211
- errors: [],
212
- preview: isImageFile(file) ? URL.createObjectURL(file) : undefined
213
- };
214
- files = [replacement];
181
+ if (old) revokeIntakePreviews([old]);
182
+ files = [createIntakeEntry(file)];
215
183
  lastSyncedFile = file;
216
184
  onFilesChange?.(files);
217
185
  });
@@ -219,36 +187,7 @@
219
187
  function processFiles(incoming: File[]) {
220
188
  if (disabled) return;
221
189
 
222
- const accepted: FileUploadFile[] = [];
223
- const rejected: FileRejection[] = [];
224
-
225
- const remaining = maxFiles - files.length;
226
- const toProcess = incoming.slice(0, remaining);
227
- const excess = incoming.slice(remaining);
228
-
229
- for (const file of excess) {
230
- rejected.push({
231
- file,
232
- errors: [
233
- { code: 'TOO_MANY_FILES', message: bt('fileUpload.tooMany', { count: String(maxFiles) }) }
234
- ]
235
- });
236
- }
237
-
238
- for (const file of toProcess) {
239
- const errors = validateFile(file);
240
- if (errors.length > 0) {
241
- rejected.push({ file, errors });
242
- } else {
243
- accepted.push({
244
- id: `file-${Date.now()}-${++idCounter}`,
245
- file,
246
- status: 'pending',
247
- errors: [],
248
- preview: isImageFile(file) ? URL.createObjectURL(file) : undefined
249
- });
250
- }
251
- }
190
+ const { accepted, rejected } = partitionIntake(incoming, files, constraints, messages);
252
191
 
253
192
  if (accepted.length > 0) {
254
193
  files = [...files, ...accepted];
@@ -262,16 +201,14 @@
262
201
  }
263
202
 
264
203
  function removeFile(entry: FileUploadFile) {
265
- if (entry.preview) URL.revokeObjectURL(entry.preview);
204
+ revokeIntakePreviews([entry]);
266
205
  files = files.filter((f) => f.id !== entry.id);
267
206
  onFileRemove?.(entry);
268
207
  onFilesChange?.(files);
269
208
  }
270
209
 
271
210
  onDestroy(() => {
272
- for (const entry of files) {
273
- if (entry.preview) URL.revokeObjectURL(entry.preview);
274
- }
211
+ revokeIntakePreviews(files);
275
212
  });
276
213
 
277
214
  // ── Drag Handlers ──────────────────────────────────────────────────────────
@@ -284,16 +221,7 @@
284
221
  dragging = true;
285
222
 
286
223
  if (accept && e.dataTransfer?.items?.length) {
287
- const types = Array.isArray(accept) ? accept : accept.split(',').map((t) => t.trim());
288
- const hasInvalid = Array.from(e.dataTransfer.items).some((item) => {
289
- if (item.kind !== 'file') return true;
290
- return !types.some((type) => {
291
- if (type.endsWith('/*')) return item.type.startsWith(type.replace('/*', '/'));
292
- if (type.startsWith('.')) return true;
293
- return item.type === type;
294
- });
295
- });
296
- dragInvalid = hasInvalid;
224
+ dragInvalid = !dragItemsMatchAccept(e.dataTransfer.items, accept);
297
225
  }
298
226
  }
299
227
 
@@ -369,21 +297,6 @@
369
297
  };
370
298
  });
371
299
 
372
- // ── Helpers ────────────────────────────────────────────────────────────────
373
-
374
- function formatFileSize(bytes: number): string {
375
- if (bytes === 0) return '0 B';
376
- if (!isFinite(bytes)) return '';
377
- const k = 1024;
378
- const sizes = ['B', 'KB', 'MB', 'GB'];
379
- const i = Math.floor(Math.log(bytes) / Math.log(k));
380
- return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
381
- }
382
-
383
- function isImageFile(file: File): boolean {
384
- return file.type.startsWith('image/');
385
- }
386
-
387
300
  // ── Mint ─────────────────────────────────────────────────────────────────
388
301
 
389
302
  $effect(() => {
@@ -520,7 +433,21 @@
520
433
  <!-- Status indicator -->
521
434
  <div class={slot('fileItemStatusIcon')}>
522
435
  {#if entry.status === 'uploading'}
523
- <Spinner size="xs" visible />
436
+ <!--
437
+ CoreSpinner instead of the public Spinner (see internal/core/).
438
+ The old call was `visible` (explicitly true) with no label
439
+ override, and this branch already gates rendering on the
440
+ uploading status — so the core needs no {#if} of its own.
441
+ Deliberate a11y delta: the old Spinner emitted role="status" +
442
+ aria-live + an sr-only "Loading..." inside the file list's own
443
+ aria-live="polite" region (nested live regions); the core emits
444
+ no semantics — the list region owns announcements. `text-primary`
445
+ pins the old default color (the public Spinner's intent default),
446
+ matching the explicit text-success/text-danger on the sibling
447
+ status icons; it wins the duel against the core's `text-current`
448
+ by stylesheet order (theme colors sort after keyword colors).
449
+ -->
450
+ <CoreSpinner size="xs" class="text-primary" />
524
451
  {:else if entry.status === 'complete'}
525
452
  <CheckCircleIcon size={itemIconSize} class="text-success" />
526
453
  {:else if entry.status === 'error'}
@@ -1,4 +1,4 @@
1
1
  import type { FileUploadProps } from './index.js';
2
- declare const FileUpload: import("svelte").Component<FileUploadProps, {}, "files" | "file">;
2
+ declare const FileUpload: import("svelte").Component<FileUploadProps, {}, "file" | "files">;
3
3
  type FileUpload = ReturnType<typeof FileUpload>;
4
4
  export default FileUpload;
@@ -2,34 +2,8 @@ import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import type { MintProp } from '../../mint/index.js';
4
4
  import type { FileUploadSlots, FileUploadVariants } from './fileUpload.variants.js';
5
- export type FileUploadErrorCode = 'FILE_INVALID_TYPE' | 'FILE_TOO_LARGE' | 'FILE_TOO_SMALL' | 'TOO_MANY_FILES' | 'FILE_EXISTS' | 'CUSTOM';
6
- export interface FileUploadError {
7
- /** Machine-readable error code. */
8
- code: FileUploadErrorCode;
9
- /** Human-readable error message. */
10
- message: string;
11
- }
12
- export type FileUploadStatus = 'pending' | 'uploading' | 'complete' | 'error';
13
- export interface FileUploadFile {
14
- /** Unique identifier for this file entry. */
15
- id: string;
16
- /** The native File object. */
17
- file: File;
18
- /** Object URL for image previews (auto-generated, auto-revoked). */
19
- preview?: string;
20
- /** Upload progress 0–100. Undefined when not tracking. */
21
- progress?: number;
22
- /** Current lifecycle status. */
23
- status: FileUploadStatus;
24
- /** Validation or upload errors. */
25
- errors: FileUploadError[];
26
- }
27
- export interface FileRejection {
28
- /** The rejected file. */
29
- file: File;
30
- /** Why it was rejected. */
31
- errors: FileUploadError[];
32
- }
5
+ import type { FileIntakeRejection as FileRejection, FileIntakeError as FileUploadError, FileIntakeErrorCode as FileUploadErrorCode, FileIntakeEntry as FileUploadFile, FileIntakeStatus as FileUploadStatus } from '../../utils/file-intake.js';
6
+ export type { FileRejection, FileUploadError, FileUploadErrorCode, FileUploadFile, FileUploadStatus };
33
7
  export declare const IMAGE_MIME_TYPES: string[];
34
8
  export declare const PDF_MIME_TYPE: string[];
35
9
  export declare const VIDEO_MIME_TYPES: string[];
@@ -6,7 +6,8 @@
6
6
  -->
7
7
  <script lang="ts">
8
8
  import { useBlocksI18n } from '../..';
9
- import { Button } from '../../primitives/Button';
9
+ // internal core, not the public component — keeps the public-to-public import graph clean (see internal/core/)
10
+ import CoreIconButton from '../../internal/core/CoreIconButton.svelte';
10
11
  import { Tooltip } from '../../primitives/Tooltip';
11
12
  import { resolveIcon } from '../../icons';
12
13
  import ChevronLeftIconDefault from '../../icons/ChevronLeftIcon.svelte';
@@ -37,44 +38,46 @@
37
38
  );
38
39
  </script>
39
40
 
41
+ <!--
42
+ Nav buttons render on the internal CoreIconButton (was `<Button unstyled
43
+ mint="none">`, which emitted only the call-site classes). The core's plumbing
44
+ overlaps the navButton slot's old baseline (inline-flex centring, focus-visible
45
+ reset, disabled opacity/cursor — now supplied by the core, stripped from the
46
+ slot); the deliberate deltas it introduces are documented on the slot in
47
+ planner.variants.ts.
48
+ -->
40
49
  <div class={ctx.slot('header')}>
41
50
  <div class={ctx.slot('nav')}>
42
- <Button
43
- unstyled
44
- mint="none"
51
+ <CoreIconButton
45
52
  class={ctx.slot('navButton')}
46
53
  onclick={() => ctx.navigate(-1)}
47
54
  disabled={!ctx.canGoBack || ctx.disabled}
48
55
  aria-label={prevLabel}
49
56
  >
50
57
  <ChevronLeftIcon size={16} />
51
- </Button>
58
+ </CoreIconButton>
52
59
  </div>
53
60
 
54
61
  <span class={ctx.slot('headerTitle')}>{ctx.title}</span>
55
62
 
56
63
  <div class={ctx.slot('nav')}>
57
64
  <Tooltip label={bt('planner.today')}>
58
- <Button
59
- unstyled
60
- mint="none"
65
+ <CoreIconButton
61
66
  class={ctx.slot('navButton')}
62
67
  onclick={() => ctx.goToToday()}
63
68
  disabled={!ctx.canGoToToday || ctx.disabled}
64
69
  aria-label={bt('planner.today')}
65
70
  >
66
71
  <CalendarDaysIcon size={16} />
67
- </Button>
72
+ </CoreIconButton>
68
73
  </Tooltip>
69
- <Button
70
- unstyled
71
- mint="none"
74
+ <CoreIconButton
72
75
  class={ctx.slot('navButton')}
73
76
  onclick={() => ctx.navigate(1)}
74
77
  disabled={!ctx.canGoForward || ctx.disabled}
75
78
  aria-label={nextLabel}
76
79
  >
77
80
  <ChevronRightIcon size={16} />
78
- </Button>
81
+ </CoreIconButton>
79
82
  </div>
80
83
  </div>
@@ -14,12 +14,23 @@ export const plannerVariants = tv({
14
14
  header: ['flex items-center justify-between gap-2', 'border-b border-border-hairline'],
15
15
  headerTitle: 'font-semibold text-text-primary select-none tabular-nums',
16
16
  nav: 'flex items-center gap-1',
17
+ // Rendered on the internal CoreIconButton (behaviour-only base: inline-flex
18
+ // centring, cursor/select affordance, focus-visible reset, disabled
19
+ // opacity/cursor/inertness), so this slot carries only the visual identity
20
+ // on top — the classes the core already supplies (inline-flex items-center
21
+ // justify-center, focus-visible:outline-none, disabled:opacity-50,
22
+ // disabled:cursor-not-allowed) are not repeated here. Deliberate deltas vs.
23
+ // the old `<Button unstyled mint="none">` render (which had NO plumbing):
24
+ // `cursor-pointer` (was the UA arrow — Tailwind 4 preflight doesn't set it;
25
+ // now consistent with every styled Button) and `disabled:pointer-events-none`
26
+ // (a disabled nav button is fully inert: no more hover-bg feedback or
27
+ // not-allowed cursor while disabled — matching the styled Button base).
28
+ // Mirrors calendar.variants navButton. See internal/core/.
17
29
  navButton: [
18
- 'inline-flex items-center justify-center rounded-md',
30
+ 'rounded-md',
19
31
  'text-text-secondary hover:bg-surface-hover hover:text-text-primary',
20
32
  'transition-colors duration-[var(--blocks-duration-fast)]',
21
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2',
22
- 'disabled:opacity-50 disabled:cursor-not-allowed'
33
+ 'focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2'
23
34
  ],
24
35
  // Grid scaffolding
25
36
  grid: 'w-full',
@@ -6,6 +6,7 @@ export * from './BarChart/index.js';
6
6
  export type { CalendarContext, CalendarEvent, CalendarEventCategory, CalendarHeaderProps, CalendarProps, CalendarSelection, CalendarSlotName, CalendarViewMode, DateRange, DayCellContext, EventItemContext, HeaderContext } from './Calendar/index.js';
7
7
  export * from './Calendar/index.js';
8
8
  export * from './ChartFrame/index.js';
9
+ export * from './Chat/index.js';
9
10
  export * from './CommandPalette/index.js';
10
11
  export type { CompositionBarIntent, CompositionBarProps, CompositionItem } from './CompositionBar/index.js';
11
12
  export * from './CompositionBar/index.js';
@@ -5,6 +5,7 @@ export * from './AreaChart/index.js';
5
5
  export * from './BarChart/index.js';
6
6
  export * from './Calendar/index.js';
7
7
  export * from './ChartFrame/index.js';
8
+ export * from './Chat/index.js';
8
9
  export * from './CommandPalette/index.js';
9
10
  export * from './CompositionBar/index.js';
10
11
  export * from './CurrencyInput/index.js';