Files
deepseek-harness/packages/client/ui-primitives
Chinesezjc 20b9cd6b36 Merge remote-tracking branch 'origin/master' into feat/web-search-card
# Conflicts:
#	packages/client/connection/src/client/fixture.ts
#	packages/client/ui-conversation/src/client/apply.ts
#	packages/client/ui-conversation/src/client/chat/GenericToolCard.tsx
#	packages/client/ui-conversation/src/client/skeleton/DetailsPanel.module.css
#	packages/client/ui-conversation/src/client/skeleton/DetailsPanel.tsx
#	packages/client/ui-conversation/tests/chat-apply.spec.tsx
#	packages/client/ui-primitives/src/index.ts
2026-07-31 16:49:20 +08:00
..

@deepseek-ai/dsh-client-ui-primitives

English | 中文

Pure React atoms (zero cordis): StateDot, ic_ds_* icons, Button/Pill/Menu/Modal/Input, the markdown family (MessageText/MarkdownText/JsonBlock), the read-only JsonTree inspector, the useAnchoredMaxHeight hook that clamps a bottom-anchored overlay to the viewport space above its anchor (re-measured on resize, scroll, and a caller-supplied dependency), TerminalBlock, SearchBlock, DiffBlock, and WebBlock. Contract: api-contracts v3 §8.

Markdown rendering

MarkdownText renders GFM from untrusted assistant output through React elements. It omits raw HTML, neutralizes relative and non-HTTP(S)/mailto links, opens HTTP(S) links with safe external-link attributes, and renders image alt text without loading remote resources; MessageText remains the literal-text primitive for user-authored content. extractMarkdownPlainText removes Markdown presentation markup for compact labels while preserving raw HTML as literal text. Element spacing, tables, links, and inline code use the same --dsw-alias-markdown-* / --dsw-font-markdown-* tokens as deepsuite @deepseek/md. Fenced blocks render through CodeBlock (language banner, copy control, shiki for the registered grammars).

Terminal output

TerminalBlock renders a shell command as a terminal surface: one prompt row per line of the command (the shortened cwd label on the first row only, since the view knows one working directory and a cd moves later lines elsewhere, then that line), the command's output, a status pill for a non-zero exit code or a terminating signal, and a copy control that writes the raw output prop. A run-state StateDot marks the call once, on the first row, out of flow in a gutter the card reserves as its own left padding, so the dot sits inside the card box yet left of the prompt text. It reaches three of StateDot's states — the chase while running, red for the same exit status that renders the pill, green otherwise — so a card states whether its command is still running rather than leaving that to be inferred from the presence of output; it carries one visually hidden text label because StateDot is aria-hidden. One dot regardless of line count is deliberate: the exit status is the whole call's, so a dot per line would claim a per-line outcome the view does not carry. Command text is white-space: pre, so repeated spaces, tabs, and an indented continuation render verbatim while the row stays single-line and ellipsizes. ANSI escape sequences are parsed with the anser runtime dependency into React spans; cursor movements replay into a per-line column buffer before inert controls are stripped, since carriage return and backspace only MOVE the cursor: 100% + CR + OK alone shows OK0%, while the \x1b[K a spinner writes with its redraw erases the tail so 100%\r\x1b[KOK shows OK. Erase-in-line is honored in all three parameter forms, the cursor advances by terminal columns (8-column tab stops, two for emoji and CJK, none for a combining mark), and SGR state is normalized per cell as a terminal stores it, threading across lines and closing at the state the line ended in; basic-16 foreground colors map onto --dsw-* tokens, while 256-palette and truecolor values pass through as literal rgb. Output keeps white-space: pre with horizontal scrolling, so column-aligned output holds its alignment instead of soft-wrapping, and collapses to a head slice plus a tail slice past maxLines (default 16, the TUI transcript's split arithmetic) behind an expand button. Rationale: the web terminal card note.

Search results

SearchBlock renders a completed search, one component for both kinds (discriminated by kind). A matches (grep) shows each file as a bold path header with its lineNumber: line rows, the per-file group collapsible; a paths (glob) shows a flat path list. Both flatten to one row list the height cap slices head/tail over (default 16, the TerminalBlock split arithmetic), and neither soft-wraps — a long match line or path scrolls horizontally instead of folding. The banner summary folds the pre-cap total in when the tool capped the result (显示 X / 共 N 处匹配 · K 个文件 for grep, 显示 X / 共 N 个路径 for glob), so the card never presents a capped result as complete; a copy control writes the whole structured result regardless of the cap or which groups are collapsed. Geometry mirrors CodeBlock/TerminalBlock. Rationale: the web search card note.

Diff rendering

DiffBlock renders a file mutation as an inline diff surface: one bold path header per file, the removed lines (- , error token) above the added lines (+ , success token), a gap before a same-file second hunk, and a dim └ +A -R · N file(s) footer. Lines are white-space: pre with horizontal scrolling, so a source line holds its indentation instead of soft-wrapping, and the body collapses to a head slice plus a tail slice past maxLines (default 16, TerminalBlock's split arithmetic) behind an expand button. A create (oldText: null) has no removed side. The copy control writes the prefixed diff text (path headers, - /+ lines, the gap) so a multi-file copy stays attributable, and floats in the top-right corner rather than on a banner row of its own. Geometry mirrors CodeBlock/TerminalBlock. The +/- block form mirrors the TUI transcript's diff card so a diff reads the same across front ends. Rationale: the web diff card note.

Web retrieval

WebBlock renders a completed web retrieval, one component for both kinds of the web render intent (discriminated by kind). A search shows an optional provider answer (through MarkdownText) above an ordered citation list: each source is a safe external link labelled by its title, or its hostname, falling back to the raw URL when the URL does not parse or has no hostname (a file:/data: URL) so a label is never blank; its snippet and publication date render below it. Only http(s) URLs become anchors (target/rel set) — the http(s) subset of the allowlist MarkdownText applies to untrusted links (it also permits mailto:, excluded here); any other URL renders as plain text. A long list caps at maxSources (default 16, the TerminalBlock split arithmetic) with a head/tail collapse; the collapsed tail keeps each source's original citation number via <li value>, and the expand control is a marker-less <li> so the <ol> stays valid HTML. When a search legitimately returns no answer and no sources, the card shows an explicit empty-state note rather than a blank <ol> (the chat row does not surface the raw result content). A fetch shows a compact summary: the linked final URL and its HTTP status. Both mark a capped retrieval. Rationale: the web result card note.

Model Experience

None, as the package renders pure React atoms in the browser; nothing here reaches a model request.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

  • Glyph-level icons are redrawn approximations — the fish logo (and the sparkle held by ui-conversation) come from font glyphs whose vector geometry is not exportable from the local design data; hand-authored recreations stand in until an exact export path exists.
  • 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.
  • StateDot Active variant is a hidden placeholder in the design — not implemented; the four shipped states (done/warning/ongoing/error) are the complete P-I surface.
  • User-facing copy localizes through label props, defaulting to the original Chinese literals — the atoms are zero-cordis and cannot reach ctx.locale, so TerminalBlock (labels), JsonTree (labels), CodeBlock (copyLabel/copiedLabel), MarkdownText (codeLabels), JsonBlock (truncatedLabel), ConnectionBanner (label), and Modal (closeLabel) take their copy as optional props with the previous hardcoded strings as defaults. Localized plugins pass dictionary-driven labels from their own t seat; a consumer that passes nothing renders exactly the pre-localization output. WebBlock does not yet follow this pattern: its source expand/collapse controls, source-list and fetch truncation notes, and empty-search note stay inline Chinese, pending the same label-prop treatment.
  • TerminalBlock is not a terminal emulator — it renders settled or still-running command output, not an interactive session: SGR color and attributes are honored, and so are the in-line cursor movements a progress line uses — carriage return, backspace, erase-in-line, tab stops and character width. Absolute cursor positioning, screen clearing, and alternate-screen sequences are stripped. Basic-16 magenta and cyan have no token equivalent and stay literal rgb.