@deepseek-ai/dsh-client-ui-primitives 0.1.3-alpha.2 → 0.1.5-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-primitives/README.md
5
- README.md: 8e579d9467f1ab8c87bfb11d45354f68229fd138
6
- README.zh.md: 83dce1bdbdab34cde00e7cfb56c648a1d721421f
5
+ README.md: 18b136c78539d8ba6da3b17239112967faf63602
6
+ README.zh.md: f91435e44a5ca79b245fde3dd025a732d35e54e9
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-client-ui-primitives` is the web client's shared React component library: every feature plugin composes its UI from these atoms, and nothing here depends on Cordis or the slot system. It provides the control set (buttons, pills, inputs, menus, modals, toast banners, disclosure rows, hover cards, connection indicators), the icon glyphs and brand marks, positioning hooks for anchored overlays, and the content renderers for agent output: markdown with TeX math, terminal output, file reads, diffs, search results, web retrieval, and JSON inspection. The renderers are built for untrusted model output raw HTML is dropped, links are neutralized or opened safely, and ANSI escape sequences are parsed rather than passed through. User-facing copy is supplied through label props; the feature plugin that composes an atom owns localization.
12
+ 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. Callers must supply localized labels, and the components rely only on React and `--dsw-*` design tokens.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -64,11 +64,11 @@ Writing your own component in your own package is fine when the need is genuinel
64
64
 
65
65
  ### Controls and icons
66
66
 
67
- 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. `LinkIcon` draws the leading category glyph for clickable artifact links — globe, folder, code, image, document, or plain paper, all riding `currentColor` — and `classifyLinkPath` derives a file path's category from its extension. `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. 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.
67
+ 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. `LinkIcon` draws the leading category glyph for clickable artifact links — globe, folder, code, image, document, or plain paper, all riding `currentColor` — and `classifyLinkPath` derives a file path's category from its extension. `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.
68
68
 
69
69
  ### Rendering agent output
70
70
 
71
- `MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. 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.
71
+ `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.
72
72
 
73
73
 
74
74
  ### Localizing copy
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-library"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-client-ui-primitives` 是 Web 客户端共享的 React 组件库:每个功能插件都用这些原子组件拼装自己的 UI,而这里没有任何内容依赖 Cordis slot 系统。它提供控件集(按钮、胶囊、输入框、菜单、模态框、Toast 横幅、折叠行、悬浮卡片、连接指示器)、图标字形与品牌标记、锚定浮层用的定位钩子,以及 agent 输出的内容渲染器:带 TeX 公式的 markdown、终端输出、文件读取、差异、搜索结果、网页检索与 JSON 检查。这些渲染器为不受信任的模型输出而设计——原始 HTML 会被丢弃、链接会被失效或安全打开、ANSI 转义序列会被解析而非透传。面向用户的文案通过 label prop 提供;拼装某个原子组件的功能插件负责本地化。
12
+ 使用 `dsh-client-ui-primitives`,通过共享 React UI 构建 Web 客户端控件并渲染 agent 输出。它提供标准控件、图标、锚定浮层,以及用于带 TeX 公式的 Markdown、终端输出、文件读取、差异、搜索、网页检索和 JSON 的渲染器。这些渲染器会丢弃原始 HTML、限制链接并解析 ANSI 转义序列,以处理不受信任的模型输出。调用方必须提供本地化 label;这些组件仅依赖 React 和 `--dsw-*` 设计 token。
13
13
 
14
14
  ## 目录
15
15
 
@@ -64,11 +64,11 @@ kind: "package-library"
64
64
 
65
65
  ### 控件与图标
66
66
 
67
- 上面的目录说明每个导出的用途;本节讲 props 本身看不出来的行为。`ic_ds_*` 图标集与 `FishLogo`/`BrandWordmark` 标记填充品牌与行内图标 slot。`LinkIcon` 为可点击产物链接绘制前置分类图形——地球、文件夹、代码、图片、文档或纸张,全部随 `currentColor`——`classifyLinkPath` 按扩展名推导文件路径的类别。`ConnectionIndicator` 可渲染警告色的断联操作、以独立于 retry 时序的 500ms 节奏推进一至三个点的连接中状态,或成功色的恢复状态。所有状态都为最长的输入 label 预留空间,并使用固定的图标列和文字列,因此文案变化不会移动控件或改变其宽度。它的 owner 提供可见性、恢复驻留时间、本地化 label 与立即重连回调;该原语不使用原生 title tooltip。`useAnchoredPosition` 与 `useAnchoredMaxHeight` 让浮动面板与底部锚定浮层始终钳制在视口内并跟随锚点。`HoverCard` 通过指针离开宽限期让采用 portal 的预览在跨过锚点间隙时仍可触及,并可通过 `copyText` prop 提供复制按钮。 `Toast` 的停留时长由使用方通过 `holdMs` 指定,因为横幅该留多久取决于有多少内容要读;同一个值同时驱动它的卸载定时器与样式表的淡出延迟,两者不可能再错位。 `rankByName` 是 `/` 菜单命令源与 skill 源共享的候选排序器:查询必须是名字的不区分大小写的有序子序列;前缀命中排最前,其次按对齐分数,再按来源顺序。
67
+ 上面的目录说明每个导出的用途;本节讲 props 本身看不出来的行为。`ic_ds_*` 图标集与 `FishLogo`/`BrandWordmark` 标记填充品牌与行内图标 slot。`LinkIcon` 为可点击产物链接绘制前置分类图形——地球、文件夹、代码、图片、文档或纸张,全部随 `currentColor`——`classifyLinkPath` 按扩展名推导文件路径的类别。`ConnectionIndicator` 可渲染警告色的断联操作、以独立于 retry 时序的 500ms 节奏推进一至三个点的连接中状态,或成功色的恢复状态。悬停或键盘聚焦时只显示重连操作文案,连接中的圆点动画也保持隐藏。所有状态都为最长的输入 label 预留空间,并使用固定的图标列和文字列,因此文案变化不会移动控件或改变其宽度。它的 owner 提供可见性、恢复驻留时间、本地化 label 与立即重连回调;该原语不使用原生 title tooltip。`useAnchoredPosition` 与 `useAnchoredMaxHeight` 让浮动面板与底部锚定浮层始终钳制在视口内并跟随锚点。`HoverCard` 通过指针离开宽限期让采用 portal 的预览在跨过锚点间隙时仍可触及,并可通过 `copyText` prop 提供复制按钮。 `Toast` 的停留时长由使用方通过 `holdMs` 指定,因为横幅该留多久取决于有多少内容要读;同一个值同时驱动它的卸载定时器与样式表的淡出延迟,两者不可能再错位。 `rankByName` 是 `/` 菜单命令源与 skill 源共享的候选排序器:查询必须是名字的不区分大小写的有序子序列;前缀命中排最前,其次按对齐分数,再按来源顺序。
68
68
 
69
69
  ### 渲染 agent 输出
70
70
 
71
- `MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`projectUserText` 把已发送的用户文本投影为行内普通文本段与引用 chip,供消息气泡和排队行使用。
71
+ `MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。当 owner 传入 `pathImages` 词表时,本地媒体路径的图片目标只在落定渲染阶段重写为可展示 URL(与 file mentions 相同的流式门);不传词表时本地目标保持惰性 alt 文本。加载或解码失败后,图片替换为作者的 alt 文本;alt 为空时显示原始目标路径。图片源变化后可重新加载。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`projectUserText` 把已发送的用户文本投影为行内普通文本段与引用 chip,供消息气泡和排队行使用。
72
72
 
73
73
 
74
74
  ### 本地化文案
@@ -90,13 +90,13 @@
90
90
  }
91
91
 
92
92
  @keyframes reveal-second-dot {
93
- 0%, 33.32% { visibility: hidden; }
94
- 33.33%, 100% { visibility: visible; }
93
+ 0%, 33.32% { opacity: 0; }
94
+ 33.33%, 100% { opacity: 1; }
95
95
  }
96
96
 
97
97
  @keyframes reveal-third-dot {
98
- 0%, 66.65% { visibility: hidden; }
99
- 66.66%, 100% { visibility: visible; }
98
+ 0%, 66.65% { opacity: 0; }
99
+ 66.66%, 100% { opacity: 1; }
100
100
  }
101
101
 
102
102
  @media (prefers-reduced-motion: reduce) {
@@ -5,7 +5,7 @@
5
5
 
6
6
  .toast {
7
7
  position: fixed;
8
- top: 120px;
8
+ top: 40px;
9
9
  left: 50%;
10
10
  /* Above the 1000 the image lightbox backdrop uses: a failure reported while
11
11
  a preview is open must stay readable. */
@@ -15,7 +15,12 @@
15
15
  display: flex;
16
16
  align-items: center;
17
17
  gap: 10px;
18
- max-width: min(560px, calc(100vw - 48px));
18
+ /* Fixed boxes with `left` set shrink-to-fit against the space RIGHT of
19
+ `left` (the -50% translate happens after sizing), so an anchored banner
20
+ near the window edge would wrap early. max-content sizes the box from
21
+ its text alone, capped by the max-width below. */
22
+ width: max-content;
23
+ max-width: min(640px, calc(100vw - 48px));
19
24
  padding: 12px 16px;
20
25
  border-radius: 14px;
21
26
  background: var(--dsw-alias-button-contrast-fill);
package/lib/index.js CHANGED
@@ -1205,6 +1205,35 @@ const IconClockOutline16 = ({ size = 16, className }) => jsxs("svg", {
1205
1205
  strokeWidth: "1.25"
1206
1206
  })]
1207
1207
  });
1208
+ /** Thin-stroke gauge: dial arc open at the bottom, filled hub, square-cut needle to the upper right.
1209
+ * The dial center sits at y=8.75, not 8: the bottom opening leaves the glyph top-heavy, and the
1210
+ * 0.75 drop optically centers the drawn extent in the 16 box. */
1211
+ const IconGaugeOutline16 = ({ size = 16, className }) => jsxs("svg", {
1212
+ width: size,
1213
+ height: size,
1214
+ className,
1215
+ viewBox: "0 0 16 16",
1216
+ fill: "none",
1217
+ xmlns: "http://www.w3.org/2000/svg",
1218
+ children: [
1219
+ jsx("path", {
1220
+ d: "M3.49 13.26A6.375 6.375 0 1 1 12.51 13.26",
1221
+ stroke: "currentColor",
1222
+ strokeWidth: "1.25"
1223
+ }),
1224
+ jsx("path", {
1225
+ d: "M8 8.75L11.4 5.35",
1226
+ stroke: "currentColor",
1227
+ strokeWidth: "1.25"
1228
+ }),
1229
+ jsx("circle", {
1230
+ cx: "8",
1231
+ cy: "8.75",
1232
+ r: "1.55",
1233
+ fill: "currentColor"
1234
+ })
1235
+ ]
1236
+ });
1208
1237
  /** ic_send_outline_14 (figma extract): thin-stroke upward send arrow. */
1209
1238
  const IconSendOutline14 = ({ size = 14, className }) => jsx("svg", {
1210
1239
  width: size,
@@ -6490,6 +6519,30 @@ function remoteImageUrl(url) {
6490
6519
  return;
6491
6520
  }
6492
6521
  }
6522
+ /** Protocols a vocabulary-rewritten image destination may carry. */
6523
+ function vocabularyImageUrl(url) {
6524
+ try {
6525
+ const protocol = new URL(url).protocol;
6526
+ return protocol === "http:" || protocol === "https:" || protocol === "blob:" || protocol === "data:" ? url : void 0;
6527
+ } catch {
6528
+ return;
6529
+ }
6530
+ }
6531
+ /**
6532
+ * The displayable source for one image destination: absolute HTTP(S) as
6533
+ * authored, otherwise the context's local-path vocabulary when it vouches for
6534
+ * the destination. Either miss leaves the authored fallback (alt text) to the
6535
+ * caller.
6536
+ * @param url - The authored markdown destination.
6537
+ * @param pathImages - Rewriting vocabulary, when the render pass has one.
6538
+ * @returns The displayable image URL, or undefined.
6539
+ */
6540
+ function imageSource(url, pathImages) {
6541
+ const remote = remoteImageUrl(sanitizeUrl(normalizeUri(url)));
6542
+ if (remote !== void 0) return remote;
6543
+ const rewritten = pathImages?.resolve(url);
6544
+ return rewritten === void 0 ? void 0 : vocabularyImageUrl(rewritten);
6545
+ }
6493
6546
  /**
6494
6547
  * Create an empty {@link ReferenceTargets}.
6495
6548
  * @returns Fresh empty maps.
@@ -6604,7 +6657,7 @@ function renderNode(node, key, context) {
6604
6657
  inLink: true
6605
6658
  }), key, !anchorWrapsOnlyImages(node.children));
6606
6659
  case "linkReference": return renderLinkReference(node, key, context);
6607
- case "image": return renderImage(node.url, node.alt ?? "", key);
6660
+ case "image": return renderImage(node.url, node.alt ?? "", key, context);
6608
6661
  case "imageReference": return renderImageReference(node, key, context);
6609
6662
  case "footnoteReference": return renderFootnoteReference(node, key, context);
6610
6663
  case "definition":
@@ -6738,20 +6791,36 @@ function inlineCodeHttpUrl(value) {
6738
6791
  return;
6739
6792
  }
6740
6793
  }
6741
- function renderImage(url, alt, key) {
6742
- const imageSrc = remoteImageUrl(sanitizeUrl(normalizeUri(url)));
6794
+ function renderImage(url, alt, key, context) {
6795
+ const imageSrc = imageSource(url, context.pathImages);
6743
6796
  if (imageSrc === void 0) return jsx("span", {
6744
6797
  className: css$22.imageAlt,
6745
6798
  children: alt
6746
6799
  }, key);
6800
+ return jsx(MarkdownImage, {
6801
+ src: imageSrc,
6802
+ alt,
6803
+ destination: url
6804
+ }, `${key}:${imageSrc}`);
6805
+ }
6806
+ /** Failed loads retain the authored alt or destination; a new source remounts the image. */
6807
+ function MarkdownImage({ src, alt, destination }) {
6808
+ const [failed, setFailed] = useState(false);
6809
+ if (failed) return jsx("span", {
6810
+ className: css$22.imageAlt,
6811
+ children: alt || destination
6812
+ });
6747
6813
  return jsx("img", {
6748
6814
  className: css$22.image,
6749
- src: imageSrc,
6815
+ src,
6750
6816
  alt,
6817
+ onError: () => {
6818
+ setFailed(true);
6819
+ },
6751
6820
  loading: "lazy",
6752
6821
  decoding: "async",
6753
6822
  referrerPolicy: "no-referrer"
6754
- }, key);
6823
+ });
6755
6824
  }
6756
6825
  /** The bracketed source text a reference reverts to when its definition is missing. */
6757
6826
  function referenceSuffix(node) {
@@ -6775,7 +6844,7 @@ function renderLinkReference(node, key, context) {
6775
6844
  function renderImageReference(node, key, context) {
6776
6845
  const definition = context.targets.definitions.get(node.identifier.toUpperCase());
6777
6846
  if (definition === void 0) return `![${node.alt ?? ""}${referenceSuffix(node)}`;
6778
- return renderImage(definition.url, node.alt ?? "", key);
6847
+ return renderImage(definition.url, node.alt ?? "", key, context);
6779
6848
  }
6780
6849
  function renderFootnoteReference(node, key, context) {
6781
6850
  const id = node.identifier.toUpperCase();
@@ -6838,7 +6907,7 @@ function renderFootnoteSection(context) {
6838
6907
  * full parse self-heals it.
6839
6908
  */
6840
6909
  /** One settled full render: parse with math, resolve references, append the footnote section. */
6841
- function renderSettled(text, labels, fileMentions) {
6910
+ function renderSettled(text, labels, fileMentions, pathImages) {
6842
6911
  const root = parseGfmWithMath(text);
6843
6912
  const targets = createReferenceTargets();
6844
6913
  collectReferenceTargets(root.children, targets);
@@ -6846,6 +6915,7 @@ function renderSettled(text, labels, fileMentions) {
6846
6915
  streaming: false,
6847
6916
  labels,
6848
6917
  fileMentions,
6918
+ pathImages,
6849
6919
  targets,
6850
6920
  footnoteOrder: [],
6851
6921
  footnoteCounts: /* @__PURE__ */ new Map()
@@ -6912,6 +6982,7 @@ var StreamingRenderer = class {
6912
6982
  streaming: true,
6913
6983
  labels: this.labels,
6914
6984
  fileMentions: void 0,
6985
+ pathImages: void 0,
6915
6986
  targets: frameTargets,
6916
6987
  footnoteOrder: this.frozenFootnoteOrder,
6917
6988
  footnoteCounts: this.frozenFootnoteCounts
@@ -6928,6 +6999,7 @@ var StreamingRenderer = class {
6928
6999
  streaming: true,
6929
7000
  labels: this.labels,
6930
7001
  fileMentions: void 0,
7002
+ pathImages: void 0,
6931
7003
  targets: frameTargets,
6932
7004
  footnoteOrder: [...this.frozenFootnoteOrder],
6933
7005
  footnoteCounts: new Map(this.frozenFootnoteCounts)
@@ -6953,21 +7025,23 @@ var StreamingRenderer = class {
6953
7025
  * `labels` forwards localized fence and footnote chrome — pass a
6954
7026
  * reference-stable object (memoized per locale revision), because a new
6955
7027
  * identity discards the streaming render cache mid-message. `fileMentions`
6956
- * links inline-code tokens its resolver recognizes as real files; this is
6957
- * the single streaming gate it applies to settled renders only, because a
7028
+ * links inline-code tokens its resolver recognizes as real files, and
7029
+ * `pathImages` rewrites image destinations that are local file paths into
7030
+ * displayable URLs its resolver vouches for; both vocabularies are the
7031
+ * single streaming gate — they apply to settled renders only, because a
6958
7032
  * streaming message's vocabulary is not final and frozen cached elements
6959
7033
  * must not bake in handlers that could go stale.
6960
7034
  * @returns A GFM document with TeX math rendered through KaTeX; raw HTML,
6961
7035
  * relative links, and unsafe protocols are disabled, while absolute HTTP(S)
6962
7036
  * images render directly.
6963
7037
  */
6964
- const MarkdownText = memo(function MarkdownText({ text, streaming = false, labels, fileMentions }) {
7038
+ const MarkdownText = memo(function MarkdownText({ text, streaming = false, labels, fileMentions, pathImages }) {
6965
7039
  const streamRef = useRef(null);
6966
7040
  const streamLabelsRef = useRef(labels);
6967
7041
  const children = useMemo(() => {
6968
7042
  if (!streaming) {
6969
7043
  streamRef.current = null;
6970
- return renderSettled(text, labels, fileMentions);
7044
+ return renderSettled(text, labels, fileMentions, pathImages);
6971
7045
  }
6972
7046
  if (streamRef.current === null || streamLabelsRef.current !== labels) {
6973
7047
  streamRef.current = new StreamingRenderer(labels);
@@ -6978,7 +7052,8 @@ const MarkdownText = memo(function MarkdownText({ text, streaming = false, label
6978
7052
  text,
6979
7053
  streaming,
6980
7054
  labels,
6981
- fileMentions
7055
+ fileMentions,
7056
+ pathImages
6982
7057
  ]);
6983
7058
  return jsx("div", {
6984
7059
  className: css$22.markdown,
@@ -7261,6 +7336,6 @@ function extractMarkdownPlainText(markdown, options = {}) {
7261
7336
  }
7262
7337
  }
7263
7338
  //#endregion
7264
- export { BrandWordmark, Button, CodeBlock, ConnectionIndicator, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DiffBlock, DisclosureRow, DocumentFileIcon, FISH_LOGO_PATH, FISH_LOGO_VIEWBOX, FishLogo, HoverCard, IconAgentPresetOutline16, IconAlarmClockOutline16, IconApiOutline14, IconArchiveOutline20, IconBranchOutline16, IconBrowseOutline16, IconCheckOutline14, IconCheckOutline16, IconChecklistOutline14, IconChevronDownOutline14, IconChevronLeftOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconClockOutline16, IconCloseFill14, IconCloseOutline16, IconCodeOutline16, IconContextInjectionOutline16, IconCopyOutline16, IconCordisPluginOutline14, IconDarkOutline16, IconDataOutline16, IconDatabaseOutline16, IconDislikeFill16, IconDislikeOutline16, IconDownloadOutline16, IconEditOutline16, IconEllipsisOutline16, IconEnhanceOutline16, IconFolderClose16, IconFolderOpen16, IconFolderOpenOutline16, IconFollowsystemOutline16, IconFullscreenOutline16, IconGlobeOutline14, IconGoalOutline16, IconInspectOutline12, IconLightOutline16, IconLikeFill16, IconLikeOutline16, IconLinkOutline14, IconLinkOutline16, IconListPenOutline16, IconLoadingOutline16, IconNewChatOutline16, IconPanelLeftOutline16, IconPaperclipOutline16, IconPauseOutline16, IconPersonalizationOutline16, IconPlayOutline16, IconPlusOutline16, IconProjectAddOutline16, IconQuestionOutline14, IconQueueOutline14, IconRefreshOutline14, IconRefreshOutline16, IconRightUpOutline14, IconRightUpOutline16, IconSearchOutline16, IconSendOutline14, IconSendOutline16, IconSettingsOutline14, IconSettingsOutline16, IconShareOutline16, IconSkillOutline16, IconSparkle16, IconStopFill16, IconThinkOutline14, IconThinkOutline16, IconTrashOutline16, IconTreeCorner8x10, IconTriangleRightFill14, IconUserOutline16, IconWarningOutline16, Input, JsonBlock, JsonTree, LinkIcon, MarkdownText, Menu, Modal, OnboardingSurface, Pill, ReadBlock, ReferenceIcon, RiskConfirmation, SearchBlock, StateDot, Switch, Tag, TerminalBlock, Toast, Tooltip, WebBlock, classifyLinkPath, diffTotals, extractMarkdownPlainText, fileSizeText, projectUserText, rankByName, relativeTime, useAnchoredMaxHeight, useAnchoredPosition, useDismissOnOutsidePointer, writeClipboard };
7339
+ export { BrandWordmark, Button, CodeBlock, ConnectionIndicator, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DiffBlock, DisclosureRow, DocumentFileIcon, FISH_LOGO_PATH, FISH_LOGO_VIEWBOX, FishLogo, HoverCard, IconAgentPresetOutline16, IconAlarmClockOutline16, IconApiOutline14, IconArchiveOutline20, IconBranchOutline16, IconBrowseOutline16, IconCheckOutline14, IconCheckOutline16, IconChecklistOutline14, IconChevronDownOutline14, IconChevronLeftOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconClockOutline16, IconCloseFill14, IconCloseOutline16, IconCodeOutline16, IconContextInjectionOutline16, IconCopyOutline16, IconCordisPluginOutline14, IconDarkOutline16, IconDataOutline16, IconDatabaseOutline16, IconDislikeFill16, IconDislikeOutline16, IconDownloadOutline16, IconEditOutline16, IconEllipsisOutline16, IconEnhanceOutline16, IconFolderClose16, IconFolderOpen16, IconFolderOpenOutline16, IconFollowsystemOutline16, IconFullscreenOutline16, IconGaugeOutline16, IconGlobeOutline14, IconGoalOutline16, IconInspectOutline12, IconLightOutline16, IconLikeFill16, IconLikeOutline16, IconLinkOutline14, IconLinkOutline16, IconListPenOutline16, IconLoadingOutline16, IconNewChatOutline16, IconPanelLeftOutline16, IconPaperclipOutline16, IconPauseOutline16, IconPersonalizationOutline16, IconPlayOutline16, IconPlusOutline16, IconProjectAddOutline16, IconQuestionOutline14, IconQueueOutline14, IconRefreshOutline14, IconRefreshOutline16, IconRightUpOutline14, IconRightUpOutline16, IconSearchOutline16, IconSendOutline14, IconSendOutline16, IconSettingsOutline14, IconSettingsOutline16, IconShareOutline16, IconSkillOutline16, IconSparkle16, IconStopFill16, IconThinkOutline14, IconThinkOutline16, IconTrashOutline16, IconTreeCorner8x10, IconTriangleRightFill14, IconUserOutline16, IconWarningOutline16, Input, JsonBlock, JsonTree, LinkIcon, MarkdownText, Menu, Modal, OnboardingSurface, Pill, ReadBlock, ReferenceIcon, RiskConfirmation, SearchBlock, StateDot, Switch, Tag, TerminalBlock, Toast, Tooltip, WebBlock, classifyLinkPath, diffTotals, extractMarkdownPlainText, fileSizeText, projectUserText, rankByName, relativeTime, useAnchoredMaxHeight, useAnchoredPosition, useDismissOnOutsidePointer, writeClipboard };
7265
7340
 
7266
7341
  //# sourceMappingURL=index.js.map
@@ -76,6 +76,6 @@ export declare function Menu({ open, anchor, items, selectedId, selectedIds, onS
76
76
  compact?: boolean;
77
77
  selection?: 'check' | 'fill';
78
78
  getAnchorRect?: () => DOMRect | null;
79
- className?: string;
79
+ className?: string | undefined;
80
80
  }): import("react").JSX.Element;
81
81
  //# sourceMappingURL=Menu.d.ts.map
@@ -135,6 +135,10 @@ export declare const IconDataOutline16: ({ size, className }: IconProps) => impo
135
135
  export declare const IconDatabaseOutline16: ({ size, className }: IconProps) => import("react").JSX.Element;
136
136
  /** Thin-stroke clock: outlined dial with square-cut hour and minute hands. */
137
137
  export declare const IconClockOutline16: ({ size, className }: IconProps) => import("react").JSX.Element;
138
+ /** Thin-stroke gauge: dial arc open at the bottom, filled hub, square-cut needle to the upper right.
139
+ * The dial center sits at y=8.75, not 8: the bottom opening leaves the glyph top-heavy, and the
140
+ * 0.75 drop optically centers the drawn extent in the 16 box. */
141
+ export declare const IconGaugeOutline16: ({ size, className }: IconProps) => import("react").JSX.Element;
138
142
  /** ic_send_outline_14 (figma extract): thin-stroke upward send arrow. */
139
143
  export declare const IconSendOutline14: ({ size, className }: IconProps) => import("react").JSX.Element;
140
144
  /** ic_queue_outline_14 (figma extract): open chat bubble with two queued lines. */
@@ -58,7 +58,7 @@ export { CodeBlock } from './markdown/CodeBlock.tsx';
58
58
  export type { CodeBlockProps } from './markdown/CodeBlock.tsx';
59
59
  export { JsonBlock } from './markdown/JsonBlock.tsx';
60
60
  export { MarkdownText } from './markdown/MarkdownText.tsx';
61
- export type { MarkdownCodeLabels, MarkdownFileMentions, MarkdownLabels } from './markdown/MarkdownText.tsx';
61
+ export type { MarkdownCodeLabels, MarkdownFileMentions, MarkdownLabels, MarkdownPathImages } from './markdown/MarkdownText.tsx';
62
62
  export { extractMarkdownPlainText } from './markdown/plain-text.ts';
63
63
  export type { MarkdownPlainTextMode, MarkdownPlainTextOptions } from './markdown/plain-text.ts';
64
64
  export * from './icons/index.tsx';
@@ -10,9 +10,9 @@
10
10
  * other side of the freeze boundary renders literally until the settled
11
11
  * full parse self-heals it.
12
12
  */
13
- import type { MarkdownFileMentions, MarkdownLabels } from './render.tsx';
13
+ import type { MarkdownFileMentions, MarkdownLabels, MarkdownPathImages } from './render.tsx';
14
14
  import 'katex/dist/katex.min.css';
15
- export type { MarkdownCodeLabels, MarkdownFileMentions, MarkdownLabels } from './render.tsx';
15
+ export type { MarkdownCodeLabels, MarkdownFileMentions, MarkdownLabels, MarkdownPathImages } from './render.tsx';
16
16
  /**
17
17
  * Render untrusted assistant-authored Markdown as semantic React elements.
18
18
  * @param props - Markdown source text preserved by the session projection;
@@ -22,18 +22,21 @@ export type { MarkdownCodeLabels, MarkdownFileMentions, MarkdownLabels } from '.
22
22
  * `labels` forwards localized fence and footnote chrome — pass a
23
23
  * reference-stable object (memoized per locale revision), because a new
24
24
  * identity discards the streaming render cache mid-message. `fileMentions`
25
- * links inline-code tokens its resolver recognizes as real files; this is
26
- * the single streaming gate it applies to settled renders only, because a
25
+ * links inline-code tokens its resolver recognizes as real files, and
26
+ * `pathImages` rewrites image destinations that are local file paths into
27
+ * displayable URLs its resolver vouches for; both vocabularies are the
28
+ * single streaming gate — they apply to settled renders only, because a
27
29
  * streaming message's vocabulary is not final and frozen cached elements
28
30
  * must not bake in handlers that could go stale.
29
31
  * @returns A GFM document with TeX math rendered through KaTeX; raw HTML,
30
32
  * relative links, and unsafe protocols are disabled, while absolute HTTP(S)
31
33
  * images render directly.
32
34
  */
33
- export declare const MarkdownText: import("react").MemoExoticComponent<({ text, streaming, labels, fileMentions }: {
35
+ export declare const MarkdownText: import("react").MemoExoticComponent<({ text, streaming, labels, fileMentions, pathImages }: {
34
36
  text: string;
35
37
  streaming?: boolean;
36
38
  labels: MarkdownLabels;
37
39
  fileMentions?: MarkdownFileMentions | undefined;
40
+ pathImages?: MarkdownPathImages | undefined;
38
41
  }) => import("react").JSX.Element>;
39
42
  //# sourceMappingURL=MarkdownText.d.ts.map
@@ -49,6 +49,23 @@ export declare function createReferenceTargets(): ReferenceTargets;
49
49
  * @param targets - Accumulator, typically shared across incremental segments.
50
50
  */
51
51
  export declare function collectReferenceTargets(nodes: readonly Md.RootContent[], targets: ReferenceTargets): void;
52
+ /**
53
+ * Local-path image vocabulary for image destinations: the owner maps an
54
+ * authored destination that fails the remote-URL allowlist (an absolute local
55
+ * file path, for example) to a displayable URL it can vouch for. Absent
56
+ * wherever no such vocabulary exists, authored local destinations keep their
57
+ * documented fallback (the image's alt text). Rewritten destinations must be
58
+ * absolute; the renderer re-checks their protocol before emitting them.
59
+ */
60
+ export interface MarkdownPathImages {
61
+ /**
62
+ * Resolve one authored image destination.
63
+ * @param value - The destination exactly as the markdown author wrote it.
64
+ * @returns A displayable absolute URL, or undefined when the destination
65
+ * names no displayable image — it then stays inert alt text.
66
+ */
67
+ resolve(value: string): string | undefined;
68
+ }
52
69
  /**
53
70
  * File-mention affordance for inline code: the owner resolves an authored
54
71
  * token to the file it names, using its own vocabulary of real files — the
@@ -80,6 +97,8 @@ export interface MarkdownRenderContext {
80
97
  readonly inBlockquote?: boolean;
81
98
  /** Inline-code file mentions; absent wherever no opener vocabulary exists. */
82
99
  readonly fileMentions: MarkdownFileMentions | undefined;
100
+ /** Local-path image vocabulary; absent wherever no rewriting owner exists. */
101
+ readonly pathImages: MarkdownPathImages | undefined;
83
102
  /** Inside an anchor's children: interactive mentions must not nest there. */
84
103
  readonly inLink?: boolean;
85
104
  /** Reference targets visible to this pass. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-primitives",
3
3
  "description": "Pure React atoms for the dsh web UI: controls, icons, markdown, and JSON inspectors (zero cordis)",
4
- "version": "0.1.3-alpha.2",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },