@adea-ai/ui 0.99.2 → 0.99.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 (75) hide show
  1. package/dist/components/composites/workspace-mark/workspace-mark.d.ts.map +1 -1
  2. package/dist/components/composites/workspace-mark/workspace-mark.js +2 -2
  3. package/dist/components/composites/workspace-mark/workspace-mark.js.map +1 -1
  4. package/dist/components/conversation/message-row.d.ts.map +1 -1
  5. package/dist/components/conversation/message-row.js +2 -2
  6. package/dist/components/conversation/message-row.js.map +1 -1
  7. package/dist/components/conversation/thread-panel.d.ts.map +1 -1
  8. package/dist/components/conversation/thread-panel.js +1 -1
  9. package/dist/components/conversation/thread-panel.js.map +1 -1
  10. package/dist/components/ui/badge/badge.d.ts +1 -1
  11. package/dist/components/ui/badge/badge.d.ts.map +1 -1
  12. package/dist/components/ui/badge/badge.js +8 -1
  13. package/dist/components/ui/badge/badge.js.map +1 -1
  14. package/dist/components/ui/context-menu/context-menu.d.ts +8 -0
  15. package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
  16. package/dist/components/ui/context-menu/context-menu.js +8 -0
  17. package/dist/components/ui/context-menu/context-menu.js.map +1 -1
  18. package/dist/components/ui/drawer/drawer.d.ts +8 -1
  19. package/dist/components/ui/drawer/drawer.d.ts.map +1 -1
  20. package/dist/components/ui/drawer/drawer.js.map +1 -1
  21. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts +8 -0
  22. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  23. package/dist/components/ui/dropdown-menu/dropdown-menu.js +8 -0
  24. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  25. package/dist/components/ui/menubar/menubar.d.ts +8 -0
  26. package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
  27. package/dist/components/ui/menubar/menubar.js +8 -0
  28. package/dist/components/ui/menubar/menubar.js.map +1 -1
  29. package/dist/components/ui/navigation-menu/navigation-menu.d.ts +14 -2
  30. package/dist/components/ui/navigation-menu/navigation-menu.d.ts.map +1 -1
  31. package/dist/components/ui/navigation-menu/navigation-menu.js +28 -23
  32. package/dist/components/ui/navigation-menu/navigation-menu.js.map +1 -1
  33. package/dist/components/ui/tabs/tabs.d.ts +6 -0
  34. package/dist/components/ui/tabs/tabs.d.ts.map +1 -1
  35. package/dist/components/ui/tabs/tabs.js +7 -1
  36. package/dist/components/ui/tabs/tabs.js.map +1 -1
  37. package/dist/components/ui/toast/toast.d.ts.map +1 -1
  38. package/dist/components/ui/toast/toast.js +19 -6
  39. package/dist/components/ui/toast/toast.js.map +1 -1
  40. package/dist/components/ui/tooltip/tooltip.d.ts.map +1 -1
  41. package/dist/components/ui/tooltip/tooltip.js +1 -0
  42. package/dist/components/ui/tooltip/tooltip.js.map +1 -1
  43. package/dist/lib/version-notes.d.ts +6 -0
  44. package/dist/lib/version-notes.d.ts.map +1 -1
  45. package/dist/lib/version-notes.js +9 -1
  46. package/dist/lib/version-notes.js.map +1 -1
  47. package/dist/r/src/components/composites/workspace-mark/workspace-mark.tsx +13 -2
  48. package/dist/r/src/components/conversation/message-row.tsx +6 -1
  49. package/dist/r/src/components/conversation/thread-panel.tsx +4 -1
  50. package/dist/r/src/components/ui/badge/badge.tsx +7 -0
  51. package/dist/r/src/components/ui/context-menu/context-menu.tsx +8 -0
  52. package/dist/r/src/components/ui/drawer/drawer.tsx +8 -1
  53. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
  54. package/dist/r/src/components/ui/menubar/menubar.tsx +8 -0
  55. package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
  56. package/dist/r/src/components/ui/tabs/tabs.tsx +7 -1
  57. package/dist/r/src/components/ui/toast/toast.tsx +21 -6
  58. package/dist/r/src/components/ui/tooltip/tooltip.tsx +3 -0
  59. package/dist/r/src/lib/version-notes.ts +8 -0
  60. package/dist/r/src/styles/base.css +13 -0
  61. package/package.json +1 -1
  62. package/src/components/composites/workspace-mark/workspace-mark.tsx +13 -2
  63. package/src/components/conversation/message-row.tsx +6 -1
  64. package/src/components/conversation/thread-panel.tsx +4 -1
  65. package/src/components/ui/badge/badge.tsx +7 -0
  66. package/src/components/ui/context-menu/context-menu.tsx +8 -0
  67. package/src/components/ui/drawer/drawer.tsx +8 -1
  68. package/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
  69. package/src/components/ui/menubar/menubar.tsx +8 -0
  70. package/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
  71. package/src/components/ui/tabs/tabs.tsx +7 -1
  72. package/src/components/ui/toast/toast.tsx +21 -6
  73. package/src/components/ui/tooltip/tooltip.tsx +3 -0
  74. package/src/lib/version-notes.ts +8 -0
  75. package/src/styles/base.css +13 -0
@@ -264,6 +264,7 @@ function TooltipContent(props) {
264
264
  const context = useTooltipContext();
265
265
  return createComponent(Tooltip.Portal, { get children() {
266
266
  return createComponent(Tooltip.Content, mergeProps({
267
+ "data-slot": "tooltip-content",
267
268
  get ["class"]() {
268
269
  return cn(tooltipTip, "z-(--z-tooltip) w-fit max-w-(--tooltip-max-width) px-2 py-1", "origin-(--kb-tooltip-content-transform-origin) text-balance", "data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95", "data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", "data-expanded:duration-150 data-closed:duration-100", local.class);
269
270
  },
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.js","names":["Tooltip","KobalteTooltip","useTooltipContext","ComponentProps","createContext","createEffect","createSignal","createUniqueId","onCleanup","splitProps","useContext","isServer","tooltipTip","cn","TooltipTiming","openDelay","closeDelay","skipDelayDuration","tooltipDefaults","TooltipTimingContext","TooltipInteraction","id","trigger","HTMLElement","setTrigger","element","isOpen","isDisabled","isTriggerFocused","markOpenIntent","clearOpenIntent","requestCloseFromAnotherTooltip","markPointerCloseIntent","clearPointerCloseIntent","markCloseIntent","TooltipInteractionContext","openTooltipInteractions","Set","pendingTooltipOpenId","tooltipScrollListenerAttached","clearPendingTooltipOpen","undefined","tooltipScrollContainsTrigger","event","Event","target","window","Node","contains","handleTooltipScroll","interaction","handleTooltipPointerMove","PointerEvent","setTooltipInteractionOpen","open","has","add","addEventListener","document","delete","size","removeEventListener","registerTooltipInteraction","invokeEventHandler","handler","Array","isArray","callback","data","TooltipProviderProps","children","TooltipProvider","props","_$createComponent","Provider","value","TooltipRootProps","timing","local","rest","controlled","uncontrolledOpen","setUncontrolledOpen","defaultOpen","closeIntent","ignoreDuplicateClose","onOpenChange","next","source","pointerCloseIntentUntil","pointerCloseIntentTimeout","ReturnType","setTimeout","clearTimeout","Date","now","queueMicrotask","disabled","Boolean","activeElement","openInteraction","focusedTrigger","anotherTooltipOpening","pointerClosePending","handleKeyDown","KeyboardEvent","key","_$mergeProps","TooltipTrigger","Trigger","ref","onFocus","defaultPrevented","onBlur","onPointerEnter","onPointerLeave","onClick","TooltipContentProps","Content","TooltipContent","context","Portal","class","aria-hidden","_$memo","onEscapeKeyDown","onPointerDownOutside","_$createComponent","Tooltip","KobalteTooltip","_$mergeProps","_$memo"],"sources":["../../../../src/components/ui/tooltip/tooltip.tsx"],"sourcesContent":["import { Tooltip as KobalteTooltip, useTooltipContext } from '@kobalte/core/tooltip'\nimport type { ComponentProps } from 'solid-js'\nimport {\n createContext,\n createEffect,\n createSignal,\n createUniqueId,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { isServer } from 'solid-js/web'\nimport { tooltipTip } from '#lib/overlay'\nimport { cn } from '#lib/utils'\n\n/**\n * The provider sets the *timing* defaults for every tooltip below it.\n *\n * Timing is the part of a tooltip that has to agree across an app: one that\n * opens in 200ms in a toolbar and 700ms in a sidebar reads as a bug. The\n * provider is where that agreement lives. It is optional — a tooltip outside a\n * provider falls back to the same defaults — so a consumer can drop it in\n * without restructuring anything.\n */\ntype TooltipTiming = {\n openDelay: number\n closeDelay: number\n skipDelayDuration: number\n}\n\nconst tooltipDefaults: TooltipTiming = {\n /** Long enough that a pointer crossing a toolbar does not strobe tooltips. */\n openDelay: 400,\n /** Short, because the pointer has already left the control. */\n closeDelay: 80,\n /**\n * After a tooltip has been seen, the next one opens immediately for this\n * long. This is what makes moving along a row of icons feel like one\n * gesture instead of four separate waits.\n */\n skipDelayDuration: 500,\n}\n\nconst TooltipTimingContext = createContext<TooltipTiming>(tooltipDefaults)\n\ntype TooltipInteraction = {\n id: string\n trigger: () => HTMLElement | undefined\n setTrigger: (element: HTMLElement) => void\n isOpen: () => boolean\n isDisabled: () => boolean\n isTriggerFocused: () => boolean\n markOpenIntent: () => void\n clearOpenIntent: () => void\n requestCloseFromAnotherTooltip: () => void\n markPointerCloseIntent: () => void\n clearPointerCloseIntent: () => void\n markCloseIntent: () => void\n}\n\nconst TooltipInteractionContext = createContext<TooltipInteraction>()\nconst openTooltipInteractions = new Set<TooltipInteraction>()\nlet pendingTooltipOpenId: string | undefined\nlet tooltipScrollListenerAttached = false\n\nfunction clearPendingTooltipOpen(id: string) {\n if (pendingTooltipOpenId === id) pendingTooltipOpenId = undefined\n}\n\nfunction tooltipScrollContainsTrigger(event: Event, trigger: HTMLElement) {\n const target = event.target\n return target === window || (target instanceof Node && target.contains(trigger))\n}\n\nfunction handleTooltipScroll(event: Event) {\n for (const interaction of openTooltipInteractions) {\n const trigger = interaction.trigger()\n if (interaction.isOpen() && trigger && tooltipScrollContainsTrigger(event, trigger)) {\n interaction.markCloseIntent()\n }\n }\n}\n\nfunction handleTooltipPointerMove(event: PointerEvent) {\n for (const interaction of openTooltipInteractions) {\n const trigger = interaction.trigger()\n const target = event.target\n if (\n interaction.isOpen() &&\n interaction.isTriggerFocused() &&\n trigger &&\n !(target instanceof Node && trigger.contains(target))\n ) {\n interaction.markPointerCloseIntent()\n }\n }\n}\n\nfunction setTooltipInteractionOpen(interaction: TooltipInteraction, open: boolean) {\n if (isServer) return\n\n if (open) {\n if (openTooltipInteractions.has(interaction)) return\n openTooltipInteractions.add(interaction)\n if (!tooltipScrollListenerAttached) {\n tooltipScrollListenerAttached = true\n // Register before Kobalte's global listeners so intentional scroll\n // dismissal can be distinguished from its delayed pointer dismissal.\n window.addEventListener('scroll', handleTooltipScroll, true)\n document.addEventListener('pointermove', handleTooltipPointerMove, true)\n }\n } else {\n openTooltipInteractions.delete(interaction)\n if (openTooltipInteractions.size === 0 && tooltipScrollListenerAttached) {\n tooltipScrollListenerAttached = false\n window.removeEventListener('scroll', handleTooltipScroll, true)\n document.removeEventListener('pointermove', handleTooltipPointerMove, true)\n }\n }\n}\n\nfunction registerTooltipInteraction(interaction: TooltipInteraction) {\n onCleanup(() => {\n setTooltipInteractionOpen(interaction, false)\n clearPendingTooltipOpen(interaction.id)\n })\n}\n\nfunction invokeEventHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\nexport type TooltipProviderProps = {\n children?: ComponentProps<'div'>['children']\n openDelay?: number\n closeDelay?: number\n skipDelayDuration?: number\n}\n\nexport function TooltipProvider(props: TooltipProviderProps) {\n return (\n <TooltipTimingContext.Provider\n value={{\n openDelay: props.openDelay ?? tooltipDefaults.openDelay,\n closeDelay: props.closeDelay ?? tooltipDefaults.closeDelay,\n skipDelayDuration: props.skipDelayDuration ?? tooltipDefaults.skipDelayDuration,\n }}\n >\n {props.children}\n </TooltipTimingContext.Provider>\n )\n}\n\nexport type TooltipRootProps = ComponentProps<typeof KobalteTooltip>\n\nexport function Tooltip(props: TooltipRootProps) {\n const timing = useContext(TooltipTimingContext)\n const [local, rest] = splitProps(props, ['open', 'defaultOpen', 'onOpenChange', 'disabled'])\n const id = createUniqueId()\n const controlled = () => local.open !== undefined\n const [uncontrolledOpen, setUncontrolledOpen] = createSignal(local.defaultOpen ?? false)\n const isOpen = () => (controlled() ? (local.open ?? false) : uncontrolledOpen())\n const [trigger, setTrigger] = createSignal<HTMLElement>()\n let closeIntent = false\n let ignoreDuplicateClose = false\n let onOpenChange: (next: boolean, source?: 'handoff') => void\n let pointerCloseIntentUntil = 0\n let pointerCloseIntentTimeout: ReturnType<typeof setTimeout> | undefined\n const clearPointerCloseIntent = () => {\n pointerCloseIntentUntil = 0\n if (pointerCloseIntentTimeout) clearTimeout(pointerCloseIntentTimeout)\n pointerCloseIntentTimeout = undefined\n }\n const markPointerCloseIntent = () => {\n if (!isOpen()) return\n clearPointerCloseIntent()\n const closeDelay = props.closeDelay ?? timing.closeDelay\n pointerCloseIntentUntil = Date.now() + closeDelay + 50\n pointerCloseIntentTimeout = setTimeout(clearPointerCloseIntent, closeDelay + 50)\n }\n const markCloseIntent = () => {\n closeIntent = true\n clearPointerCloseIntent()\n queueMicrotask(() => {\n closeIntent = false\n })\n }\n const requestCloseFromAnotherTooltip = () => {\n if (!isOpen()) return\n\n // Another trigger is an explicit handoff, even when a focused tooltip has\n // a pending pointer-close intent that would otherwise suppress Kobalte's\n // close callback. Route the request through the same controlled/uncontrolled\n // state contract as Kobalte and ignore its duplicate callback for this turn.\n ignoreDuplicateClose = true\n queueMicrotask(() => {\n ignoreDuplicateClose = false\n })\n onOpenChange(false, 'handoff')\n }\n const interaction: TooltipInteraction = {\n id,\n trigger,\n setTrigger,\n isOpen,\n isDisabled: () => local.disabled ?? false,\n isTriggerFocused: () =>\n Boolean(trigger() && typeof document !== 'undefined' && trigger() === document.activeElement),\n markOpenIntent: () => {\n clearPointerCloseIntent()\n pendingTooltipOpenId = id\n // A focused tooltip ignores ordinary pointer movement, but entering or\n // focusing another tooltip is an explicit transfer.\n for (const openInteraction of openTooltipInteractions) {\n if (openInteraction.id !== id) openInteraction.requestCloseFromAnotherTooltip()\n }\n },\n clearOpenIntent: () => clearPendingTooltipOpen(id),\n requestCloseFromAnotherTooltip,\n markPointerCloseIntent,\n clearPointerCloseIntent,\n markCloseIntent,\n }\n registerTooltipInteraction(interaction)\n onCleanup(() => {\n clearPointerCloseIntent()\n ignoreDuplicateClose = false\n })\n if (isOpen()) setTooltipInteractionOpen(interaction, true)\n createEffect(() => setTooltipInteractionOpen(interaction, isOpen()))\n onOpenChange = (next: boolean, source) => {\n if (!next && source !== 'handoff' && ignoreDuplicateClose) return\n\n if (next) {\n clearPendingTooltipOpen(id)\n clearPointerCloseIntent()\n // Install this capture listener before Kobalte's open-state effect adds\n // its scroll listener later in the same update.\n setTooltipInteractionOpen(interaction, true)\n } else {\n const focusedTrigger = interaction.isTriggerFocused()\n const anotherTooltipOpening =\n pendingTooltipOpenId !== undefined && pendingTooltipOpenId !== id\n const pointerClosePending = Date.now() <= pointerCloseIntentUntil\n if (\n source !== 'handoff' &&\n focusedTrigger &&\n pointerClosePending &&\n !closeIntent &&\n !anotherTooltipOpening\n ) {\n clearPointerCloseIntent()\n return\n }\n closeIntent = false\n clearPointerCloseIntent()\n clearPendingTooltipOpen(id)\n }\n\n if (!controlled()) setUncontrolledOpen(next)\n local.onOpenChange?.(next)\n if (next) {\n // A controlled parent can reject the open request without changing its\n // signal, so the effect above would not rerun to release this listener.\n queueMicrotask(() => {\n if (!isOpen()) setTooltipInteractionOpen(interaction, false)\n })\n }\n }\n\n // Kobalte dismisses on Escape only when its content owns the top-most\n // layer, and a handoff keeps the previous tooltip's content mounted while\n // it exits — swallowing the key for the tooltip that just took over.\n // Listening on document (capture) closes this tooltip first; the layer's\n // own handler still runs for anything stacked above it.\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n markCloseIntent()\n onOpenChange(false)\n }\n createEffect(() => {\n if (isServer || !isOpen()) return\n document.addEventListener('keydown', handleKeyDown, true)\n onCleanup(() => document.removeEventListener('keydown', handleKeyDown, true))\n })\n\n return (\n <TooltipInteractionContext.Provider value={interaction}>\n <KobalteTooltip\n open={isOpen()}\n onOpenChange={onOpenChange}\n disabled={local.disabled}\n openDelay={props.openDelay ?? timing.openDelay}\n closeDelay={props.closeDelay ?? timing.closeDelay}\n skipDelayDuration={props.skipDelayDuration ?? timing.skipDelayDuration}\n {...rest}\n />\n </TooltipInteractionContext.Provider>\n )\n}\n\nexport function TooltipTrigger(props: ComponentProps<typeof KobalteTooltip.Trigger>) {\n const interaction = useContext(TooltipInteractionContext)\n const [local, rest] = splitProps(props, [\n 'ref',\n 'onFocus',\n 'onBlur',\n 'onPointerEnter',\n 'onPointerLeave',\n 'onClick',\n ])\n\n return (\n <KobalteTooltip.Trigger\n {...rest}\n ref={(element) => {\n interaction?.setTrigger(element)\n if (typeof local.ref === 'function') local.ref(element)\n }}\n onFocus={(event) => {\n invokeEventHandler(local.onFocus, event)\n if (!event.defaultPrevented && !interaction?.isDisabled()) interaction?.markOpenIntent()\n }}\n onBlur={(event) => {\n invokeEventHandler(local.onBlur, event)\n interaction?.clearOpenIntent()\n interaction?.markCloseIntent()\n }}\n onPointerEnter={(event) => {\n invokeEventHandler(local.onPointerEnter, event)\n if (!event.defaultPrevented && !interaction?.isDisabled()) interaction?.markOpenIntent()\n }}\n onPointerLeave={(event) => {\n invokeEventHandler(local.onPointerLeave, event)\n interaction?.clearOpenIntent()\n interaction?.markPointerCloseIntent()\n }}\n onClick={(event) => {\n invokeEventHandler(local.onClick, event)\n interaction?.markCloseIntent()\n }}\n />\n )\n}\n\nexport type TooltipContentProps = ComponentProps<typeof KobalteTooltip.Content>\n\nexport function TooltipContent(props: TooltipContentProps) {\n const interaction = useContext(TooltipInteractionContext)\n const [local, rest] = splitProps(props, [\n 'class',\n 'children',\n 'aria-hidden',\n 'onEscapeKeyDown',\n 'onPointerDownOutside',\n ])\n const context = useTooltipContext()\n\n // Kobalte keeps force-mounted and exiting content present after close. Hide\n // that retained tooltip from assistive technology until it opens again.\n return (\n <KobalteTooltip.Portal>\n <KobalteTooltip.Content\n class={cn(\n tooltipTip,\n // The max width is a token, not a fixed measure: it has to fall\n // behind the viewport at large root font sizes or the tip — which\n // Kobalte can shift but never shrink — forces a horizontal scroll.\n 'z-(--z-tooltip) w-fit max-w-(--tooltip-max-width) px-2 py-1',\n 'origin-(--kb-tooltip-content-transform-origin) text-balance',\n 'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95',\n 'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n 'data-expanded:duration-150 data-closed:duration-100',\n local.class\n )}\n aria-hidden={context.isOpen() ? local['aria-hidden'] : true}\n onEscapeKeyDown={(event) => {\n invokeEventHandler(local.onEscapeKeyDown, event)\n if (!event.defaultPrevented) interaction?.markCloseIntent()\n }}\n onPointerDownOutside={(event) => {\n invokeEventHandler(local.onPointerDownOutside, event)\n if (!event.defaultPrevented) interaction?.markCloseIntent()\n }}\n {...rest}\n >\n {local.children}\n </KobalteTooltip.Content>\n </KobalteTooltip.Portal>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tooltip.js","names":["Tooltip","KobalteTooltip","useTooltipContext","ComponentProps","createContext","createEffect","createSignal","createUniqueId","onCleanup","splitProps","useContext","isServer","tooltipTip","cn","TooltipTiming","openDelay","closeDelay","skipDelayDuration","tooltipDefaults","TooltipTimingContext","TooltipInteraction","id","trigger","HTMLElement","setTrigger","element","isOpen","isDisabled","isTriggerFocused","markOpenIntent","clearOpenIntent","requestCloseFromAnotherTooltip","markPointerCloseIntent","clearPointerCloseIntent","markCloseIntent","TooltipInteractionContext","openTooltipInteractions","Set","pendingTooltipOpenId","tooltipScrollListenerAttached","clearPendingTooltipOpen","undefined","tooltipScrollContainsTrigger","event","Event","target","window","Node","contains","handleTooltipScroll","interaction","handleTooltipPointerMove","PointerEvent","setTooltipInteractionOpen","open","has","add","addEventListener","document","delete","size","removeEventListener","registerTooltipInteraction","invokeEventHandler","handler","Array","isArray","callback","data","TooltipProviderProps","children","TooltipProvider","props","_$createComponent","Provider","value","TooltipRootProps","timing","local","rest","controlled","uncontrolledOpen","setUncontrolledOpen","defaultOpen","closeIntent","ignoreDuplicateClose","onOpenChange","next","source","pointerCloseIntentUntil","pointerCloseIntentTimeout","ReturnType","setTimeout","clearTimeout","Date","now","queueMicrotask","disabled","Boolean","activeElement","openInteraction","focusedTrigger","anotherTooltipOpening","pointerClosePending","handleKeyDown","KeyboardEvent","key","_$mergeProps","TooltipTrigger","Trigger","ref","onFocus","defaultPrevented","onBlur","onPointerEnter","onPointerLeave","onClick","TooltipContentProps","Content","TooltipContent","context","Portal","class","aria-hidden","_$memo","onEscapeKeyDown","onPointerDownOutside","_$createComponent","Tooltip","KobalteTooltip","_$mergeProps","_$memo"],"sources":["../../../../src/components/ui/tooltip/tooltip.tsx"],"sourcesContent":["import { Tooltip as KobalteTooltip, useTooltipContext } from '@kobalte/core/tooltip'\nimport type { ComponentProps } from 'solid-js'\nimport {\n createContext,\n createEffect,\n createSignal,\n createUniqueId,\n onCleanup,\n splitProps,\n useContext,\n} from 'solid-js'\nimport { isServer } from 'solid-js/web'\nimport { tooltipTip } from '#lib/overlay'\nimport { cn } from '#lib/utils'\n\n/**\n * The provider sets the *timing* defaults for every tooltip below it.\n *\n * Timing is the part of a tooltip that has to agree across an app: one that\n * opens in 200ms in a toolbar and 700ms in a sidebar reads as a bug. The\n * provider is where that agreement lives. It is optional — a tooltip outside a\n * provider falls back to the same defaults — so a consumer can drop it in\n * without restructuring anything.\n */\ntype TooltipTiming = {\n openDelay: number\n closeDelay: number\n skipDelayDuration: number\n}\n\nconst tooltipDefaults: TooltipTiming = {\n /** Long enough that a pointer crossing a toolbar does not strobe tooltips. */\n openDelay: 400,\n /** Short, because the pointer has already left the control. */\n closeDelay: 80,\n /**\n * After a tooltip has been seen, the next one opens immediately for this\n * long. This is what makes moving along a row of icons feel like one\n * gesture instead of four separate waits.\n */\n skipDelayDuration: 500,\n}\n\nconst TooltipTimingContext = createContext<TooltipTiming>(tooltipDefaults)\n\ntype TooltipInteraction = {\n id: string\n trigger: () => HTMLElement | undefined\n setTrigger: (element: HTMLElement) => void\n isOpen: () => boolean\n isDisabled: () => boolean\n isTriggerFocused: () => boolean\n markOpenIntent: () => void\n clearOpenIntent: () => void\n requestCloseFromAnotherTooltip: () => void\n markPointerCloseIntent: () => void\n clearPointerCloseIntent: () => void\n markCloseIntent: () => void\n}\n\nconst TooltipInteractionContext = createContext<TooltipInteraction>()\nconst openTooltipInteractions = new Set<TooltipInteraction>()\nlet pendingTooltipOpenId: string | undefined\nlet tooltipScrollListenerAttached = false\n\nfunction clearPendingTooltipOpen(id: string) {\n if (pendingTooltipOpenId === id) pendingTooltipOpenId = undefined\n}\n\nfunction tooltipScrollContainsTrigger(event: Event, trigger: HTMLElement) {\n const target = event.target\n return target === window || (target instanceof Node && target.contains(trigger))\n}\n\nfunction handleTooltipScroll(event: Event) {\n for (const interaction of openTooltipInteractions) {\n const trigger = interaction.trigger()\n if (interaction.isOpen() && trigger && tooltipScrollContainsTrigger(event, trigger)) {\n interaction.markCloseIntent()\n }\n }\n}\n\nfunction handleTooltipPointerMove(event: PointerEvent) {\n for (const interaction of openTooltipInteractions) {\n const trigger = interaction.trigger()\n const target = event.target\n if (\n interaction.isOpen() &&\n interaction.isTriggerFocused() &&\n trigger &&\n !(target instanceof Node && trigger.contains(target))\n ) {\n interaction.markPointerCloseIntent()\n }\n }\n}\n\nfunction setTooltipInteractionOpen(interaction: TooltipInteraction, open: boolean) {\n if (isServer) return\n\n if (open) {\n if (openTooltipInteractions.has(interaction)) return\n openTooltipInteractions.add(interaction)\n if (!tooltipScrollListenerAttached) {\n tooltipScrollListenerAttached = true\n // Register before Kobalte's global listeners so intentional scroll\n // dismissal can be distinguished from its delayed pointer dismissal.\n window.addEventListener('scroll', handleTooltipScroll, true)\n document.addEventListener('pointermove', handleTooltipPointerMove, true)\n }\n } else {\n openTooltipInteractions.delete(interaction)\n if (openTooltipInteractions.size === 0 && tooltipScrollListenerAttached) {\n tooltipScrollListenerAttached = false\n window.removeEventListener('scroll', handleTooltipScroll, true)\n document.removeEventListener('pointermove', handleTooltipPointerMove, true)\n }\n }\n}\n\nfunction registerTooltipInteraction(interaction: TooltipInteraction) {\n onCleanup(() => {\n setTooltipInteractionOpen(interaction, false)\n clearPendingTooltipOpen(interaction.id)\n })\n}\n\nfunction invokeEventHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\nexport type TooltipProviderProps = {\n children?: ComponentProps<'div'>['children']\n openDelay?: number\n closeDelay?: number\n skipDelayDuration?: number\n}\n\nexport function TooltipProvider(props: TooltipProviderProps) {\n return (\n <TooltipTimingContext.Provider\n value={{\n openDelay: props.openDelay ?? tooltipDefaults.openDelay,\n closeDelay: props.closeDelay ?? tooltipDefaults.closeDelay,\n skipDelayDuration: props.skipDelayDuration ?? tooltipDefaults.skipDelayDuration,\n }}\n >\n {props.children}\n </TooltipTimingContext.Provider>\n )\n}\n\nexport type TooltipRootProps = ComponentProps<typeof KobalteTooltip>\n\nexport function Tooltip(props: TooltipRootProps) {\n const timing = useContext(TooltipTimingContext)\n const [local, rest] = splitProps(props, ['open', 'defaultOpen', 'onOpenChange', 'disabled'])\n const id = createUniqueId()\n const controlled = () => local.open !== undefined\n const [uncontrolledOpen, setUncontrolledOpen] = createSignal(local.defaultOpen ?? false)\n const isOpen = () => (controlled() ? (local.open ?? false) : uncontrolledOpen())\n const [trigger, setTrigger] = createSignal<HTMLElement>()\n let closeIntent = false\n let ignoreDuplicateClose = false\n let onOpenChange: (next: boolean, source?: 'handoff') => void\n let pointerCloseIntentUntil = 0\n let pointerCloseIntentTimeout: ReturnType<typeof setTimeout> | undefined\n const clearPointerCloseIntent = () => {\n pointerCloseIntentUntil = 0\n if (pointerCloseIntentTimeout) clearTimeout(pointerCloseIntentTimeout)\n pointerCloseIntentTimeout = undefined\n }\n const markPointerCloseIntent = () => {\n if (!isOpen()) return\n clearPointerCloseIntent()\n const closeDelay = props.closeDelay ?? timing.closeDelay\n pointerCloseIntentUntil = Date.now() + closeDelay + 50\n pointerCloseIntentTimeout = setTimeout(clearPointerCloseIntent, closeDelay + 50)\n }\n const markCloseIntent = () => {\n closeIntent = true\n clearPointerCloseIntent()\n queueMicrotask(() => {\n closeIntent = false\n })\n }\n const requestCloseFromAnotherTooltip = () => {\n if (!isOpen()) return\n\n // Another trigger is an explicit handoff, even when a focused tooltip has\n // a pending pointer-close intent that would otherwise suppress Kobalte's\n // close callback. Route the request through the same controlled/uncontrolled\n // state contract as Kobalte and ignore its duplicate callback for this turn.\n ignoreDuplicateClose = true\n queueMicrotask(() => {\n ignoreDuplicateClose = false\n })\n onOpenChange(false, 'handoff')\n }\n const interaction: TooltipInteraction = {\n id,\n trigger,\n setTrigger,\n isOpen,\n isDisabled: () => local.disabled ?? false,\n isTriggerFocused: () =>\n Boolean(trigger() && typeof document !== 'undefined' && trigger() === document.activeElement),\n markOpenIntent: () => {\n clearPointerCloseIntent()\n pendingTooltipOpenId = id\n // A focused tooltip ignores ordinary pointer movement, but entering or\n // focusing another tooltip is an explicit transfer.\n for (const openInteraction of openTooltipInteractions) {\n if (openInteraction.id !== id) openInteraction.requestCloseFromAnotherTooltip()\n }\n },\n clearOpenIntent: () => clearPendingTooltipOpen(id),\n requestCloseFromAnotherTooltip,\n markPointerCloseIntent,\n clearPointerCloseIntent,\n markCloseIntent,\n }\n registerTooltipInteraction(interaction)\n onCleanup(() => {\n clearPointerCloseIntent()\n ignoreDuplicateClose = false\n })\n if (isOpen()) setTooltipInteractionOpen(interaction, true)\n createEffect(() => setTooltipInteractionOpen(interaction, isOpen()))\n onOpenChange = (next: boolean, source) => {\n if (!next && source !== 'handoff' && ignoreDuplicateClose) return\n\n if (next) {\n clearPendingTooltipOpen(id)\n clearPointerCloseIntent()\n // Install this capture listener before Kobalte's open-state effect adds\n // its scroll listener later in the same update.\n setTooltipInteractionOpen(interaction, true)\n } else {\n const focusedTrigger = interaction.isTriggerFocused()\n const anotherTooltipOpening =\n pendingTooltipOpenId !== undefined && pendingTooltipOpenId !== id\n const pointerClosePending = Date.now() <= pointerCloseIntentUntil\n if (\n source !== 'handoff' &&\n focusedTrigger &&\n pointerClosePending &&\n !closeIntent &&\n !anotherTooltipOpening\n ) {\n clearPointerCloseIntent()\n return\n }\n closeIntent = false\n clearPointerCloseIntent()\n clearPendingTooltipOpen(id)\n }\n\n if (!controlled()) setUncontrolledOpen(next)\n local.onOpenChange?.(next)\n if (next) {\n // A controlled parent can reject the open request without changing its\n // signal, so the effect above would not rerun to release this listener.\n queueMicrotask(() => {\n if (!isOpen()) setTooltipInteractionOpen(interaction, false)\n })\n }\n }\n\n // Kobalte dismisses on Escape only when its content owns the top-most\n // layer, and a handoff keeps the previous tooltip's content mounted while\n // it exits — swallowing the key for the tooltip that just took over.\n // Listening on document (capture) closes this tooltip first; the layer's\n // own handler still runs for anything stacked above it.\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n markCloseIntent()\n onOpenChange(false)\n }\n createEffect(() => {\n if (isServer || !isOpen()) return\n document.addEventListener('keydown', handleKeyDown, true)\n onCleanup(() => document.removeEventListener('keydown', handleKeyDown, true))\n })\n\n return (\n <TooltipInteractionContext.Provider value={interaction}>\n <KobalteTooltip\n open={isOpen()}\n onOpenChange={onOpenChange}\n disabled={local.disabled}\n openDelay={props.openDelay ?? timing.openDelay}\n closeDelay={props.closeDelay ?? timing.closeDelay}\n skipDelayDuration={props.skipDelayDuration ?? timing.skipDelayDuration}\n {...rest}\n />\n </TooltipInteractionContext.Provider>\n )\n}\n\nexport function TooltipTrigger(props: ComponentProps<typeof KobalteTooltip.Trigger>) {\n const interaction = useContext(TooltipInteractionContext)\n const [local, rest] = splitProps(props, [\n 'ref',\n 'onFocus',\n 'onBlur',\n 'onPointerEnter',\n 'onPointerLeave',\n 'onClick',\n ])\n\n return (\n <KobalteTooltip.Trigger\n {...rest}\n ref={(element) => {\n interaction?.setTrigger(element)\n if (typeof local.ref === 'function') local.ref(element)\n }}\n onFocus={(event) => {\n invokeEventHandler(local.onFocus, event)\n if (!event.defaultPrevented && !interaction?.isDisabled()) interaction?.markOpenIntent()\n }}\n onBlur={(event) => {\n invokeEventHandler(local.onBlur, event)\n interaction?.clearOpenIntent()\n interaction?.markCloseIntent()\n }}\n onPointerEnter={(event) => {\n invokeEventHandler(local.onPointerEnter, event)\n if (!event.defaultPrevented && !interaction?.isDisabled()) interaction?.markOpenIntent()\n }}\n onPointerLeave={(event) => {\n invokeEventHandler(local.onPointerLeave, event)\n interaction?.clearOpenIntent()\n interaction?.markPointerCloseIntent()\n }}\n onClick={(event) => {\n invokeEventHandler(local.onClick, event)\n interaction?.markCloseIntent()\n }}\n />\n )\n}\n\nexport type TooltipContentProps = ComponentProps<typeof KobalteTooltip.Content>\n\nexport function TooltipContent(props: TooltipContentProps) {\n const interaction = useContext(TooltipInteractionContext)\n const [local, rest] = splitProps(props, [\n 'class',\n 'children',\n 'aria-hidden',\n 'onEscapeKeyDown',\n 'onPointerDownOutside',\n ])\n const context = useTooltipContext()\n\n // Kobalte keeps force-mounted and exiting content present after close. Hide\n // that retained tooltip from assistive technology until it opens again.\n return (\n <KobalteTooltip.Portal>\n <KobalteTooltip.Content\n /* The slot is what base.css's pointer-transparency rule keys on; every\n tip carries it, so no consumer has to remember to pass it. */\n data-slot=\"tooltip-content\"\n class={cn(\n tooltipTip,\n // The max width is a token, not a fixed measure: it has to fall\n // behind the viewport at large root font sizes or the tip — which\n // Kobalte can shift but never shrink — forces a horizontal scroll.\n 'z-(--z-tooltip) w-fit max-w-(--tooltip-max-width) px-2 py-1',\n 'origin-(--kb-tooltip-content-transform-origin) text-balance',\n 'data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95',\n 'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n 'data-expanded:duration-150 data-closed:duration-100',\n local.class\n )}\n aria-hidden={context.isOpen() ? local['aria-hidden'] : true}\n onEscapeKeyDown={(event) => {\n invokeEventHandler(local.onEscapeKeyDown, event)\n if (!event.defaultPrevented) interaction?.markCloseIntent()\n }}\n onPointerDownOutside={(event) => {\n invokeEventHandler(local.onPointerDownOutside, event)\n if (!event.defaultPrevented) interaction?.markCloseIntent()\n }}\n {...rest}\n >\n {local.children}\n </KobalteTooltip.Content>\n </KobalteTooltip.Portal>\n )\n}\n"],"mappings":"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"}
@@ -15,6 +15,12 @@ export declare function plainTextFromMarkdown(markdown: string): string;
15
15
  * A release date, formatted for a reader. Returns the input when it cannot be
16
16
  * parsed — a date a build tool wrote in an unusual format is still better shown
17
17
  * verbatim than dropped.
18
+ *
19
+ * A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`
20
+ * reads it as UTC midnight, and formatting that instant in the reader's zone
21
+ * moves it to the previous day anywhere west of UTC — so a date-only value is
22
+ * formatted in UTC, where its midnight still falls on the day it names. A full
23
+ * timestamp is a real instant and keeps the reader's zone.
18
24
  */
19
25
  export declare function formatReleaseDate(value: string | null): string | null;
20
26
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"version-notes.d.ts","sourceRoot":"","sources":["../../src/lib/version-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAkB9D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CASrE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWjD"}
1
+ {"version":3,"file":"version-notes.d.ts","sourceRoot":"","sources":["../../src/lib/version-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAkB9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAWrE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWjD"}
@@ -18,15 +18,23 @@ function plainTextFromMarkdown(markdown) {
18
18
  * A release date, formatted for a reader. Returns the input when it cannot be
19
19
  * parsed — a date a build tool wrote in an unusual format is still better shown
20
20
  * verbatim than dropped.
21
+ *
22
+ * A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`
23
+ * reads it as UTC midnight, and formatting that instant in the reader's zone
24
+ * moves it to the previous day anywhere west of UTC — so a date-only value is
25
+ * formatted in UTC, where its midnight still falls on the day it names. A full
26
+ * timestamp is a real instant and keeps the reader's zone.
21
27
  */
22
28
  function formatReleaseDate(value) {
23
29
  if (!value) return null;
24
30
  const date = new Date(value);
25
31
  if (Number.isNaN(date.getTime())) return value;
32
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value.trim());
26
33
  return new Intl.DateTimeFormat(void 0, {
27
34
  day: "numeric",
28
35
  month: "short",
29
- year: "numeric"
36
+ year: "numeric",
37
+ ...dateOnly ? { timeZone: "UTC" } : {}
30
38
  }).format(date);
31
39
  }
32
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"version-notes.js","names":[],"sources":["../../src/lib/version-notes.ts"],"sourcesContent":["/**\n * Version notes, as text.\n *\n * A release changelog arrives as markdown and is shown to a reader who wants to\n * know what changed, not to a renderer. Rendering it would mean a markdown\n * dependency, a sanitiser, and a typographic result that competes with the\n * dialog's own type scale — so the markup is stripped to its words instead.\n *\n * The order matters: images and links are unwrapped to their alt text and label\n * *before* the emphasis rules run, because `[a](b)` contains brackets and\n * parentheses that the later patterns would otherwise trip on.\n */\nexport function plainTextFromMarkdown(markdown: string): string {\n return markdown\n .replace(/\\r\\n/g, '\\n')\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/\\[([^\\]]+)\\]\\([^)]*\\)/g, '$1')\n .replace(/^[ \\t]*#{1,6}[ \\t]*/gm, '')\n .replace(/^[ \\t]*>[ \\t]?/gm, '')\n .replace(/^[ \\t]*---+[ \\t]*$/gm, '')\n .replace(/^[ \\t]*[-*+][ \\t]+/gm, '• ')\n .replace(/^[ \\t]*\\d+\\.[ \\t]+/gm, '')\n .replace(/```[^\\n]*\\n?/g, '')\n .replace(/`([^`]+)`/g, '$1')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1')\n .replace(/__([^_]+)__/g, '$1')\n .replace(/\\*([^*]+)\\*/g, '$1')\n .replace(/_([^_]+)_/g, '$1')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/**\n * A release date, formatted for a reader. Returns the input when it cannot be\n * parsed — a date a build tool wrote in an unusual format is still better shown\n * verbatim than dropped.\n */\nexport function formatReleaseDate(value: string | null): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return value\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n }).format(date)\n}\n\n/**\n * Bytes as a reader-facing size. Used by the update dialog's progress readout,\n * where the number is reassurance rather than data — \"12.4 MB of 48.1 MB\" says\n * the download is moving, which a percentage alone does not.\n */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '—'\n if (bytes < 1024) return `${bytes} B`\n const units = ['kB', 'MB', 'GB']\n let value = bytes / 1024\n let unit = 0\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit += 1\n }\n return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,sBAAsB,UAA0B;CAC9D,OAAO,SACJ,QAAQ,SAAS,IAAI,CAAC,CACtB,QAAQ,2BAA2B,IAAI,CAAC,CACxC,QAAQ,0BAA0B,IAAI,CAAC,CACvC,QAAQ,yBAAyB,EAAE,CAAC,CACpC,QAAQ,oBAAoB,EAAE,CAAC,CAC/B,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,wBAAwB,IAAI,CAAC,CACrC,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,iBAAiB,EAAE,CAAC,CAC5B,QAAQ,cAAc,IAAI,CAAC,CAC3B,QAAQ,oBAAoB,IAAI,CAAC,CACjC,QAAQ,gBAAgB,IAAI,CAAC,CAC7B,QAAQ,gBAAgB,IAAI,CAAC,CAC7B,QAAQ,cAAc,IAAI,CAAC,CAC3B,QAAQ,WAAW,MAAM,CAAC,CAC1B,KAAK;AACV;;;;;;AAOA,SAAgB,kBAAkB,OAAqC;CACrE,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG,OAAO;CACzC,OAAO,IAAI,KAAK,eAAe,KAAA,GAAW;EACxC,KAAK;EACL,OAAO;EACP,MAAM;CACR,CAAC,CAAC,CAAC,OAAO,IAAI;AAChB;;;;;;AAOA,SAAgB,YAAY,OAAuB;CACjD,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CACjD,IAAI,QAAQ,MAAM,OAAO,GAAG,MAAM;CAClC,MAAM,QAAQ;EAAC;EAAM;EAAM;CAAI;CAC/B,IAAI,QAAQ,QAAQ;CACpB,IAAI,OAAO;CACX,OAAO,SAAS,QAAQ,OAAO,MAAM,SAAS,GAAG;EAC/C,SAAS;EACT,QAAQ;CACV;CACA,OAAO,GAAG,MAAM,QAAQ,SAAS,KAAK,IAAI,CAAC,EAAE,GAAG,MAAM;AACxD"}
1
+ {"version":3,"file":"version-notes.js","names":[],"sources":["../../src/lib/version-notes.ts"],"sourcesContent":["/**\n * Version notes, as text.\n *\n * A release changelog arrives as markdown and is shown to a reader who wants to\n * know what changed, not to a renderer. Rendering it would mean a markdown\n * dependency, a sanitiser, and a typographic result that competes with the\n * dialog's own type scale — so the markup is stripped to its words instead.\n *\n * The order matters: images and links are unwrapped to their alt text and label\n * *before* the emphasis rules run, because `[a](b)` contains brackets and\n * parentheses that the later patterns would otherwise trip on.\n */\nexport function plainTextFromMarkdown(markdown: string): string {\n return markdown\n .replace(/\\r\\n/g, '\\n')\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/\\[([^\\]]+)\\]\\([^)]*\\)/g, '$1')\n .replace(/^[ \\t]*#{1,6}[ \\t]*/gm, '')\n .replace(/^[ \\t]*>[ \\t]?/gm, '')\n .replace(/^[ \\t]*---+[ \\t]*$/gm, '')\n .replace(/^[ \\t]*[-*+][ \\t]+/gm, '• ')\n .replace(/^[ \\t]*\\d+\\.[ \\t]+/gm, '')\n .replace(/```[^\\n]*\\n?/g, '')\n .replace(/`([^`]+)`/g, '$1')\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1')\n .replace(/__([^_]+)__/g, '$1')\n .replace(/\\*([^*]+)\\*/g, '$1')\n .replace(/_([^_]+)_/g, '$1')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\n/**\n * A release date, formatted for a reader. Returns the input when it cannot be\n * parsed — a date a build tool wrote in an unusual format is still better shown\n * verbatim than dropped.\n *\n * A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`\n * reads it as UTC midnight, and formatting that instant in the reader's zone\n * moves it to the previous day anywhere west of UTC — so a date-only value is\n * formatted in UTC, where its midnight still falls on the day it names. A full\n * timestamp is a real instant and keeps the reader's zone.\n */\nexport function formatReleaseDate(value: string | null): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return value\n const dateOnly = /^\\d{4}-\\d{2}-\\d{2}$/.test(value.trim())\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n ...(dateOnly ? { timeZone: 'UTC' } : {}),\n }).format(date)\n}\n\n/**\n * Bytes as a reader-facing size. Used by the update dialog's progress readout,\n * where the number is reassurance rather than data — \"12.4 MB of 48.1 MB\" says\n * the download is moving, which a percentage alone does not.\n */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '—'\n if (bytes < 1024) return `${bytes} B`\n const units = ['kB', 'MB', 'GB']\n let value = bytes / 1024\n let unit = 0\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024\n unit += 1\n }\n return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,sBAAsB,UAA0B;CAC9D,OAAO,SACJ,QAAQ,SAAS,IAAI,CAAC,CACtB,QAAQ,2BAA2B,IAAI,CAAC,CACxC,QAAQ,0BAA0B,IAAI,CAAC,CACvC,QAAQ,yBAAyB,EAAE,CAAC,CACpC,QAAQ,oBAAoB,EAAE,CAAC,CAC/B,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,wBAAwB,IAAI,CAAC,CACrC,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,iBAAiB,EAAE,CAAC,CAC5B,QAAQ,cAAc,IAAI,CAAC,CAC3B,QAAQ,oBAAoB,IAAI,CAAC,CACjC,QAAQ,gBAAgB,IAAI,CAAC,CAC7B,QAAQ,gBAAgB,IAAI,CAAC,CAC7B,QAAQ,cAAc,IAAI,CAAC,CAC3B,QAAQ,WAAW,MAAM,CAAC,CAC1B,KAAK;AACV;;;;;;;;;;;;AAaA,SAAgB,kBAAkB,OAAqC;CACrE,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG,OAAO;CACzC,MAAM,WAAW,sBAAsB,KAAK,MAAM,KAAK,CAAC;CACxD,OAAO,IAAI,KAAK,eAAe,KAAA,GAAW;EACxC,KAAK;EACL,OAAO;EACP,MAAM;EACN,GAAI,WAAW,EAAE,UAAU,MAAM,IAAI,CAAC;CACxC,CAAC,CAAC,CAAC,OAAO,IAAI;AAChB;;;;;;AAOA,SAAgB,YAAY,OAAuB;CACjD,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CACjD,IAAI,QAAQ,MAAM,OAAO,GAAG,MAAM;CAClC,MAAM,QAAQ;EAAC;EAAM;EAAM;CAAI;CAC/B,IAAI,QAAQ,QAAQ;CACpB,IAAI,OAAO;CACX,OAAO,SAAS,QAAQ,OAAO,MAAM,SAAS,GAAG;EAC/C,SAAS;EACT,QAAQ;CACV;CACA,OAAO,GAAG,MAAM,QAAQ,SAAS,KAAK,IAAI,CAAC,EAAE,GAAG,MAAM;AACxD"}
@@ -49,12 +49,23 @@ export function WorkspaceMark(props: WorkspaceMarkProps) {
49
49
  >
50
50
  <Show
51
51
  when={local.children}
52
- fallback={<span aria-hidden="true">{local.name.charAt(0).toUpperCase()}</span>}
52
+ fallback={
53
+ <span data-slot="workspace-mark-initial" aria-hidden="true">
54
+ {local.name.charAt(0).toUpperCase()}
55
+ </span>
56
+ }
53
57
  >
54
58
  {local.children}
55
59
  </Show>
60
+ {/* The badge is centred on the corner rather than tucked inside it. Inset
61
+ by an edge offset, a count badge is wider than the space between the
62
+ corner and the monogram, so it sat on the letter; centred on the corner,
63
+ a dot, a one-digit and a two-digit count all clear the letter's box. */}
56
64
  <Show when={local.badge}>
57
- <span class="absolute -end-0.5 -top-0.5 flex items-center justify-center">
65
+ <span
66
+ data-slot="workspace-mark-badge"
67
+ class="absolute end-0 top-0 flex translate-x-1/2 -translate-y-1/2 items-center justify-center rtl:-translate-x-1/2"
68
+ >
58
69
  {local.badge}
59
70
  </span>
60
71
  </Show>
@@ -320,7 +320,10 @@ export function MessageBody(
320
320
  .filter(Boolean)
321
321
  .map((segment) => {
322
322
  if (!segment.startsWith('```')) {
323
- return { kind: 'prose' as const, value: segment }
323
+ // The newlines that separate prose from a fence belong to the fence
324
+ // syntax. Kept, `whitespace-pre-wrap` draws them as blank lines on top
325
+ // of the column gap, doubling the space beside every code block.
326
+ return { kind: 'prose' as const, value: segment.replace(/^\n+|\n+$/g, '') }
324
327
  }
325
328
  const body = segment.replace(/^```/, '').replace(/```$/, '')
326
329
  // The language is the first token on the opening line, and only when the
@@ -334,6 +337,8 @@ export function MessageBody(
334
337
  language,
335
338
  }
336
339
  })
340
+ // Newlines alone between two fences leave an empty paragraph once trimmed.
341
+ .filter((segment) => segment.kind === 'code' || segment.value !== '')
337
342
 
338
343
  const last = () => segments().at(-1)
339
344
 
@@ -86,8 +86,11 @@ export function ThreadPanel(props: ThreadPanelProps) {
86
86
  <div class="bg-muted/40 shrink-0 border-b border-border px-3 py-2">{local.root}</div>
87
87
  </Show>
88
88
 
89
+ {/* The replies carry their own inline padding: a row brings only `px-1`, so
90
+ a bubble at its widest otherwise meets the panel's edge. `px-2` plus the
91
+ row's `px-1` lands on the `px-3` line the header and root use. */}
89
92
  <div
90
- class="min-h-0 flex-1 overflow-y-auto overscroll-contain"
93
+ class="min-h-0 flex-1 overflow-y-auto overscroll-contain px-2 py-2"
91
94
  role="region"
92
95
  aria-label="Thread replies"
93
96
  tabindex="0"
@@ -25,6 +25,13 @@ export const badgeVariants = cva(
25
25
  destructive: 'bg-destructive-subtle text-foreground',
26
26
  info: 'bg-info-subtle text-foreground',
27
27
  subtle: 'bg-primary-subtle text-primary',
28
+ /**
29
+ * A count that asks for attention, pinned to a corner of another control
30
+ * (a workspace mark, a rail icon). At `sm` it is a 2xs label on whatever
31
+ * surface sits behind the corner, where the destructive tint all but
32
+ * disappears — so it takes the solid, contrast-measured action fill.
33
+ */
34
+ notification: 'bg-destructive-action text-destructive-action-foreground',
28
35
  },
29
36
  size: {
30
37
  sm: 'h-4 px-1.5 text-2xs',
@@ -131,6 +131,14 @@ export function ContextMenuRadioItem(props: ComponentProps<typeof KobalteContext
131
131
  )
132
132
  }
133
133
 
134
+ /**
135
+ * The caption of a `ContextMenuGroup`, and that group's accessible name. It must be a
136
+ * child of the group it names: Kobalte reads the group from context, so a label
137
+ * placed directly in the content throws `useMenuGroupContext must be used within
138
+ * a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
139
+ * label and its items in a group anyway — the grouping is what a screen reader
140
+ * announces.
141
+ */
134
142
  export function ContextMenuLabel(props: ComponentProps<typeof KobalteContextMenu.GroupLabel>) {
135
143
  const [local, rest] = splitProps(props, ['class'])
136
144
  return <KobalteContextMenu.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
@@ -41,7 +41,14 @@ export function DrawerOverlay(props: ComponentProps<typeof DrawerPrimitive.Overl
41
41
  )
42
42
  }
43
43
 
44
- export type DrawerContentProps = ComponentProps<typeof DrawerPrimitive.Content> & {
44
+ export type DrawerContentProps = Omit<ComponentProps<typeof DrawerPrimitive.Content>, 'side'> & {
45
+ /**
46
+ * Not accepted here. corvu reads the side from the root `Drawer`, which uses it
47
+ * for the drag axis and publishes it as `data-side`; a `side` on the content
48
+ * would type-check through the polymorphic props and be silently ignored,
49
+ * leaving the drawer on the bottom edge. Set `<Drawer side="right">` instead.
50
+ */
51
+ side?: never
45
52
  /**
46
53
  * Draw the grabber at the leading edge of the sheet.
47
54
  *
@@ -187,6 +187,14 @@ export function DropdownMenuRadioItem(props: ComponentProps<typeof KobalteDropdo
187
187
  )
188
188
  }
189
189
 
190
+ /**
191
+ * The caption of a `DropdownMenuGroup`, and that group's accessible name. It must be a
192
+ * child of the group it names: Kobalte reads the group from context, so a label
193
+ * placed directly in the content throws `useMenuGroupContext must be used within
194
+ * a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
195
+ * label and its items in a group anyway — the grouping is what a screen reader
196
+ * announces.
197
+ */
190
198
  export function DropdownMenuLabel(props: ComponentProps<typeof KobalteDropdownMenu.GroupLabel>) {
191
199
  const [local, rest] = splitProps(props, ['class'])
192
200
  return <KobalteDropdownMenu.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
@@ -160,6 +160,14 @@ export function MenubarRadioItem(props: ComponentProps<typeof KobalteMenubar.Rad
160
160
  )
161
161
  }
162
162
 
163
+ /**
164
+ * The caption of a `MenubarGroup`, and that group's accessible name. It must be a
165
+ * child of the group it names: Kobalte reads the group from context, so a label
166
+ * placed directly in the content throws `useMenuGroupContext must be used within
167
+ * a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
168
+ * label and its items in a group anyway — the grouping is what a screen reader
169
+ * announces.
170
+ */
163
171
  export function MenubarLabel(props: ComponentProps<typeof KobalteMenubar.GroupLabel>) {
164
172
  const [local, rest] = splitProps(props, ['class'])
165
173
  return <KobalteMenubar.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
@@ -41,9 +41,15 @@ import { menuContentPadding, menuItem, overlayMotion, overlaySurface } from '../
41
41
  * - `Content` must be a direct child of `Menu`, and the `Viewport` is a **sibling
42
42
  * of the menus** — not inside them. All the panels share one viewport, which is
43
43
  * what lets the bar animate between them instead of each panel popping in place.
44
+ * - The viewport is an empty surface. Each `Content` reaches it through Kobalte's
45
+ * `Portal`, which mounts the panel inside the viewport while keeping it in its
46
+ * own `Menu`'s context. A `Content` rendered anywhere else — the viewport's own
47
+ * children included — has no `Menu` above it, and Kobalte throws
48
+ * `useMenuRootContext must be used within a MenuRoot` the moment a panel opens.
44
49
  *
45
- * The wrapper enforces the last of those by rendering the `Viewport` itself, so a
46
- * caller cannot forget it and get a menu whose panels never appear.
50
+ * The wrapper enforces the last two by rendering the `Viewport` itself and by
51
+ * portalling every `NavigationMenuContent`, so a caller cannot forget either and
52
+ * get a menu whose panels never appear.
47
53
  */
48
54
 
49
55
  export type NavigationMenuProps = ComponentProps<typeof KobalteNavigationMenu.Root> & {
@@ -112,25 +118,41 @@ export function NavigationMenuContent(props: ComponentProps<typeof KobalteNaviga
112
118
  const [local, rest] = splitProps(props, ['class', 'children'])
113
119
 
114
120
  return (
115
- <KobalteNavigationMenu.Content
116
- class={cn(
117
- 'grid w-max gap-1 p-2',
118
- // A two-column panel is the shape most menu bars want, and it is opt-in
119
- // through this hook rather than a prop because the column count is a
120
- // layout decision the caller's content makes.
121
- 'data-[wide]:grid-cols-2',
122
- local.class
123
- )}
124
- {...rest}
125
- >
126
- {local.children}
127
- </KobalteNavigationMenu.Content>
121
+ <KobalteNavigationMenu.Portal>
122
+ <KobalteNavigationMenu.Content
123
+ class={cn(
124
+ // Absolute so the viewport sizes to the active panel's measured box and
125
+ // animates between panels, rather than stacking them.
126
+ 'absolute top-0 left-0 grid w-max gap-1 p-2 outline-none',
127
+ // The motion is the panel's, not the viewport's. Kobalte keeps an
128
+ // outgoing panel mounted until the viewport's own animation ends; a
129
+ // viewport that carries an entrance animation never fires that again
130
+ // after it opens, so the previous panel would stay painted under the
131
+ // next one.
132
+ overlayMotion,
133
+ // A two-column panel is the shape most menu bars want, and it is opt-in
134
+ // through this hook rather than a prop because the column count is a
135
+ // layout decision the caller's content makes.
136
+ 'data-[wide]:grid-cols-2',
137
+ local.class
138
+ )}
139
+ {...rest}
140
+ >
141
+ {local.children}
142
+ </KobalteNavigationMenu.Content>
143
+ </KobalteNavigationMenu.Portal>
128
144
  )
129
145
  }
130
146
 
131
147
  /**
132
148
  * The shared panel surface. One per menu bar, and the wrapper supplies it — but
133
149
  * exported so a caller with an unusual layout can place it themselves.
150
+ *
151
+ * It renders no children: the panels arrive through each `NavigationMenuContent`'s
152
+ * portal. Kobalte positions it under the bar and publishes the active panel's size
153
+ * as `--kb-navigation-menu-viewport-*`, which the surface animates between.
154
+ * `box-content` keeps the border outside that measured size so a panel is never
155
+ * clipped by its own frame.
134
156
  */
135
157
  export function NavigationMenuViewport(
136
158
  props: ComponentProps<typeof KobalteNavigationMenu.Viewport>
@@ -139,24 +161,16 @@ export function NavigationMenuViewport(
139
161
 
140
162
  return (
141
163
  <KobalteNavigationMenu.Viewport
164
+ data-slot="navigation-menu-viewport"
142
165
  class={cn(
143
- 'absolute top-full left-0 isolate z-(--z-menu) mt-1.5 flex justify-center overflow-hidden',
166
+ overlaySurface,
167
+ 'isolate z-(--z-menu) mt-1.5 box-content list-none overflow-hidden',
144
168
  'h-(--kb-navigation-menu-viewport-height) w-(--kb-navigation-menu-viewport-width)',
145
- 'origin-top transition-[width,height] duration-200 ease-out',
169
+ 'origin-(--kb-menu-content-transform-origin) transition-[width,height] duration-200 ease-out',
146
170
  local.class
147
171
  )}
148
172
  {...rest}
149
- >
150
- <div
151
- class={cn(
152
- overlaySurface,
153
- overlayMotion,
154
- 'w-full rounded-lg border border-border bg-popover p-1 shadow-lg'
155
- )}
156
- >
157
- <KobalteNavigationMenu.Content />
158
- </div>
159
- </KobalteNavigationMenu.Viewport>
173
+ />
160
174
  )
161
175
  }
162
176
 
@@ -75,6 +75,12 @@ export type TabsListProps = ComponentProps<typeof KobalteTabs.List> & {
75
75
  appearance?: 'underline' | 'segmented'
76
76
  }
77
77
 
78
+ /**
79
+ * The underline appearance's rule is the baseline a horizontal row of tabs sits
80
+ * on. A vertical list has no baseline — its triggers stack — so the rule is
81
+ * dropped there; otherwise it draws as a stray line under the last trigger,
82
+ * spanning only the list's width (the SettingsLayout rail showed exactly that).
83
+ */
78
84
  export function TabsList(props: TabsListProps) {
79
85
  const [local, rest] = splitProps(props, ['class', 'appearance'])
80
86
 
@@ -84,7 +90,7 @@ export function TabsList(props: TabsListProps) {
84
90
  'inline-flex w-fit shrink-0 items-center justify-center',
85
91
  local.appearance === 'segmented'
86
92
  ? 'gap-0.5 rounded-lg bg-surface-hover p-0.5'
87
- : 'gap-1 rounded-none border-b border-border',
93
+ : 'gap-1 rounded-none border-b border-border data-[orientation=vertical]:border-b-0',
88
94
  'data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch',
89
95
  local.class
90
96
  )}
@@ -179,11 +179,23 @@ export type ToasterProps = ComponentProps<typeof KobalteToast.Region> & {
179
179
  region?: string
180
180
  }
181
181
 
182
+ /**
183
+ * The corner is set on the region, not the list. The region is the `fixed`
184
+ * element: an inset on the list inside it positions nothing, and the region falls
185
+ * back to wherever it happens to sit in the document flow.
186
+ */
182
187
  const toasterPosition = {
183
- 'top-left': 'top-0 left-0 items-start',
184
- 'top-right': 'top-0 right-0 items-end',
185
- 'bottom-left': 'bottom-0 left-0 items-start',
186
- 'bottom-right': 'bottom-0 right-0 items-end',
188
+ 'top-left': 'top-0 left-0',
189
+ 'top-right': 'top-0 right-0',
190
+ 'bottom-left': 'bottom-0 left-0',
191
+ 'bottom-right': 'bottom-0 right-0',
192
+ }
193
+
194
+ const toasterAlignment = {
195
+ 'top-left': 'items-start',
196
+ 'top-right': 'items-end',
197
+ 'bottom-left': 'items-start',
198
+ 'bottom-right': 'items-end',
187
199
  }
188
200
 
189
201
  /** Mount once, near the root. Renders the stack and owns its timing defaults. */
@@ -203,13 +215,16 @@ export function Toaster(props: ToasterProps) {
203
215
  * in-flight notification, and clicking a toast must not dismiss the
204
216
  * dialog behind it.
205
217
  */
206
- class="pointer-events-none fixed z-(--z-toast) flex max-h-screen w-full flex-col gap-2 p-4 sm:max-w-sm"
218
+ class={cn(
219
+ 'pointer-events-none fixed z-(--z-toast) flex max-h-screen w-full flex-col gap-2 p-4 sm:max-w-sm',
220
+ toasterPosition[local.position ?? 'bottom-right']
221
+ )}
207
222
  {...rest}
208
223
  >
209
224
  <KobalteToast.List
210
225
  class={cn(
211
226
  'flex flex-col gap-2',
212
- toasterPosition[local.position ?? 'bottom-right'],
227
+ toasterAlignment[local.position ?? 'bottom-right'],
213
228
  local.class
214
229
  )}
215
230
  />
@@ -367,6 +367,9 @@ export function TooltipContent(props: TooltipContentProps) {
367
367
  return (
368
368
  <KobalteTooltip.Portal>
369
369
  <KobalteTooltip.Content
370
+ /* The slot is what base.css's pointer-transparency rule keys on; every
371
+ tip carries it, so no consumer has to remember to pass it. */
372
+ data-slot="tooltip-content"
370
373
  class={cn(
371
374
  tooltipTip,
372
375
  // The max width is a token, not a fixed measure: it has to fall
@@ -34,15 +34,23 @@ export function plainTextFromMarkdown(markdown: string): string {
34
34
  * A release date, formatted for a reader. Returns the input when it cannot be
35
35
  * parsed — a date a build tool wrote in an unusual format is still better shown
36
36
  * verbatim than dropped.
37
+ *
38
+ * A date-only value (`2026-09-24`) is a calendar day, not an instant. `new Date`
39
+ * reads it as UTC midnight, and formatting that instant in the reader's zone
40
+ * moves it to the previous day anywhere west of UTC — so a date-only value is
41
+ * formatted in UTC, where its midnight still falls on the day it names. A full
42
+ * timestamp is a real instant and keeps the reader's zone.
37
43
  */
38
44
  export function formatReleaseDate(value: string | null): string | null {
39
45
  if (!value) return null
40
46
  const date = new Date(value)
41
47
  if (Number.isNaN(date.getTime())) return value
48
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value.trim())
42
49
  return new Intl.DateTimeFormat(undefined, {
43
50
  day: 'numeric',
44
51
  month: 'short',
45
52
  year: 'numeric',
53
+ ...(dateOnly ? { timeZone: 'UTC' } : {}),
46
54
  }).format(date)
47
55
  }
48
56
 
@@ -592,6 +592,19 @@
592
592
  color var(--duration-normal) var(--ease-out);
593
593
  }
594
594
 
595
+ /* A tooltip is informational: neither its popper positioner nor its content
596
+ may intercept the pointer and block the control the tooltip describes —
597
+ or a control beside it. The layer stack writes `pointer-events: auto`
598
+ inline on the active layer, so the content needs `!important` to stay out
599
+ of the pointer path. */
600
+ [data-popper-positioner]:has(> [data-slot='tooltip-content']) {
601
+ pointer-events: none;
602
+ }
603
+
604
+ [data-slot='tooltip-content'] {
605
+ pointer-events: none !important;
606
+ }
607
+
595
608
  /* Keyboard focus only. `:focus-visible` rather than `:focus` so a pointer
596
609
  click never paints a ring. */
597
610
  :focus-visible {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.99.2",
3
+ "version": "0.99.4",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
@@ -49,12 +49,23 @@ export function WorkspaceMark(props: WorkspaceMarkProps) {
49
49
  >
50
50
  <Show
51
51
  when={local.children}
52
- fallback={<span aria-hidden="true">{local.name.charAt(0).toUpperCase()}</span>}
52
+ fallback={
53
+ <span data-slot="workspace-mark-initial" aria-hidden="true">
54
+ {local.name.charAt(0).toUpperCase()}
55
+ </span>
56
+ }
53
57
  >
54
58
  {local.children}
55
59
  </Show>
60
+ {/* The badge is centred on the corner rather than tucked inside it. Inset
61
+ by an edge offset, a count badge is wider than the space between the
62
+ corner and the monogram, so it sat on the letter; centred on the corner,
63
+ a dot, a one-digit and a two-digit count all clear the letter's box. */}
56
64
  <Show when={local.badge}>
57
- <span class="absolute -end-0.5 -top-0.5 flex items-center justify-center">
65
+ <span
66
+ data-slot="workspace-mark-badge"
67
+ class="absolute end-0 top-0 flex translate-x-1/2 -translate-y-1/2 items-center justify-center rtl:-translate-x-1/2"
68
+ >
58
69
  {local.badge}
59
70
  </span>
60
71
  </Show>
@@ -320,7 +320,10 @@ export function MessageBody(
320
320
  .filter(Boolean)
321
321
  .map((segment) => {
322
322
  if (!segment.startsWith('```')) {
323
- return { kind: 'prose' as const, value: segment }
323
+ // The newlines that separate prose from a fence belong to the fence
324
+ // syntax. Kept, `whitespace-pre-wrap` draws them as blank lines on top
325
+ // of the column gap, doubling the space beside every code block.
326
+ return { kind: 'prose' as const, value: segment.replace(/^\n+|\n+$/g, '') }
324
327
  }
325
328
  const body = segment.replace(/^```/, '').replace(/```$/, '')
326
329
  // The language is the first token on the opening line, and only when the
@@ -334,6 +337,8 @@ export function MessageBody(
334
337
  language,
335
338
  }
336
339
  })
340
+ // Newlines alone between two fences leave an empty paragraph once trimmed.
341
+ .filter((segment) => segment.kind === 'code' || segment.value !== '')
337
342
 
338
343
  const last = () => segments().at(-1)
339
344