description: "Shared React UI atoms for the dsh web client: controls, icons, markdown and math rendering, and the terminal/read/diff/search/web output cards (zero Cordis)."
English | 中文
Use dsh-client-ui-primitives to build web-client controls and render agent output with shared React UI. It includes standard controls, icons, anchored overlays, and renderers for Markdown with TeX, terminal output, file reads, diffs, search, web retrieval, and JSON. The renderers handle untrusted model output by dropping raw HTML, restricting links, and parsing ANSI escape sequences. The components import no Cordis runtime; callers supply localized labels, and theme-facing colors use --dsw-* design tokens.
This package is a Web-shell build input. Its static ESM retains third-party imports and styles for Vite; independent consumers supply its development dependencies (dependency rules).
Compose feature UI from these atoms whenever the web client needs a standard control or an agent-output renderer. They render through React only and take --dsw-* design tokens from the theme, so they fit any plugin without importing the theme or the slot system.
Check this table before writing a control in a feature package. A plugin cannot import another plugin's component, so this package is the only place a control can be shared: reuse what fits, and lift a deliberate visual difference into a prop rather than starting a second copy.
| Export | What it is |
|---|---|
Button |
Clickable action; variant selects primary, ghost, outline, or toolbar. |
Switch |
Two-state toggle, 36×20. label is required, so the control cannot ship unnamed. |
Input |
Single-line text entry for search boxes and inline forms. |
Menu |
Dropdown of items, separators, and group labels, with nested submenus. |
Pill |
Selectable capsule button for view switchers and filters; takes active and onClick. |
Tag |
Read-only capsule badge; tone selects one of eight palettes. |
StateDot |
Status mark: done, warning, ongoing, error, or idle. aria-hidden, so the render site owns the name. |
ConnectionIndicator |
Inline connection-recovery control across outage, retry, and recovered states. |
DisclosureRow |
24px compact disclosure that lays title and content side by side. |
Modal |
Centered dialog over a page mask. |
RiskConfirmation |
Sensitive action gated behind an explicit checkbox. |
OnboardingSurface |
First-run stage that holds the application root inert. |
Tooltip |
Hover text on a cloned anchor, placed right, bottom, or top. |
HoverCard |
Hover preview the pointer can rest on and select from; optional copy button. |
Toast |
Transient top-center banner held for the owner's holdMs. |
JsonTree, JsonBlock |
Read-only JSON inspection. |
MarkdownText, CodeBlock |
Untrusted GFM with TeX math, and highlighted code. CodeBlock accepts opt-in lineNumbers; copied source excludes the gutter, and contentRef exposes its stable source wrapper to an owner that uses it as a scrollport. |
TerminalBlock, ReadBlock, DiffBlock, SearchBlock, WebBlock |
The agent-output card matching each tool-result intent. |
icons/*, FishLogo, BrandWordmark, ReferenceIcon, LinkIcon |
Glyphs and brand marks. Use LinkIcon for 14px clickable-link categories. |
FileTypeIcon, classifyFileType, fileExtension |
A category-colored 28px file or folder glyph and the shared case-insensitive filename mapping behind it. Code and configuration files use detailed full-color technology glyphs; use LinkIcon for link-leading glyphs and image previews for image content. |
Three pairs are easy to confuse:
Tag against Pill. Reach for Tag for a read-only badge at the 11px capsule size, and for Pill when the capsule is selectable (active and onClick, as view switchers and filters use) or when it must sit on a 24px text line — TerminalBlock renders its exit status as a static Pill for exactly that reason. Size decides as much as interactivity here; the two are not interchangeable.DisclosureRow against a card. The row lays its title and content side by side at a fixed 24px. A card that stacks a name over a description is a different layout, and belongs in the feature package — ui-settings-plugins' PluginCard is the precedent and records why.FoldToggle against the exported surface. It is package-internal and not exported; the output cards use it for their head-tail fold.Writing your own component in your own package is fine when the need is genuinely specific. What is not fine is copying a control that already exists here — and once a second package needs the same control, it belongs in this package (decision).
The catalog above lists what each export is for; this section covers the behavior that props alone do not show. The ic_ds_* icon set and FishLogo/BrandWordmark marks fill brand and inline-icon slots. FileTypeIcon renders the traditional 28px Excel, folder, HTML, image, Markdown, generic, PDF, PPT, video, and Word glyphs, and uses the imported square technology artwork for the established 48 code and configuration categories. The import replaces artwork only: archive-only categories do not extend CodeFileType. classifyFileType applies exact filename, prefix, suffix, optional project-context, and extension rules in that order; React names win over TypeScript/JavaScript, Angular suffixes win over their base extension, and a Dart file becomes Flutter only when the supplied project files contain a pubspec.yaml with flutter:. Markdown and SVG remain traditional Markdown and image files. Office mappings include XLSM/Numbers as Excel, KEY as slides, and RTF/ODT/Pages as documents. fileExtension exposes the same basename and final-dot parsing for adjacent metadata labels. Traditional glyphs use a solid category-colored sheet with a white mark and translucent white corner; the generic file uses a grey sheet and darker grey corner. Callers may override the sheet color through --dsh-file-type-icon-color. The full-color technology artwork is the deliberate exception and retains its embedded palette. All glyphs are decorative and carry no label. LinkIcon remains the smaller leading glyph for clickable artifact links — globe, folder, code, image, document, or plain paper — while classifyLinkPath folds the shared file types into that existing six-category vocabulary. ConnectionIndicator renders a warning-colored disconnected action, a connecting label whose one-to-three dots advance every 500ms independently of retry timing, or a success-colored recovered status. Hover or keyboard focus shows only the reconnect action label, including while the connecting dots animate. Every state reserves the widest supplied label and uses fixed icon and text columns, so copy changes do not move or resize the control. Its owner supplies visibility, the recovery hold, localized labels, and the immediate-reconnect callback; the primitive uses no native title tooltip. useAnchoredPosition and useAnchoredMaxHeight keep floating panels and bottom-anchored overlays clamped to the viewport and following their anchor. HoverCard keeps its portaled preview reachable across the anchor gap and can expose a copy button through the copyText prop. Toast holds for the window its owner names through holdMs, because how long a banner has to stay depends on how much there is to read; the same value drives its unmount timer and the stylesheet's fade delay, so the two cannot disagree. rankByName is the / menu's shared candidate ranker for the command and skill sources: the query must be a case-insensitive ordered subsequence of the name; prefix hits rank first, then alignment score, then source order. Menu.autoFocus focuses its first enabled item, supports Arrow Up/Down and Home/End navigation, and focuses the first button in the anchor on Escape; it is opt-in for action menus.
MarkdownText renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. When the owner passes a pathImages vocabulary, image destinations that are local media paths rewrite to displayable URLs on settled renders only (the same streaming gate as file mentions); without a vocabulary, local destinations remain inert alt text. A load or decode failure replaces the image with its authored alt text, or the original destination when alt is empty. Changing the image source permits a fresh load. While a reply streams, it freezes completed blocks, advances a top-level open fence by completed lines, and highlights that fence from saved Shiki grammar state. Completed token lines enter fixed-size React groups, so later chunks reconcile only the growing group; an unchanged fence retains that DOM when the final full parse resolves cross-document syntax. TerminalBlock, ReadBlock, DiffBlock, SearchBlock, and WebBlock render the matching tool-result intent with copy controls, overflow handling, and ANSI processing where applicable. JsonTree and JsonBlock inspect JSON values read-only, while projectUserText projects sent user text into inline plain runs and reference chips for the message bubble and queue rows. When supplied with UserTextReferences, file and skill references become keyboard-accessible preview buttons using the same hover and focus styling as prose file links; the first pointer click can open a preview, while subsequent clicks and existing text selections retain native selection handling. Keyboard activation opens previews even when text is selected.
The atoms cannot read the application locale, so every piece of user-facing copy arrives through required label props. HoverCard, TerminalBlock, JsonTree, CodeBlock, MarkdownText, JsonBlock, ConnectionIndicator, Modal, DiffBlock, ReadBlock, SearchBlock, and WebBlock accept complete localized labels. The package owns no language fallback; omission fails typechecking, and each feature maps its typed t seat into the primitive's label interface.
These pages place the atoms in the client stack and the design system.
--dsw-* token system these atoms style through.None, as the package is a browser-side UI plugin layer that registers nothing model-facing.
None; this package neither assembles nor sends a provider request.
These limits define how the atoms behave at the edges; they are current package constraints, not a component roadmap.
Pill and Input have no design source — both atoms are self-defined; the sidebar search field and view-tab strip that resemble them are consumer-owned compositions, not these atoms.Active StateDot variant — the supported states are done, warning, ongoing, error, and idle.ctx.locale; each feature must supply complete localized labels through the primitive's typed props (decision).TerminalBlock is not a terminal emulator — it renders settled or still-running command output, not an interactive session: SGR colors, carriage return, backspace, erase-in-line, tab stops, and character width are honored; absolute cursor positioning, screen clearing, and alternate-screen sequences are stripped.Runtime invariant: No companion is published. Pure props-in React atoms with no Cordis API — no events, no services, no mutable cross-plugin state; rendering contracts are asserted directly by this package's component specs.