Skip to content

v0.2.16

Choose a tag to compare

@yuhuanowo yuhuanowo released this 06 Aug 09:49
· 153 commits to main since this release

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 enableZoom (default true) opens the
    diagram in a full-screen lightbox on a theme-matched panel (readable in light
    and dark). ImageLightbox now accepts arbitrary children (not just an
    <img src>), so any inline SVG can reuse its zoom/rotate/close controls.
  • Localizable labels for the last hardcoded stringsAreaChart
    noDataLabel, AudioPlayer labels (play/pause/seek/download aria), and
    ChatComposer sendLabel / stopLabel, so every user-facing string in the
    new components is host-overridable.
  • ChatComposer allowSendEmpty — allow sending with an empty textarea
    (e.g. when attachments alone form a valid message).
  • ScrollArea / ScrollBar (primitives) — a styled, cross-browser custom
    scrollbar around any overflowing content (Radix-based). Adds
    @radix-ui/react-scroll-area.
  • Card sub-componentsCardHeader, CardTitle, CardDescription,
    CardContent, CardFooter for composing structured cards (previously Card
    was a single container).

Docs / site

  • Showcase Content + Chat sections are now localized. They were the only
    sections with hardcoded English section/demo titles + descriptions and nav
    labels; every string now flows through the showcase message namespace
    (en / zh-CN / zh-TW), matching every other section.
  • Showcase gained the missing demos — a GenerationStats metric row in the
    chat assistant message (tokens / throughput / latency, beside the model badge)
    and a standalone ContentImage (a self-contained local SVG scene, so the
    lazy-loaded, click-to-zoom frame always renders without an external fetch).
  • SegmentedBar docs page no longer 500s — its "Total and remainder" demo
    passed a live formatValue function inline from the (server-rendered) MDX,
    which React Server Components reject. Moved to a client demo component
    (SegmentedBarLegendDemo), matching the AreaChart pattern.
  • Select partsSelectLabel, SelectSeparator, SelectScrollUpButton,
    SelectScrollDownButton (round out the Radix Select surface).
  • PopoverAnchor — position a Popover relative to a separate anchor element.
  • Badge structural variantssecondary, outline, and destructive
    (alias of error) alongside the existing semantic variants, for
    shadcn-style call sites.
  • @yuhuanowo/yunui/content (new subpath) — the content-rendering stack.
    Rich rendering for LLM/chat/doc content, extracted from the Agent (garvea)
    project (the most complete of our three apps) so all of them share one
    canonical implementation: MarkdownRenderer (GFM tables/task-lists, KaTeX
    math, Shiki code, Mermaid diagrams, GitHub-style callouts, heading anchors,
    lazy zoomable images), plus standalone CodeBlock/InlineCode (Shiki),
    CalloutBlock, MathRenderer/InlineMath/BlockMath, MermaidDiagram,
    ContentImage and ImageLightbox. Decoupled via props: urlTransform
    (rewrite image/link URLs) and onCodeEdit (open code in an editor). The heavy
    engines are optional peerDependencies (react-markdown, remark-gfm,
    remark-math, rehype-raw/-katex/-slug, katex, shiki, mermaid) and
    Shiki/Mermaid load on demand, so content.js itself stays ~36 KB. Ships
    @yuhuanowo/yunui/content.css; consumers also import katex/dist/katex.min.css.
  • @yuhuanowo/yunui/chat (new subpath) — the chat pattern. Presentational,
    slot-based building blocks for AI chat UIs (no data/model types, no i18n):
    ChatMessage (avatar + header/badges + body + footer + hover actions),
    ChatMessageList (smart stick-to-bottom scroller), ChatComposer
    (auto-growing textarea, Enter-to-send, send/stop, attachment + toolbar slots)
    and ChatHeader (backdrop-blur shell with slots). Pairs with
    @yuhuanowo/yunui/content. No new dependencies.
  • GenerationStats (chat) — a compact metric-pill row for an assistant
    message (tokens, throughput tok/s, latency, and any custom pills), sitting
    beside the model badge in a ChatMessage header. Presentational; the host
    formats and supplies the values.
  • AreaChart (primitive) — an interactive line/area chart for a time series:
    smooth bezier curve, gradient area fill, dashed grid, and a hover guide with a
    value tooltip. Container-width (ResizeObserver), pure SVG. The "full" chart
    counterpart to Sparkline. Extracted from Yunxin's analytics charts so both
    apps (and Yunshu) share one canonical chart.
  • Sparkline (primitive) — a tiny inline SVG line/area chart for a single
    number series (throughput, GPU utilization, latency), tone-colored and
    container-width. No chart library.
  • Gauge (primitive) — a circular 0–100 percentage ring with a centered
    value or custom label; the arc color follows a semantic tone.
  • SegmentedBar (primitive) — a proportional multi-segment bar toward a
    total (memory allocation, request mix) with an optional value legend.
    Complements MetricBar's single fill.
  • FileDropzone (primitive) — a drag-and-drop / click-to-browse upload
    target that reports files via onFiles; presentation + interaction only.
  • AudioPlayer (patterns) — a compact controls bar around an HTML5
    <audio>: play/pause, seek, time, and an optional download button.
  • MediaGallery (patterns) — one canonical result surface for generation
    pages: a grid/list of media results (image · video · audio) with per-item
    status (pending/processing/completed/failed), a progress bar, signed-URL
    expiry detection, and hover download/delete/preview. Extracted so Yunxin's
    image/video/audio galleries and Yunshu's generation pages share one component.
  • Full docs (3 locales), showcase demos and prop tables for all of the above.
    Motivated by the Yunshu webui rewrite onto YunUI and the tri-repo unification.