You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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-zoom — ChatComposer'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-lg→rounded-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 loop — MermaidDiagram (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 strings — AreaChart noDataLabel, AudioPlayerlabels (play/pause/seek/download aria), and ChatComposersendLabel / stopLabel, so every user-facing string in the
new components is host-overridable.
ChatComposerallowSendEmpty — 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-components — CardHeader, 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 parts — SelectLabel, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton (round out the Radix Select surface).
PopoverAnchor — position a Popover relative to a separate anchor element.
Badge structural variants — secondary, 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.