Releases: YuhuanStudio/YunUI
Releases · YuhuanStudio/YunUI
Release list
v0.2.17
Added
ModelSelectgainsrenderHeader+filterResetKey—renderHeaderrenders a caller-owned
toolbar pinned to the TOP of the open panel, always visible even when the result list is empty
(unlikerenderFooter, 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.filterResetKeyimperatively 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
MermaidDiagramrestores its documented.mermaid-containerDOM 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.CodeBlockno 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 keepsMarkdownRendereron 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%-opaquebg-background/60frosted panel, so content behind them showed through and read as if
it were still clickable. They now usebg-popover/85(still frosted viabackdrop-blur, but
opaque enough to occlude what's underneath) and the correct--popoversurface token. - Button labels never wrap — the
.btnbase now setswhite-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
TableHead—TableHeadgains optional, backward-compatible sort props:
onSortmakes the header a sort control (renders its content as a<button>with an
asc/desc/unsorted chevron and setsaria-sorton the<th>),sortDirection
("asc" | "desc" | false) is the column's current state, andalign
("left" | "right" | "center") aligns both the content and the control (use"right"
for numeric columns). OmitonSortand it behaves exactly as before. Fills the
data-table column-sorting gap without a separate component.TableHeadis also now
whitespace-nowrapso 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.cardlanguage. Unlike the
AgentStepspanel, the answer is atextblock 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/5tints, and anapprovalblock renders an amber allow/deny gate.
Prop-driven and copy-free — the consumer maps records ontoAgentTimelineBlock,
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 viarole="status"/aria-live). ComplementsAgentTimeline: 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 stayaria-hidden.activepauses the sweep without a layout shift.ChatAttachment(@yuhuanowo/yunui/chat) — a compact attachment surface shared by
the composer and sent messages: name/meta, an optionalprevieworicon, a determinate
uploadprogressbar,status(idle/loading/error) affordances and anactionsslot.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 viaonOpen.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/backToToptoggle each half;thresholdandlabels.backToTop
are configurable. One rAF-coalesced scroll listener; the ring animates viastroke-dashoffset.SettingsShell(@yuhuanowo/yunui/patterns) — the canonical settings layout: grouped
desktop sidebar navigation, a compact mobileSelect, and one scroll-safe content lane; works
in a dialog or full page. Controlled viavalue/onValueChange, sections fromgroups
(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 inSidebar/SettingsShell; a presentational<span>driven by
active/runningdata-states. Extracted so bespoke navigation surfaces can match the system.CustomSelectserver-backed search + infinite scroll —onSearch(debounced by
searchDebounceMs, default 250) drives results from a backend, paired withloading;
onLoadMore+hasMoreappend pages as the list nears its end. Both optional and independent
of the existing client-sidesearchablefiltering.Comboboxgainsclearable(defaulttrue) — a clear (×) action for the current value;
setclearable={false}when the field must always hold a value.useAnchoredPositionflips 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 sanitized —
MermaidDiagramrenders with a strict
securityLeveland KaTeX withtrust: false, so diagram/math content coming from model
or user input can't inject scripts or navigate via crafted labels (XSS hardening). Tooltipcontent escapes clipping ancestors — tooltips now portal out, so they're no
longer cut off by anoverflow: hidden/clipparent.
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 againstAgentSteps.
Changed
ModelSelectselection bar inset refined to 5px (fromleft-1.5/6px),
set as an inline style since Tailwind's1.25step isn't core and could be
dropped by a consumer's JIT scan.AgentTimelinenow speaks YunUI's semantic status vocabulary — the error /
success / warning affordances moved off raw Tailwindred-*/emerald-*/amber-*
onto the--error/--success/--warningtokens, so they track the active
theme (light / dark / brand) instead of drifting from the system's palette../package.jsonis 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,shiki4,framer-motion12.43,lucide-react1.28 and more.
No API changes;typescriptintentionally held at 6.x (7.x isn't yet compatible
with thetsup/rollup-plugin-dtsbuild toolchain).
v0.2.16
Fixed
ModelSelectselection bar sat flush against the row edge. The selected /
hover accent bar wasleft-0; inset toleft-1.5so it no longer crowds the
panel edge next to the model icon.ChatMessageListauto-scroll hijacked the whole document. Its
stick-to-bottom usedendRef.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.MediaGallerygrid 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 sameImageLightboxthatContentImageuses (zoom /
rotate / download / keyboard); a hostonPreviewstill overrides it.ModelManagerCardno longer looks top-heavy when it has actions but no
row-select control. The top control bar (min-h-7 mb-3) rendered whenever
selectSlotORactionsexisted, so a card with onlyactions(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 (withselectSlot) is
unchanged.Sheetbody 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).ThemeTogglepill no longer shows a lopsided blank stretch in dark mode.
The Sun stayed in-flow whilescale-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 fromw-16to the trigger's real footprint.TabsListgainedmax-w-full— as aninline-flexit sized to content,
so a long tab strip dragged the page sideways on narrow screens instead of
engaging its ownoverflow-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
mutedbridge.--color-muted-foreground
and the.text-labelutility 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-mutedalready equals--text-tertiary). Fixes the
app-wide "washed-out details" seen across consumer surfaces. Gauge/Slidertracks 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.Sliderthumb 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).TabsTriggericon + label spacing. Addedgap-1.5so 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: everysrc/content/*andsrc/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/--accentand the
.card/.badge/*-softclasses — instead of the shadcn@themealiases
(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. CodeBlockrewritten to YunUI's original code-block design — the.card
container, window-chrome traffic-light dots, a language.badge,
--bg-elevatedheader /--bg-basebody and--accent-subtlehover buttons,
matchingpatterns/CodeBlock(keeps Shiki highlighting). Was a divergent
terminal-header design lifted from Agent.MarkdownRendererroot isnot-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.
CalloutBlockmaps 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.
CodeBlockandMarkdownRendererdrop rawbg-[#f6f8fa]/bg-green-100/
bg-yellow-200forbg-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 becomeCalloutBlock
(which composesAlert). - Shiki code was double-spaced — block
.linespans separated by literal
"\n"text nodes rendered an extra empty line underwhite-space: pre.
content.csscollapses the inter-line newlines while each.linekeepspre. - 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
codeis 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 CSSaccent-color, nottext-*; switched toaccent-(--accent). - Code blocks drew a nested "box-in-box" — a leaked host/prose
codestyle
(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
ChatMessagehover actions
wereopacity-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 wastext-sm(<16px); now
text-base md:text-smso 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…") andMathRenderer(CPU thrash) looped forever because
useContentT()and the default adapter'suseTreturned 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. ChatMessageheader collided badges with the timestamp on narrow widths —
aflex items-centerheader meant wrapping badges (e.g. a model chip plus a
GenerationStatstoken/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.ContentImagespun forever when aChatMessageListshared the page — the
lazy-loadIntersectionObserverrooted itself at the first
[data-scroll-container="true"]element (whichChatMessageListsets). If the
image sat outside that list (the normal content + chat page), it never
intersected, so thesrcwas 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
MermaidDiagramis hand-drawn (Excalidraw-style) for every diagram type.
Uses Mermaid 11's nativelook: "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.MermaidDiagramclick-to-zoom — new `enab...
v0.2.15
Added
PageLayout(patterns) — the standard full-height page shell: a navbar slot,
aflex-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,transparentBgandmainClassNamecover 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; addedteal,lime,amber,plumandfuchsia(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 withapplyTheme(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
usetext-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/ThemeToggledropdowns (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 containeronKeyDownwould
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. Comboboxis 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-labelon theModelSelect/
CustomSelect/Combobox/SearchInputsearch fields (placeholder alone isn't
a label);aria-expanded/aria-haspopupon theThemeToggletrigger; the Navbar
mobile-menu scrim isaria-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 defaultoverflow-y: visiblemakes a browser computeoverflow-yto
autotoo, so theModelSelectprovider-filter row (and theNavTabs/Tabs
bars) could be dragged up/down a few px on a phone. Added explicit
overflow-y-hidden overscroll-x-containto those single-row scrollers. - Unbreakable text no longer widens its row/card. Added
truncateto the
LinkRowtitle,SessionItemdetail,ModelCarddeveloper label and the
Navbarapp name (withmin-w-0on 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,LanguageSwitcherandThemeTogglepositioned
their floating panel with a plainabsoluteand no collision handling, so on
narrow / mobile screens the panel (notablyModelSelect's fixedw-96) spilled
off the right edge and could run past the bottom. NewuseAnchoredPositionhook
measures each panel against the viewport and applies a horizontalmarginLeft
nudge (margin, so it never fights framer-motion /animate-intransforms) plus a
maxHeightcap paired with an internalflex-1 min-h-0scroll 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— acrossSelect,
CustomSelect,Combobox,DropdownMenu,Popover,ModelSelectand the
Navbarmobile menu (they previously diverged: squarerounded-xl, opaque
bg-(--bg-elevated)/bg-popover, weaker/absent blur,shadow-md/shadow-2xl).
The selectable rows inSelect/CustomSelect/Comboboxnow use the shared
.dropdown-itemclass — the same left accent bar on hover/selection as the
language picker, replacing per-component check-mark / tinted-background styles.
.dropdown-itemwas extended to also drive Radix rows (keyboard
data-highlighted, selecteddata-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.