@adea-ai/ui 0.72.2 → 0.72.4

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 (25) hide show
  1. package/dist/components/composites/update-dialog/update-dialog.d.ts +7 -3
  2. package/dist/components/composites/update-dialog/update-dialog.d.ts.map +1 -1
  3. package/dist/components/composites/update-dialog/update-dialog.js +48 -13
  4. package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
  5. package/dist/components/ui/alert-dialog/alert-dialog.d.ts +2 -2
  6. package/dist/components/ui/alert-dialog/alert-dialog.d.ts.map +1 -1
  7. package/dist/components/ui/alert-dialog/alert-dialog.js +2 -2
  8. package/dist/components/ui/alert-dialog/alert-dialog.js.map +1 -1
  9. package/dist/components/ui/dialog/dialog.d.ts +5 -5
  10. package/dist/components/ui/dialog/dialog.d.ts.map +1 -1
  11. package/dist/components/ui/dialog/dialog.js +3 -3
  12. package/dist/components/ui/dialog/dialog.js.map +1 -1
  13. package/dist/components/ui/sheet/sheet.d.ts +2 -2
  14. package/dist/components/ui/sheet/sheet.d.ts.map +1 -1
  15. package/dist/components/ui/sheet/sheet.js +2 -2
  16. package/dist/components/ui/sheet/sheet.js.map +1 -1
  17. package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +70 -16
  18. package/dist/r/src/components/ui/alert-dialog/alert-dialog.tsx +7 -3
  19. package/dist/r/src/components/ui/dialog/dialog.tsx +13 -8
  20. package/dist/r/src/components/ui/sheet/sheet.tsx +5 -4
  21. package/package.json +1 -1
  22. package/src/components/composites/update-dialog/update-dialog.tsx +70 -16
  23. package/src/components/ui/alert-dialog/alert-dialog.tsx +7 -3
  24. package/src/components/ui/dialog/dialog.tsx +13 -8
  25. package/src/components/ui/sheet/sheet.tsx +5 -4
@@ -56,9 +56,13 @@ import { formatBytes, formatReleaseDate, plainTextFromMarkdown } from '#lib/vers
56
56
  * the other order leaves the dialog blank for as long as the check takes, on a
57
57
  * dialog whose first question is always "what am I running?".
58
58
  *
59
- * **Errors are not always `Error`s.** An adapter can reject with a string, and a
60
- * shell can answer with an error field. Both are read, and a fallback is shown
61
- * rather than an empty alert.
59
+ * **A failed check is not a current result.** A cached `current` status can only
60
+ * be shown as up to date after the latest check succeeds; a failed check remains
61
+ * retryable and suppresses that claim.
62
+ *
63
+ * **Errors are not always `Error`s.** An adapter can reject with a string or a
64
+ * structured safe message. Unknown object fields are never serialized into the
65
+ * UI; a generic fallback is shown instead.
62
66
  *
63
67
  * The adapter's `isDesktopRuntime` gate exists because an update is a desktop
64
68
  * concern: a browser build has no installer, and the honest thing to show is that
@@ -151,10 +155,22 @@ export type UpdateDialogProps = {
151
155
  class?: string
152
156
  }
153
157
 
158
+ function messageText(value: unknown): string | undefined {
159
+ if (typeof value !== 'string') return undefined
160
+ const message = value.trim()
161
+ return message && message !== '[object Object]' ? message : undefined
162
+ }
163
+
154
164
  function errorMessage(caught: unknown, fallback: string): string {
155
- if (caught instanceof Error && caught.message) return caught.message
156
- if (typeof caught === 'string' && caught) return caught
157
- return fallback
165
+ if (caught instanceof Error) return messageText(caught.message) ?? fallback
166
+ const plainMessage = messageText(caught)
167
+ if (plainMessage) return plainMessage
168
+ if (!caught || typeof caught !== 'object') return fallback
169
+
170
+ const record = caught as { error?: unknown; safe?: unknown }
171
+ const safe = record.safe as { message?: unknown } | null | undefined
172
+ const nestedError = record.error as { safe?: { message?: unknown } | null } | null | undefined
173
+ return messageText(safe?.message) ?? messageText(nestedError?.safe?.message) ?? fallback
158
174
  }
159
175
 
160
176
  /**
@@ -199,6 +215,7 @@ export function UpdateDialog(props: UpdateDialogProps) {
199
215
  const [state, setState] = createSignal<UpdateState | null>(null)
200
216
  const [busy, setBusy] = createSignal(false)
201
217
  const [error, setError] = createSignal('')
218
+ const [checkFailed, setCheckFailed] = createSignal(false)
202
219
  let lifecycleEpoch = 0
203
220
  let activeOpenEpoch: number | undefined
204
221
  let disposed = false
@@ -213,7 +230,19 @@ export function UpdateDialog(props: UpdateDialogProps) {
213
230
  if (!desktop() || !isCurrent()) return
214
231
  try {
215
232
  const status = await local.adapter.getStatus()
216
- if (isCurrent()) setState(status)
233
+ const previous = state()
234
+ const hasActionableSnapshot = (candidate: UpdateState | null) =>
235
+ candidate?.phase === 'available' || isBusy(candidate)
236
+ const preservesSnapshot =
237
+ previous?.phase === 'available'
238
+ ? status.phase === 'available' || isBusy(status)
239
+ : isBusy(previous) && isBusy(status)
240
+ if (
241
+ isCurrent() &&
242
+ !(checkFailed() && hasActionableSnapshot(previous) && !preservesSnapshot)
243
+ ) {
244
+ setState(status)
245
+ }
217
246
  } catch (caught) {
218
247
  if (isCurrent()) setError(errorMessage(caught, 'Version status is unavailable'))
219
248
  }
@@ -226,6 +255,24 @@ export function UpdateDialog(props: UpdateDialogProps) {
226
255
  }
227
256
  })
228
257
 
258
+ const failCheck = (caught: unknown, fallback: string) => {
259
+ const message = errorMessage(caught, fallback)
260
+ setCheckFailed(true)
261
+ setError(message)
262
+ }
263
+
264
+ const applyCheckResult = (next: UpdateState) => {
265
+ if (next.phase === 'failed') {
266
+ const current = state()
267
+ if (!current) setState(next)
268
+ failCheck(next.error, 'Could not check for updates')
269
+ return
270
+ }
271
+ setState(next)
272
+ setCheckFailed(false)
273
+ setError('')
274
+ }
275
+
229
276
  const check = async () => {
230
277
  if (!desktop()) {
231
278
  setError(local.desktopOnlyMessage ?? `Update checks are available from the desktop app.`)
@@ -241,13 +288,15 @@ export function UpdateDialog(props: UpdateDialogProps) {
241
288
  if (!isCurrent()) return
242
289
  setBusy(true)
243
290
  setError('')
291
+ setCheckFailed(false)
244
292
  try {
245
293
  const checked = await local.adapter.check()
246
- if (isCurrent()) setState(checked)
294
+ if (isCurrent()) applyCheckResult(checked)
247
295
  } catch (caught) {
248
- if (!isCurrent()) return
249
- setError(errorMessage(caught, 'Could not check for updates'))
250
- await loadStatus(isCurrent)
296
+ if (isCurrent()) {
297
+ failCheck(caught, 'Could not check for updates')
298
+ await loadStatus(isCurrent)
299
+ }
251
300
  } finally {
252
301
  if (isCurrent()) setBusy(false)
253
302
  }
@@ -279,6 +328,7 @@ export function UpdateDialog(props: UpdateDialogProps) {
279
328
  }
280
329
  })
281
330
  setError('')
331
+ setCheckFailed(false)
282
332
  setBusy(true)
283
333
  void (async () => {
284
334
  try {
@@ -286,9 +336,9 @@ export function UpdateDialog(props: UpdateDialogProps) {
286
336
  if (!isCurrent()) return
287
337
  setState(current)
288
338
  const checked = await local.adapter.check()
289
- if (isCurrent()) setState(checked)
339
+ if (isCurrent()) applyCheckResult(checked)
290
340
  } catch (caught) {
291
- if (isCurrent()) setError(errorMessage(caught, 'Could not check for updates'))
341
+ if (isCurrent()) failCheck(caught, 'Could not check for updates')
292
342
  } finally {
293
343
  if (isCurrent()) setBusy(false)
294
344
  }
@@ -315,6 +365,8 @@ export function UpdateDialog(props: UpdateDialogProps) {
315
365
  // Without this the click looked like nothing happened.
316
366
  if (next.phase === 'failed') {
317
367
  setError(errorMessage(next.error, 'Update installation failed'))
368
+ } else {
369
+ setCheckFailed(false)
318
370
  }
319
371
  } catch (caught) {
320
372
  if (!isCurrent()) return
@@ -331,10 +383,12 @@ export function UpdateDialog(props: UpdateDialogProps) {
331
383
  return value ? plainTextFromMarkdown(value) : ''
332
384
  }
333
385
  const working = () => busy() || isBusy(state())
386
+ const isCurrent = () => state()?.phase === 'current' && !error() && !checkFailed() && !working()
334
387
 
335
388
  const triggerLabel = () => {
336
389
  const current = state()
337
390
  const fallback = `v${local.fallbackVersion ?? '0.1.0'}`
391
+ if (checkFailed()) return `Version ${current?.currentVersion || fallback} · Retry`
338
392
  if (!current) return `${appName()} ${fallback}`
339
393
  switch (current.phase) {
340
394
  case 'checking':
@@ -410,7 +464,7 @@ export function UpdateDialog(props: UpdateDialogProps) {
410
464
  </p>
411
465
  <p class="text-sm text-muted-foreground">
412
466
  <Show
413
- when={state()?.phase === 'current'}
467
+ when={isCurrent()}
414
468
  fallback={
415
469
  <Show
416
470
  when={state()?.phase === 'available' && state()?.availableVersion}
@@ -439,7 +493,7 @@ export function UpdateDialog(props: UpdateDialogProps) {
439
493
  <Show when={working()} fallback={<RefreshCw aria-hidden="true" />}>
440
494
  <LoaderCircle class="animate-spin" aria-hidden="true" />
441
495
  </Show>
442
- Check latest version
496
+ {checkFailed() ? 'Retry update check' : 'Check latest version'}
443
497
  </Button>
444
498
  </div>
445
499
  </section>
@@ -503,7 +557,7 @@ export function UpdateDialog(props: UpdateDialogProps) {
503
557
  </section>
504
558
  </Show>
505
559
 
506
- <Show when={state()?.phase === 'current'}>
560
+ <Show when={isCurrent()}>
507
561
  <p class="text-foreground flex items-center gap-2 text-sm" role="status">
508
562
  <Check class="size-4 text-success" aria-hidden="true" />
509
563
  {appName()} is up to date.
@@ -1,4 +1,8 @@
1
- import { AlertDialog as KobalteAlertDialog } from '@kobalte/core/alert-dialog'
1
+ import {
2
+ AlertDialog as KobalteAlertDialog,
3
+ Content as KobalteAlertDialogContent,
4
+ } from '@kobalte/core/alert-dialog'
5
+ // Use the alert-specific export rather than DialogRoot.Content, which other imports can replace.
2
6
  import type { ComponentProps } from 'solid-js'
3
7
  import { createEffect, createSignal, splitProps } from 'solid-js'
4
8
  import { overlayMotion, overlayPositioner, overlayScrim, overlaySurface } from '#lib/overlay'
@@ -70,14 +74,14 @@ const blockDismissal = {
70
74
  onInteractOutside: (event: Event) => event.preventDefault(),
71
75
  } as const
72
76
 
73
- export function AlertDialogContent(props: ComponentProps<typeof KobalteAlertDialog.Content>) {
77
+ export function AlertDialogContent(props: ComponentProps<typeof KobalteAlertDialogContent>) {
74
78
  const [local, rest] = splitProps(props, ['class'])
75
79
 
76
80
  return (
77
81
  <KobalteAlertDialog.Portal>
78
82
  <AlertDialogOverlay />
79
83
  <div class={overlayPositioner}>
80
- <KobalteAlertDialog.Content
84
+ <KobalteAlertDialogContent
81
85
  {...blockDismissal}
82
86
  class={cn(overlaySurface, overlayMotion, 'grid w-full max-w-md gap-4 p-5', local.class)}
83
87
  {...rest}
@@ -1,4 +1,9 @@
1
- import { Dialog as KobalteDialog } from '@kobalte/core/dialog'
1
+ import {
2
+ Dialog as KobalteDialog,
3
+ Content as KobalteDialogContent,
4
+ Overlay as KobalteDialogOverlay,
5
+ } from '@kobalte/core/dialog'
6
+ // AlertDialog assigns to the shared DialogRoot.Content object, so these named exports stay role-stable.
2
7
  import { X } from 'lucide-solid'
3
8
  import type { Accessor, ComponentProps, JSX } from 'solid-js'
4
9
  import { createRenderEffect, onCleanup, Show, splitProps } from 'solid-js'
@@ -43,8 +48,8 @@ function hasOtherOverlay(content: HTMLElement, opener: HTMLElement): boolean {
43
48
  type DialogFocusRestorationOptions = {
44
49
  open: Accessor<boolean>
45
50
  restoreFocusRef?: Accessor<HTMLElement | undefined>
46
- onOpenAutoFocus?: ComponentProps<typeof KobalteDialog.Content>['onOpenAutoFocus']
47
- onCloseAutoFocus?: ComponentProps<typeof KobalteDialog.Content>['onCloseAutoFocus']
51
+ onOpenAutoFocus?: ComponentProps<typeof KobalteDialogContent>['onOpenAutoFocus']
52
+ onCloseAutoFocus?: ComponentProps<typeof KobalteDialogContent>['onCloseAutoFocus']
48
53
  }
49
54
 
50
55
  /**
@@ -231,10 +236,10 @@ export function DialogPortal(props: ComponentProps<typeof KobalteDialog.Portal>)
231
236
  return <KobalteDialog.Portal {...props} />
232
237
  }
233
238
 
234
- export function DialogOverlay(props: ComponentProps<typeof KobalteDialog.Overlay>) {
239
+ export function DialogOverlay(props: ComponentProps<typeof KobalteDialogOverlay>) {
235
240
  const [local, rest] = splitProps(props, ['class'])
236
241
  return (
237
- <KobalteDialog.Overlay
242
+ <KobalteDialogOverlay
238
243
  class={cn(
239
244
  overlayScrim,
240
245
  'data-expanded:animate-in data-expanded:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
@@ -245,7 +250,7 @@ export function DialogOverlay(props: ComponentProps<typeof KobalteDialog.Overlay
245
250
  )
246
251
  }
247
252
 
248
- export type DialogContentProps = ComponentProps<typeof KobalteDialog.Content> & {
253
+ export type DialogContentProps = ComponentProps<typeof KobalteDialogContent> & {
249
254
  /**
250
255
  * Replaces the default close button. Pass `false` to remove it — only for a
251
256
  * dialog that cannot be dismissed (an update that must finish); anything the
@@ -261,7 +266,7 @@ export function DialogContent(props: DialogContentProps) {
261
266
  <KobalteDialog.Portal>
262
267
  <DialogOverlay />
263
268
  <div class={overlayPositioner}>
264
- <KobalteDialog.Content
269
+ <KobalteDialogContent
265
270
  class={cn(
266
271
  overlaySurface,
267
272
  overlayMotion,
@@ -283,7 +288,7 @@ export function DialogContent(props: DialogContentProps) {
283
288
  {local.closeButton ?? <X class="size-4" />}
284
289
  </KobalteDialog.CloseButton>
285
290
  </Show>
286
- </KobalteDialog.Content>
291
+ </KobalteDialogContent>
287
292
  </div>
288
293
  </KobalteDialog.Portal>
289
294
  )
@@ -1,4 +1,5 @@
1
- import { Dialog as KobalteDialog } from '@kobalte/core/dialog'
1
+ import { Dialog as KobalteDialog, Content as KobalteDialogContent } from '@kobalte/core/dialog'
2
+ // AlertDialog shares DialogRoot.Content; Sheet uses the stable named primitive.
2
3
  import { X } from 'lucide-solid'
3
4
  import type { ComponentProps, JSX } from 'solid-js'
4
5
  import { Show, splitProps } from 'solid-js'
@@ -35,7 +36,7 @@ const sheetVariants = cva(
35
36
  }
36
37
  )
37
38
 
38
- export type SheetContentProps = ComponentProps<typeof KobalteDialog.Content> &
39
+ export type SheetContentProps = ComponentProps<typeof KobalteDialogContent> &
39
40
  VariantProps<typeof sheetVariants> & {
40
41
  closeButton?: JSX.Element | false
41
42
  }
@@ -46,7 +47,7 @@ export function SheetContent(props: SheetContentProps) {
46
47
  return (
47
48
  <KobalteDialog.Portal>
48
49
  <DialogOverlay />
49
- <KobalteDialog.Content
50
+ <KobalteDialogContent
50
51
  class={cn(
51
52
  sheetVariants({ side: local.side }),
52
53
  'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:duration-200',
@@ -64,7 +65,7 @@ export function SheetContent(props: SheetContentProps) {
64
65
  {local.closeButton ?? <X class="size-4" />}
65
66
  </KobalteDialog.CloseButton>
66
67
  </Show>
67
- </KobalteDialog.Content>
68
+ </KobalteDialogContent>
68
69
  </KobalteDialog.Portal>
69
70
  )
70
71
  }