Skip to content

Releases: YuhuanStudio/YunUI

v0.2.17

Choose a tag to compare

@yuhuanowo yuhuanowo released this 06 Aug 09:45

Added

  • ModelSelect gains renderHeader + filterResetKeyrenderHeader renders a caller-owned
    toolbar pinned to the TOP of the open panel, always visible even when the result list is empty
    (unlike renderFooter, which hides on an empty set) — the place for a domain control that must
    stay reachable, e.g. a browse-mode switch above the picker. filterResetKey imperatively clears
    the internal search + provider/capability filters when its value changes, WITHOUT remounting, so
    the panel keeps its open state/scroll/focus and never flashes a stale-filter "no results" after the
    option set is swapped out from under the filters (again: a browse-mode switch). Both are optional
    and fully backward-compatible.

Fixed

  • MermaidDiagram restores its documented .mermaid-container DOM contract. The content
    stylesheet already scoped responsive SVG rules to that class, but the rendered diagram omitted
    it, leaving host styling and stable diagram selection disconnected from the actual output.
  • CodeBlock no longer pulls Shiki's full language catalogue into host builds. The content
    renderer now uses Shiki core with explicit lazy grammar/theme loaders and the JavaScript regex
    engine. Common application and LLM-output languages retain VS Code-grade highlighting; unknown
    fence labels fall back to escaped plaintext. This keeps MarkdownRenderer on demand in practice,
    instead of making Next/Vite discover and compile hundreds of unused language modules.
  • Overlays are no longer see-through — dropdown menus, selects, comboboxes, popovers, the
    language & theme switchers, the mobile navbar sheet and the notification panel were painted with a
    60%-opaque bg-background/60 frosted panel, so content behind them showed through and read as if
    it were still clickable. They now use bg-popover/85 (still frosted via backdrop-blur, but
    opaque enough to occlude what's underneath) and the correct --popover surface token.
  • Button labels never wrap — the .btn base now sets white-space: nowrap, so a button
    label can no longer break onto multiple lines in a narrow container. A CJK label like 使用
    in a cramped table action column used to split vertically into 使/; it now stays on one
    line (the column widens / the table scrolls instead). Matches the standard button convention.

Added

  • Sortable TableHeadTableHead gains optional, backward-compatible sort props:
    onSort makes the header a sort control (renders its content as a <button> with an
    asc/desc/unsorted chevron and sets aria-sort on the <th>), sortDirection
    ("asc" | "desc" | false) is the column's current state, and align
    ("left" | "right" | "center") aligns both the content and the control (use "right"
    for numeric columns). Omit onSort and it behaves exactly as before. Fills the
    data-table column-sorting gap without a separate component. TableHead is also now
    whitespace-nowrap so short header labels never wrap — CJK labels in particular no
    longer break mid-word (能力 → 能/力) in narrow columns / WebKit.
  • AgentTimeline (@yuhuanowo/yunui/ai) — an agent turn rendered as an
    ordered, INLINE sequence of typed blocks (reasoning / tool call+result /
    assistant text / approval), in the neutral .card language. Unlike the
    AgentSteps panel, the answer is a text block among the tool/reasoning
    blocks in conversation order (no result-on-top / steps-below split); tool
    rows expand into $-prefixed monospace output, failures use semantic
    red-500/5 tints, and an approval block renders an amber allow/deny gate.
    Prop-driven and copy-free — the consumer maps records onto AgentTimelineBlock,
    localizes every label, and supplies a markdown renderer. Verified on Chrome +
    Safari/WebKit, desktop + mobile.
  • AgentRunStatus (@yuhuanowo/yunui/ai) — a layout-stable live surface for the
    single activity an agent is doing now (a shimmering label + pulsing dot trio,
    announced via role="status"/aria-live). Complements AgentTimeline: the status
    stays put for the whole run while timeline rows become durable history. phase
    (thinking/acting/observing/reflecting/responding/waiting) is a semantic hint.
  • TextShimmer (@yuhuanowo/yunui) — a restrained animated text sweep for transient
    work states ("Thinking…"); the label is exposed to assistive tech exactly once while the
    duplicated paint layers stay aria-hidden. active pauses the sweep without a layout shift.
  • ChatAttachment (@yuhuanowo/yunui/chat) — a compact attachment surface shared by
    the composer and sent messages: name/meta, an optional preview or icon, a determinate
    upload progress bar, status (idle/loading/error) affordances and an actions slot.
  • InlineCitation (@yuhuanowo/yunui/content) — a claim-adjacent evidence marker for
    grounded AI output. A compact inline pill previews the concrete source (title, location, a
    bounded excerpt) on hover/focus and delegates opening to the host via onOpen.
  • ReadingProgress (@yuhuanowo/yunui/patterns) — reading position + back-to-top as one
    floating control: a circular ring fills with scroll progress around an up-arrow button (no
    full-width hairline). bar/backToTop toggle each half; threshold and labels.backToTop
    are configurable. One rAF-coalesced scroll listener; the ring animates via stroke-dashoffset.
  • SettingsShell (@yuhuanowo/yunui/patterns) — the canonical settings layout: grouped
    desktop sidebar navigation, a compact mobile Select, and one scroll-safe content lane; works
    in a dialog or full page. Controlled via value/onValueChange, sections from groups
    (SettingsNavGroup/SettingsNavItem). The host owns the active panel; YunUI owns navigation.
  • NavStateIndicator (@yuhuanowo/yunui/patterns) — the shared active/running marker that
    sits beside a selected item in Sidebar/SettingsShell; a presentational <span> driven by
    active/running data-states. Extracted so bespoke navigation surfaces can match the system.
  • CustomSelect server-backed search + infinite scrollonSearch (debounced by
    searchDebounceMs, default 250) drives results from a backend, paired with loading;
    onLoadMore + hasMore append pages as the list nears its end. Both optional and independent
    of the existing client-side searchable filtering.
  • Combobox gains clearable (default true) — a clear (×) action for the current value;
    set clearable={false} when the field must always hold a value.
  • useAnchoredPosition flips above the trigger when there isn't room below, and remeasures
    open panels as layout changes — hand-rolled floating panels now match the collision behavior of
    the Radix-based overlays.
  • getModelDeveloperId (@yuhuanowo/yunui/ai) — model-family icon resolution is centralized,
    so a model id resolves to its developer/family icon through one shared helper.

Fixed

  • Untrusted Mermaid/KaTeX is sanitizedMermaidDiagram renders with a strict
    securityLevel and KaTeX with trust: false, so diagram/math content coming from model
    or user input can't inject scripts or navigate via crafted labels (XSS hardening).
  • Tooltip content escapes clipping ancestors — tooltips now portal out, so they're no
    longer cut off by an overflow: hidden/clip parent.

Removed

  • AgentSteps (@yuhuanowo/yunui/ai) — the earlier Codex-style execution
    panel (result-on-top / steps-below split) is removed, superseded by
    AgentTimeline, which renders the same agent-turn data as an inline ordered
    sequence of typed blocks. No consumer shipped against AgentSteps.

Changed

  • ModelSelect selection bar inset refined to 5px (from left-1.5/6px),
    set as an inline style since Tailwind's 1.25 step isn't core and could be
    dropped by a consumer's JIT scan.
  • AgentTimeline now speaks YunUI's semantic status vocabulary — the error /
    success / warning affordances moved off raw Tailwind red-*/emerald-*/amber-*
    onto the --error / --success / --warning tokens, so they track the active
    theme (light / dark / brand) instead of drifting from the system's palette.
  • ./package.json is now an export so tooling and consumers can read the
    package version at runtime (the docs site uses it for its version badge).
  • Dependencies refreshed to latest — React 19.2.8, the Radix primitives,
    Tailwind 4.3.3, shiki 4, framer-motion 12.43, lucide-react 1.28 and more.
    No API changes; typescript intentionally held at 6.x (7.x isn't yet compatible
    with the tsup/rollup-plugin-dts build toolchain).

v0.2.16

Choose a tag to compare

@yuhuanowo yuhuanowo released this 06 Aug 09:49

Fixed

  • ModelSelect selection bar sat flush against the row edge. The selected /
    hover accent bar was left-0; inset to left-1.5 so it no longer crowds the
    panel edge next to the model icon.
  • ChatMessageList auto-scroll hijacked the whole document. Its
    stick-to-bottom used endRef.scrollIntoView(), which scrolls every
    scrollable ancestor — including the window. On a full-height chat page it was
    invisible, but when the list is one section of a long page (e.g. the showcase)
    the entire window jumped down to the chat on mount. Now it pins only its own
    overflow container (scrollTop = scrollHeight) and never touches the document
    scroll; internal stick-to-bottom is unchanged.
  • MediaGallery grid hover actions were near-invisible, and images had no
    built-in zoom.
    The download/delete controls used page-level outline button
    variants (primary = near-black icon on a transparent fill, destructive =
    coral outline) rendered on top of the image + hover scrim, so they read as
    empty outlines with no visible glyph. They are now solid, theme-aware circular
    controls (card fill + ring + shadow, foreground/error icon) that stay legible
    on any image in light and dark. Completed images are also click-to-zoom by
    default via the same ImageLightbox that ContentImage uses (zoom /
    rotate / download / keyboard); a host onPreview still overrides it.
  • ModelManagerCard no longer looks top-heavy when it has actions but no
    row-select control.
    The top control bar (min-h-7 mb-3) rendered whenever
    selectSlot OR actions existed, so a card with only actions (no
    multi-select checkbox) floated the action button alone top-right above a dead
    band, with the icon/name pushed to a second row. The bar now renders only for
    selectSlot; without it the actions ride on the identity row (right-aligned,
    aligned with the name). Multi-select admin mode (with selectSlot) is
    unchanged.
  • Sheet body now has default padding (px-5 py-4, matching its header).
    Without it every consumer's sheet content sat flush against the panel edges
    (e.g. settings drawers with sliders touching the border).
  • ThemeToggle pill no longer shows a lopsided blank stretch in dark mode.
    The Sun stayed in-flow while scale-0 (still occupying layout) and the Moon
    was absolutely positioned against the button, so dark mode rendered the icon
    off-center beside an empty slot. Both icons now stack in one 14px slot; the
    pre-mount skeleton also shrank from w-16 to the trigger's real footprint.
  • TabsList gained max-w-full — as an inline-flex it sized to content,
    so a long tab strip dragged the page sideways on narrow screens instead of
    engaging its own overflow-x-auto.
  • Scrollbar thumb is token-based (--border-default, hover
    --border-strong) instead of black-alpha, which was invisible on dark and
    true-black backgrounds.
  • Dark-mode readability of the shadcn muted bridge. --color-muted-foreground
    and the .text-label utility mapped to --text-muted (#52525b in dark) —
    only 2.57:1, so every consumer label/caption/section-header that used
    text-muted-foreground (the shadcn convention for readable secondary text)
    washed out. Both now map to --text-tertiary (#71717a, ~4:1). Light mode is
    unchanged (its --text-muted already equals --text-tertiary). Fixes the
    app-wide "washed-out details" seen across consumer surfaces.
  • Gauge / Slider tracks were invisible in dark mode. Both used
    --color-muted (= --bg-elevated) for the unfilled track, which blends into
    dark cards — the gauge ring and slider trough effectively disappeared. Now use
    --color-border.
  • Slider thumb no longer clips at the track extremes. The Root gained
    px-2.5 (half the 20px thumb) so a thumb at min/max stays inside its box
    instead of overflowing/clipping past the panel edge — very visible in narrow
    containers (e.g. a settings drawer on mobile).
  • TabsTrigger icon + label spacing. Added gap-1.5 so an icon child no
    longer sits flush against the label text.

Changed

  • Content + chat stacks fully restyled in YunUI's flat design vocabulary.
    Follow-up to the earlier token pass: every src/content/* and src/chat/*
    component now uses the flat design tokens and class API that the rest of YunUI
    (patterns / ai) uses — --bg-elevated / --bg-card / --border-hairline /
    --border-default / --text-primary·secondary·tertiary / --accent and the
    .card / .badge / *-soft classes — instead of the shadcn @theme aliases
    (bg-muted / border-border / text-muted-foreground / bg-primary) it was
    lifted from Agent with. This guarantees the content/chat surfaces match YunUI
    even inside consumer apps that define their own shadcn tokens.
  • CodeBlock rewritten to YunUI's original code-block design — the .card
    container, window-chrome traffic-light dots, a language .badge,
    --bg-elevated header / --bg-base body and --accent-subtle hover buttons,
    matching patterns/CodeBlock (keeps Shiki highlighting). Was a divergent
    terminal-header design lifted from Agent.
  • MarkdownRenderer root is not-prose — it styles every element itself, so
    a host .prose (Tailwind Typography) was double-styling the output (a second
    table frame + dead margins, shoved callout titles, code that looked different
    inside vs outside prose). The custom component map is now the single source of
    truth, immune to host prose.
  • Markdown links carry a persistent underline (subtle --border-default,
    darkening on hover) so links read as links, not body text.
  • Content stack now speaks YunUI's design language. CalloutBlock maps its
    GitHub admonition tones onto the semantic token utilities (bg-info-soft /
    text-success / border-error-soft, "important" → accent tokens) instead of
    hard-coded palette colors, so callouts re-theme with the design system.
    CodeBlock and MarkdownRenderer drop raw bg-[#f6f8fa] / bg-green-100 /
    bg-yellow-200 for bg-muted / bg-success-soft / bg-warning-soft.

Fixed

  • Markdown callouts (> [!NOTE]) rendered as a raw blockquote with the
    marker text showing — react-markdown emits a leading "\n" text node so the
    paragraph was never the first child. The blockquote handler now finds the first
    real element and preserves inline nodes, so callouts become CalloutBlock
    (which composes Alert).
  • Shiki code was double-spaced — block .line spans separated by literal
    "\n" text nodes rendered an extra empty line under white-space: pre.
    content.css collapses the inter-line newlines while each .line keeps pre.
  • Emphasized code lines (highlightLines) were clipped and off-brand — the
    highlight used indigo and, on horizontal scroll, only spanned the visible width
    (cutting mid-token). Now tinted with the host --accent (indigo fallback) and
    code is sized to its widest line so the highlight covers the whole row.
  • Task-list checkboxes rendered browser-grey — a native checkbox tick is
    colored by CSS accent-color, not text-*; switched to accent-(--accent).
  • Code blocks drew a nested "box-in-box" — a leaked host/prose code style
    (border + radius + chip background) bled into Shiki's <code>. content.css
    now neutralizes it, so code sits flat on one clean surface.
  • Touch a11y — code-block copy/edit buttons and ChatMessage hover actions
    were opacity-0 group-hover (invisible/unreachable on touch); now visible on
    coarse pointers (opacity-100 md:opacity-0 md:group-hover:opacity-100).
  • iOS focus-zoomChatComposer's textarea was text-sm (<16px); now
    text-base md:text-sm so mobile Safari doesn't zoom on focus.
  • Aligned content surfaces to YunUI's radius language (rounded-lgrounded-xl
    on callouts, code blocks, tables, details, mermaid, images), the composer focus
    state to the ring convention, and the last raw colors (border-gray-300,
    bg-destructive/10) to semantic utilities.
  • Content stack infinite render loopMermaidDiagram (stuck on
    "Rendering diagram…") and MathRenderer (CPU thrash) looped forever because
    useContentT() and the default adapter's useT returned a fresh function on
    every render, which was in those components' effect deps. useT/useContentT
    now return stable references, and the async effects no longer depend on the
    translator. Only surfaced under real rendering — caught by screenshot QA.
  • ChatMessage header collided badges with the timestamp on narrow widths
    a flex items-center header meant wrapping badges (e.g. a model chip plus a
    GenerationStats token/throughput/latency row) overlapped the right-aligned
    timestamp on mobile. The header now aligns to the top, wraps the name/badges
    group, and keeps the timestamp on the first line — clean at every width.
  • ContentImage spun forever when a ChatMessageList shared the page — the
    lazy-load IntersectionObserver rooted itself at the first
    [data-scroll-container="true"] element (which ChatMessageList sets). If the
    image sat outside that list (the normal content + chat page), it never
    intersected, so the src was never assigned and the spinner never resolved.
    The observer now only uses that container as root when it actually contains the
    image, else falls back to the viewport. Caught by screenshot QA on the showcase.

Added

  • MermaidDiagram is hand-drawn (Excalidraw-style) for every diagram type.
    Uses Mermaid 11's native look: "handDrawn" for the types it supports
    (flowchart, state, class, ER…) and, for the ones it leaves straight (sequence,
    gantt, pie, journey, gitGraph, timeline, mindmap…), post-processes the SVG with
    a turbulence-displacement filter applied to strokes/shapes only (text stays
    crisp), so all diagrams read as a sketch.
  • MermaidDiagram click-to-zoom — new `enab...
Read more

v0.2.15

Choose a tag to compare

@yuhuanowo yuhuanowo released this 06 Aug 09:49

Added

  • PageLayout (patterns) — the standard full-height page shell: a navbar slot,
    a flex-1 <main> offset (pt-28) to clear the fixed navbar, and a footer slot.
    Navbar/footer are passed as slots so the shell stays decoupled from their props;
    hideFooter, transparentBg and mainClassName cover the common variations.
    Extracted from Yunxin's hand-rolled shell (used by 8+ marketing pages).
  • Original OKLCH palette system — 24 palettes. The design-token scheme layer is
    now generated from an in-house OKLCH model (scripts/gen-tokens.mjs) with
    perceptually-even ramps; added teal, lime, amber, plum and fuchsia (19 →
    24). The brand/accent/neutral roles can target any of them at runtime.
  • YUNUI_THEME_PRESETS — 12 curated multi-color themes. Named
    {brand, accent, neutral} combos (Aurora, Sunset, Forest, Ocean, Grape, Ember,
    Lagoon, Blossom, Royal, Citrus, Orchid, Mono), exported with
    YunUIThemePreset / YunUIThemePresetName. Apply with applyTheme(preset).
  • Theme-effect utilities (in the stylesheet, token-driven so they restyle with
    the active brand/accent): .bg-brand-gradient, .text-brand-gradient,
    .glow-brand, .glow-accent, .bg-brand-sheen.

Fixed

  • iOS Safari no longer zooms the page when focusing a field. Mobile Safari
    auto-zooms when a focused input/textarea/select has font-size < 16px (our fields
    use text-sm = 14px). Added a touch-only (hover:none + pointer:coarse) rule
    flooring form fields at 16px; desktop keeps the 14px look. Verified in WebKit and
    Chromium under iPhone emulation.
  • Escape closes the LanguageSwitcher / ThemeToggle dropdowns (cross-browser).
    They previously only closed on outside-click. Added Escape-to-close with focus
    return to the trigger — via a document-level listener, because WebKit/Safari
    on macOS doesn't focus a <button> on click, so a container onKeyDown would
    never receive the key (caught by testing in the WebKit engine). CustomSelect
    now focuses its trigger on mouse-open for the same reason, so its keyboard nav
    works in Safari too.
  • Combobox is keyboard-navigable. Added ArrowUp/Down/Home/End/Enter listbox
    navigation, role="listbox"/role="option"/aria-selected, and
    aria-activedescendant — previously mouse-only.
  • Accessible names + ARIA on the dropdowns. aria-label on the ModelSelect /
    CustomSelect / Combobox / SearchInput search fields (placeholder alone isn't
    a label); aria-expanded/aria-haspopup on the ThemeToggle trigger; the Navbar
    mobile-menu scrim is aria-hidden.
  • Navbar respects the iPhone safe area. The fixed top bar uses
    max(1.5rem, env(safe-area-inset-top)) so it clears the notch / Dynamic Island in
    standalone Safari (no change on desktop, where the inset is 0).
  • Horizontal scroll bars no longer scroll vertically on touch. overflow-x-auto
    with the default overflow-y: visible makes a browser compute overflow-y to
    auto too, so the ModelSelect provider-filter row (and the NavTabs / Tabs
    bars) could be dragged up/down a few px on a phone. Added explicit
    overflow-y-hidden overscroll-x-contain to those single-row scrollers.
  • Unbreakable text no longer widens its row/card. Added truncate to the
    LinkRow title, SessionItem detail, ModelCard developer label and the
    Navbar app name (with min-w-0 on the logo link), so long ids / device strings
    / brand names ellipsize instead of pushing the layout past the viewport.
  • Hand-rolled dropdown panels stay inside the viewport. ModelSelect,
    CustomSelect, Combobox, LanguageSwitcher and ThemeToggle positioned
    their floating panel with a plain absolute and no collision handling, so on
    narrow / mobile screens the panel (notably ModelSelect's fixed w-96) spilled
    off the right edge and could run past the bottom. New useAnchoredPosition hook
    measures each panel against the viewport and applies a horizontal marginLeft
    nudge (margin, so it never fights framer-motion / animate-in transforms) plus a
    maxHeight cap paired with an internal flex-1 min-h-0 scroll region. The Radix
    panels (Select, DropdownMenu, Popover) already flip/shift via Popper and are
    unchanged.

Changed

  • All dropdown / select panels share the LanguageSwitcher look. Unified every
    floating menu panel onto one chrome — rounded-2xl border border-border bg-background/60 backdrop-blur-2xl shadow-lg shadow-black/5 — across Select,
    CustomSelect, Combobox, DropdownMenu, Popover, ModelSelect and the
    Navbar mobile menu (they previously diverged: square rounded-xl, opaque
    bg-(--bg-elevated) / bg-popover, weaker/absent blur, shadow-md/shadow-2xl).
    The selectable rows in Select/CustomSelect/Combobox now use the shared
    .dropdown-item class — the same left accent bar on hover/selection as the
    language picker, replacing per-component check-mark / tinted-background styles.
    .dropdown-item was extended to also drive Radix rows (keyboard
    data-highlighted, selected data-state=checked), so hand-rolled and Radix
    dropdowns render identically.
  • Palette values are now original. The scheme primitives are regenerated from
    the OKLCH model rather than the previously vendored ramps, so semantic-token
    consumers may see subtle color shifts. The generator and all values are first-party.