@deepseek-ai/dsh-client-ui-primitives 0.1.7-alpha.2 → 0.1.7-rc.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: 332845471056e718d49d79a077b76a02c79bafe2
6
- README.zh.md: 598b561e79e4a935f7f033002e68b91c3a3d84de
5
+ README.md: fbb8ef676e55d1ef2c3c5c8a6c91a12a3c8fe556
6
+ README.zh.md: 822ff17c3c4c6b02339cdacd03d9c52c67a738c7
package/README.md CHANGED
@@ -49,11 +49,12 @@ Check this table before writing a control in a feature package. A plugin cannot
49
49
  | `StateDot` | Solid green `done`, amber `warning`, red `error`, and neutral-grey `idle` marks in a 10px slot, plus a tertiary-grey 14px rotating `ongoing` loader whose animations pin to document time zero so every visible loader rotates in phase. `aria-hidden`, so the render site owns the name. `appearance="step"` shows a filled check for completion and a hollow pending circle. |
50
50
  | `ConnectionIndicator` | Inline connection-recovery control across outage, retry, and recovered states. |
51
51
  | `DisclosureRow` | 24px compact disclosure that lays title and content side by side. Memoized with shallow prop comparison; keep callbacks and React-node props stable when their content is unchanged. |
52
- | `Modal` | Centered dialog over a page mask. |
52
+ | `Modal` | Centered dialog over a page mask. A nested dialog can intercept keys with `onKeyDownCapture` before document Escape handlers. |
53
53
  | `RiskConfirmation` | Sensitive action gated behind an explicit checkbox. |
54
54
  | `OnboardingSurface` | First-run stage that holds the application root inert. |
55
- | `Tooltip` | Hover text anchored to a cloned child; optional `portal` rendering escapes clipping containers. |
55
+ | `Tooltip` | Hover text anchored to a cloned child; optional `portal` rendering escapes clipping containers and ancestor stacking contexts that cap the bubble's z-index. |
56
56
  | `HoverCard` | Hover preview the pointer can rest on and select from; optional copy button. |
57
+ | `ImageLightbox` | Shared image modal with focus restoration and Escape dismissal. |
57
58
  | `Toast` | Transient top-center banner held for the owner's `holdMs`. |
58
59
  | `SettingsForm`, `SettingsValueField`, `SettingsSecretField` | The frame and the controls of a plugin's settings page: the frame takes its copy as `labels`, saves only on its button, and discards on unmount; a value field shows staged text with the overridden badge and reset; a secret field starts blank and reports only whether a value is configured. |
59
60
  | `SettingsFormModel`, `settingsNumberField`, `settingsTextField` | The staged-edit model behind such a page over a settings scope: drafts are staged and written on save, a field is overridden by its presence in the user layer, and a save that did not land keeps its drafts. |
@@ -85,7 +86,7 @@ The catalog above lists what each export is for; this section covers the behavio
85
86
 
86
87
  The nearest `MarkdownDelegateProvider` supplies optional `openExternalLink` and `openFile` navigation callbacks. Nested providers replace the enclosing capabilities, and callback changes reach already-rendered links without rebuilding Markdown. Its `openFile` makes local Markdown links clickable after settlement. Absolute and workspace-relative paths support percent escapes and `#L24` / `#L24-L30` fragments; ranges open at their first line. Literal `?` and `#` in filenames must be percent-encoded. The tooltip uses the decoded path and supplies the accessible name when the label is empty. The callback receives the decoded path and optional line, while the renderer preserves the label and displays a file icon. Without a callback, local links remain text. URL schemes, queries, unsupported fragments, and malformed destinations never reach the file opener.
87
88
 
88
- `MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. A surrounding `MarkdownDelegateProvider` receives sanitized HTTP(S) URLs from ordinary clicks; modified clicks and links outside a provider retain native external-anchor behavior. 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. Reference labels inherit the consumer’s wrapping policy: long names wrap within a bubble, while queue previews retain their single-line layout. 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.
89
+ `MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. A surrounding `MarkdownDelegateProvider` receives sanitized HTTP(S) URLs from ordinary clicks; modified clicks and links outside a provider retain native external-anchor behavior. 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; `fileImages` additionally supplies a localized failure prefix. 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. Reference labels inherit the consumer’s wrapping policy: long names wrap within a bubble, while queue previews retain their single-line layout. 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.
89
90
 
90
91
  `MarkdownText` defaults to `variant="body"`. Use `variant="compact"` for secondary content: its 13px text and 20px line height follow the content-size setting, all heading levels use the same size with weight 600, and paragraphs and lists use tighter spacing. Text, links, and code keep the tertiary color; dotted underlines distinguish links. Code headers scroll with their blocks. Tables and math stay enabled at the surrounding text size and scroll horizontally within the available width. Both variants share the parser and streaming cache.
91
92
 
@@ -95,6 +96,8 @@ The nearest `MarkdownDelegateProvider` supplies optional `openExternalLink` and
95
96
 
96
97
  `JsonTree` clamps collapsed strings to `collapsedStringLines` (three by default). Expanded strings show raw text, retain sibling commas, and fit within the window and outer scrolling containers. Resize and ancestor-scroll events update that limit. Row copy feedback updates independently of JSON value rendering; pending clipboard writes cannot update a different row or an unmounted tree.
97
98
 
99
+ `ImageLightbox` is the shared original-image modal with focus restoration and Escape dismissal. The internal thumbnail renderer receives loading/failure labels from its owner. `HoverCard.inline` keeps a file link in the text flow and uses the shared menu material, keyboard-visible focus, and placement above or below the anchor without covering it. Escape closes an open thumbnail even when focus is elsewhere; subsequent Escape presses reach the owner. `MarkdownDelegateProvider.fileImages` supplies a decoded-path resolver and complete image labels: settled image links gain hover previews, and standalone images gain lightbox activation. Image-only anchors retain their single navigation target. The parser recovers only complete, unescaped, standalone local image references with bare spaces and an unambiguous supported image suffix; code and ambiguous destinations remain literal.
100
+
98
101
  ### Localizing copy
99
102
 
100
103
  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.
package/README.zh.md CHANGED
@@ -49,11 +49,12 @@ kind: "package-library"
49
49
  | `StateDot` | 10px 槽内的绿色 `done`、琥珀色 `warning`、红色 `error`、中性灰色 `idle` 圆点,以及 tertiary 灰色 14px 旋转 `ongoing` loading,其动画固定到文档时间零点,所以所有可见 loading 同相旋转。它是 `aria-hidden` 的,名称由渲染点提供。 `appearance="step"` 以实心勾表示完成、空心圆表示等待。 |
50
50
  | `ConnectionIndicator` | 行内连接恢复控件,覆盖断线、重试与已恢复三种状态。 |
51
51
  | `DisclosureRow` | 24px 紧凑折叠行,标题与内容左右排列。使用浅层 prop 比较进行 memo;内容未变时,保持回调与 React 节点 prop 的引用稳定。 |
52
- | `Modal` | 页面遮罩之上的居中对话框。 |
52
+ | `Modal` | 页面遮罩之上的居中对话框。嵌套对话框可通过 `onKeyDownCapture` 在文档级 Escape 处理器之前拦截按键。 |
53
53
  | `RiskConfirmation` | 以显式复选框把关的敏感操作确认。 |
54
54
  | `OnboardingSurface` | 首次运行的引导舞台,期间保持应用根节点 inert。 |
55
- | `Tooltip` | 锚定在克隆子元素上的悬停文本;可通过 `portal` 渲染到外层,避免被容器裁剪。 |
55
+ | `Tooltip` | 锚定在克隆子元素上的悬停文本;可通过 `portal` 渲染到外层,避免被容器裁剪,或受祖先层叠上下文限制其 z-index。 |
56
56
  | `HoverCard` | 指针可停留、可选中的悬停预览;可选带复制按钮。 |
57
+ | `ImageLightbox` | 共享图片浮层,支持焦点恢复与 Esc 关闭。 |
57
58
  | `Toast` | 顶部居中的瞬时横幅,保持时长由所有者的 `holdMs` 决定。 |
58
59
  | `SettingsForm`、`SettingsValueField`、`SettingsSecretField` | 插件设置页的框架与控件:框架以 `labels` 接收文案,只在按钮点击时保存,卸载即丢弃;值字段显示暂存文本以及已覆盖标签和重置;密文字段每次为空,只报告是否已配置。 |
59
60
  | `SettingsFormModel`、`settingsNumberField`、`settingsTextField` | 这类页面背后基于设置 scope 的暂存编辑模型:草稿先暂存、保存时写入,字段是否被覆盖看用户层是否含有它,未落地的保存保留草稿。 |
@@ -85,7 +86,7 @@ kind: "package-library"
85
86
 
86
87
  最近的 `MarkdownDelegateProvider` 提供可选的 `openExternalLink` 和 `openFile` 导航回调。嵌套 Provider 替换外层能力,回调变化无需重新构建 Markdown 即可到达已渲染链接。其 `openFile` 使本地 Markdown 链接在落定后可点击。绝对路径和工作区相对路径支持百分号转义以及 `#L24` / `#L24-L30` 片段;范围定位到起始行。文件名中的字面 `?` 和 `#` 必须百分号编码。悬停提示使用解码后的路径,并在标签为空时提供可访问名称。回调接收解码后的路径和可选行号,渲染器保留标签并显示文件图标。不传回调时,本地链接仍为文本。URL 协议、查询串、不支持的片段及格式错误的目标不会传给文件打开器。
87
88
 
88
- `MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。外层 `MarkdownDelegateProvider` 会接收普通点击产生的已净化 HTTP(S) URL;带修饰键的点击和 Provider 外的链接保留原生外部 anchor 行为。当 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,供消息气泡和排队行使用。引用名称继承调用方的换行规则:长名称在气泡内换行,排队预览保持单行布局。传入 `UserTextReferences` 时,文件和 skill 引用成为支持键盘操作的预览按钮,复用正文文件链接的悬停和聚焦样式;第一次指针点击可以打开预览,后续点击和已有选区保留原生选择行为。键盘激活在存在选区时仍可打开预览。
89
+ `MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。外层 `MarkdownDelegateProvider` 会接收普通点击产生的已净化 HTTP(S) URL;带修饰键的点击和 Provider 外的链接保留原生外部 anchor 行为。当 owner 传入 `pathImages` 词表时,本地媒体路径的图片目标只在落定渲染阶段重写为可展示 URL(与 file mentions 相同的流式门);不传词表时本地目标保持惰性 alt 文本。加载或解码失败后,图片替换为作者的 alt 文本;alt 为空时显示原始目标路径;`fileImages` 还提供本地化的失败提示前缀。图片源变化后可重新加载。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`projectUserText` 把已发送的用户文本投影为行内普通文本段与引用 chip,供消息气泡和排队行使用。引用名称继承调用方的换行规则:长名称在气泡内换行,排队预览保持单行布局。传入 `UserTextReferences` 时,文件和 skill 引用成为支持键盘操作的预览按钮,复用正文文件链接的悬停和聚焦样式;第一次指针点击可以打开预览,后续点击和已有选区保留原生选择行为。键盘激活在存在选区时仍可打开预览。
89
90
 
90
91
  `MarkdownText` 默认为 `variant="body"`。次级内容使用 `variant="compact"`:其 13px 字号与 20px 行高跟随内容字号设置,各级标题保持同一字号并使用 600 字重,段落与列表采用更紧凑的间距。正文、链接和代码均保持 tertiary 颜色,以点状下划线区分链接。代码标题栏随代码块滚动。表格和公式仍然启用,使用周围文字的字号,并在可用宽度内横向滚动。两个变体共享解析器与流式缓存。
91
92
 
@@ -95,6 +96,8 @@ kind: "package-library"
95
96
 
96
97
  `JsonTree` 把折叠字符串限制为 `collapsedStringLines` 行(默认三行)。展开后显示原始文本、保留同级逗号,并限制在窗口与外层滚动容器内;尺寸变化和祖先滚动事件会更新此限制。行复制反馈独立于 JSON 值渲染更新;尚未完成的剪贴板写入不会更新另一行或已卸载的树。
97
98
 
99
+ `ImageLightbox` 是共享原图浮层,支持焦点恢复与 Esc 关闭。包内缩略图渲染器由调用方提供加载及失败文案。`HoverCard.inline` 使文件链接保持在文本流内,并使用共享菜单材质、键盘可见焦点,并在锚点上方或下方定位而不遮挡锚点。即使焦点位于其他位置,Esc 也会关闭已打开的缩略图;后续 Esc 按键继续传给 owner。`MarkdownDelegateProvider.fileImages` 提供已解码路径解析器与完整图片文案:消息落定后的图片链接支持悬停预览,独立图片支持点击放大。仅包含图片的链接保留单一导航目标。解析器仅恢复完整、未转义、独立成段且带明确图片扩展名的含裸空格本地图片引用;代码与有歧义的目标保持原文。
100
+
98
101
  ### 本地化文案
99
102
 
100
103
  这些原子组件无法读取应用 locale,因此每段面向用户的文案都必须通过 label prop 提供。`HoverCard`、`TerminalBlock`、`JsonTree`、`CodeBlock`、`MarkdownText`、`JsonBlock`、`ConnectionIndicator`、`Modal`、`DiffBlock`、`ReadBlock`、`SearchBlock` 与 `WebBlock` 接收完整的本地化 label。本包不拥有语言回退;遗漏会导致类型检查失败,各功能会把带类型的 `t` 席位映射到 primitive 的 label 接口。
@@ -79,3 +79,16 @@
79
79
  @media (prefers-reduced-motion: reduce) {
80
80
  .preview { transition: none; }
81
81
  }
82
+
83
+ .inline { display: inline; }
84
+ .media {
85
+ --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
86
+ --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
87
+ overflow: auto;
88
+ padding: 8px;
89
+ border: 0;
90
+ color: var(--dsw-alias-label-primary);
91
+ background: var(--dsw-specific-menu);
92
+ backdrop-filter: var(--dsw-menu-backdrop-filter);
93
+ box-shadow: var(--dsw-elevation-panel);
94
+ }
@@ -0,0 +1,48 @@
1
+ .backdrop {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 1000;
5
+ display: grid;
6
+ place-items: center;
7
+ padding: 40px;
8
+ }
9
+
10
+ /* Same mask recipe as the Modal primitive and the settings dialog. A separate
11
+ layer, not a background on .backdrop: backdrop-filter there would blur the
12
+ previewed image and the close control along with the page. */
13
+ .mask {
14
+ position: absolute;
15
+ inset: 0;
16
+ background: var(--dsw-alias-bg-mask-1);
17
+ backdrop-filter: var(--dsw-mask-blur);
18
+ }
19
+
20
+ .image {
21
+ position: relative;
22
+ max-width: min(100%, 1600px);
23
+ max-height: calc(100vh - 80px);
24
+ object-fit: contain;
25
+ border-radius: 12px;
26
+ background: var(--dsw-specific-input-major);
27
+ border: 0;
28
+ box-shadow: var(--dsw-elevation-prominent);
29
+ }
30
+
31
+ .close {
32
+ position: fixed;
33
+ top: 20px;
34
+ right: 20px;
35
+ z-index: 1;
36
+ display: grid;
37
+ place-items: center;
38
+ width: 36px;
39
+ height: 36px;
40
+ border: 0.5px solid var(--dsw-alias-border-l2-darkmode-thin);
41
+ border-radius: 999px;
42
+ corner-shape: round;
43
+ background: var(--dsw-specific-input-major);
44
+ color: var(--dsw-alias-label-primary);
45
+ cursor: pointer;
46
+ }
47
+
48
+ .close:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 3px; }
@@ -0,0 +1,24 @@
1
+ .frame {
2
+ position: relative;
3
+ display: grid;
4
+ place-items: center;
5
+ width: 100%;
6
+ height: 180px;
7
+ overflow: hidden;
8
+ border-radius: 8px;
9
+ background: var(--dsw-alias-bg-base);
10
+ }
11
+ .image { width: 100%; height: 100%; min-height: 0; object-fit: contain; opacity: 0; }
12
+ .image[data-ready] { opacity: 1; }
13
+ .status {
14
+ position: absolute;
15
+ inset: 0;
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ gap: 8px;
20
+ padding: 12px;
21
+ color: var(--dsw-alias-label-tertiary);
22
+ font-size: 12px;
23
+ line-height: 18px;
24
+ }
package/lib/index.js CHANGED
@@ -56,9 +56,11 @@ import { factorySpace } from "micromark-factory-space";
56
56
  import { normalizeUri } from "micromark-util-sanitize-uri";
57
57
  import css$29 from "./markdown/CodeBlock.module.css";
58
58
  import katex from "katex";
59
+ import css$30 from "./ImageLightbox.module.css";
60
+ import css$31 from "./ImagePreview.module.css";
59
61
  import "katex/dist/katex.min.css";
60
- import css$30 from "./WebBlock.module.css";
61
- import css$31 from "./markdown/JsonBlock.module.css";
62
+ import css$32 from "./WebBlock.module.css";
63
+ import css$33 from "./markdown/JsonBlock.module.css";
62
64
  //#region lib/types/icons/shared-artwork.js
63
65
  /**
64
66
  * Render shared new-conversation geometry for product and reference icons.
@@ -1243,10 +1245,10 @@ const IconUserOutlineArtwork = ({ size = 16, className, strokeWidth }) => jsxs("
1243
1245
  "aria-hidden": "true",
1244
1246
  strokeWidth,
1245
1247
  children: [jsx("path", {
1246
- d: "M8 8.5C9.65685 8.5 11 7.15685 11 5.5C11 3.84315 9.65685 2.5 8 2.5C6.34315 2.5 5 3.84315 5 5.5C5 7.15685 6.34315 8.5 8 8.5Z",
1248
+ d: "M8 8.25C9.51878 8.25 10.75 7.01878 10.75 5.5C10.75 3.98122 9.51878 2.75 8 2.75C6.48122 2.75 5.25 3.98122 5.25 5.5C5.25 7.01878 6.48122 8.25 8 8.25Z",
1247
1249
  stroke: "currentColor"
1248
1250
  }), jsx("path", {
1249
- d: "M1.5 14.5C1.5 11.25 4.25 10 8 10C11.75 10 14.5 11.25 14.5 14.5",
1251
+ d: "M2.5 14.5C2.5 11.5 5.25 10.25 8 10.25C10.75 10.25 13.5 11.5 13.5 14.5",
1250
1252
  stroke: "currentColor"
1251
1253
  })]
1252
1254
  });
@@ -1260,6 +1262,44 @@ const IconUserOutlineMedium = (props) => jsx(IconUserOutlineArtwork, {
1260
1262
  ...props,
1261
1263
  strokeWidth: ICON_MEDIUM_STROKE
1262
1264
  });
1265
+ const IconUsersOutlineArtwork = ({ size = 16, className, strokeWidth }) => jsxs("svg", {
1266
+ width: size,
1267
+ height: size,
1268
+ className,
1269
+ viewBox: "0 0 16 16",
1270
+ fill: "none",
1271
+ xmlns: "http://www.w3.org/2000/svg",
1272
+ "aria-hidden": "true",
1273
+ strokeWidth,
1274
+ children: [
1275
+ jsx("path", {
1276
+ d: "M6 8.25C7.51878 8.25 8.75 7.01878 8.75 5.5C8.75 3.98122 7.51878 2.75 6 2.75C4.48122 2.75 3.25 3.98122 3.25 5.5C3.25 7.01878 4.48122 8.25 6 8.25Z",
1277
+ stroke: "currentColor"
1278
+ }),
1279
+ jsx("path", {
1280
+ d: "M1 14.5C1 11.5 3.5 10.25 6 10.25C8.5 10.25 11 11.5 11 14.5",
1281
+ stroke: "currentColor"
1282
+ }),
1283
+ jsx("path", {
1284
+ d: "M10.5 2.9C11.65 3.35 12.45 4.35 12.45 5.5C12.45 6.65 11.65 7.65 10.5 8.1",
1285
+ stroke: "currentColor"
1286
+ }),
1287
+ jsx("path", {
1288
+ d: "M12.4 10.6C13.9 11.3 15 12.6 15 14.5",
1289
+ stroke: "currentColor"
1290
+ })
1291
+ ]
1292
+ });
1293
+ /** Regular one-pixel IconUsersOutline artwork: a front person with a second person behind. */
1294
+ const IconUsersOutlineRegular = (props) => jsx(IconUsersOutlineArtwork, {
1295
+ ...props,
1296
+ strokeWidth: 1
1297
+ });
1298
+ /** Medium IconUsersOutline artwork with a 1.3px stroke. */
1299
+ const IconUsersOutlineMedium = (props) => jsx(IconUsersOutlineArtwork, {
1300
+ ...props,
1301
+ strokeWidth: ICON_MEDIUM_STROKE
1302
+ });
1263
1303
  const IconPaperPlaneOutlineArtwork = ({ size = 14, className, strokeWidth }) => jsxs("svg", {
1264
1304
  width: size,
1265
1305
  height: size,
@@ -4023,6 +4063,7 @@ async function writeClipboard(text) {
4023
4063
  const PREVIEW_FADE_MS = 100;
4024
4064
  const PREVIEW_MAX_HEIGHT = 420;
4025
4065
  const PREVIEW_INSET = 24;
4066
+ const INLINE_PREVIEW_WIDTH = 300;
4026
4067
  const ANCHOR_GAP = 8;
4027
4068
  const VIEWPORT_MARGIN = 8;
4028
4069
  /**
@@ -4034,6 +4075,7 @@ const VIEWPORT_MARGIN = 8;
4034
4075
  * @param props.variant - compact card beside the anchor, or a preview above/below it
4035
4076
  * with 24px side insets, a 420px height cap, frame-top clearance, and 100ms opacity transitions.
4036
4077
  * @param props.widthAnchorRef - optional element whose width and horizontal position size the preview.
4078
+ * @param props.inline - keep the anchor in prose; show a contained preview on hover or keyboard focus.
4037
4079
  * @param props.disabled - suppress opening; turning true dismisses an open card.
4038
4080
  * @param props.copyText - optional primary value copied by activation and
4039
4081
  * included in the card's accessible name.
@@ -4041,7 +4083,7 @@ const VIEWPORT_MARGIN = 8;
4041
4083
  * @param props.copiedLabel - localized visible success label.
4042
4084
  * @returns anchor wrapper with the conditional portaled card.
4043
4085
  */
4044
- function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyText, copyLabel, copiedLabel, variant = "compact", widthAnchorRef }) {
4086
+ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyText, copyLabel, copiedLabel, variant = "compact", widthAnchorRef, inline = false }) {
4045
4087
  const rootRef = useRef(null);
4046
4088
  const cardRef = useRef(null);
4047
4089
  const timerRef = useRef(null);
@@ -4108,19 +4150,22 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
4108
4150
  };
4109
4151
  }, []);
4110
4152
  useEffect(() => {
4111
- if (!open || variant !== "preview") return;
4153
+ if (!open || variant !== "preview" && !inline) return;
4112
4154
  const dismiss = (event) => {
4113
4155
  if (event.key !== "Escape") return;
4156
+ if (inline) event.stopPropagation();
4157
+ clearTimer();
4114
4158
  cancelClose();
4115
4159
  close();
4116
4160
  };
4117
- window.addEventListener("keydown", dismiss);
4161
+ window.addEventListener("keydown", dismiss, inline);
4118
4162
  return () => {
4119
- window.removeEventListener("keydown", dismiss);
4163
+ window.removeEventListener("keydown", dismiss, inline);
4120
4164
  };
4121
4165
  }, [
4122
4166
  open,
4123
4167
  variant,
4168
+ inline,
4124
4169
  cancelClose,
4125
4170
  close
4126
4171
  ]);
@@ -4152,6 +4197,23 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
4152
4197
  });
4153
4198
  return;
4154
4199
  }
4200
+ if (inline) {
4201
+ const height = Math.max(h, cardRef.current?.scrollHeight ?? 0);
4202
+ const width = Math.max(0, Math.min(INLINE_PREVIEW_WIDTH, window.innerWidth - VIEWPORT_MARGIN * 2));
4203
+ const topMargin = overlayTopMargin(VIEWPORT_MARGIN);
4204
+ const belowTop = Math.max(topMargin, r.bottom + ANCHOR_GAP);
4205
+ const above = Math.max(0, r.top - ANCHOR_GAP - topMargin);
4206
+ const below = Math.max(0, window.innerHeight - belowTop - VIEWPORT_MARGIN);
4207
+ const onTop = height > below && above > below;
4208
+ const maxHeight = onTop ? above : below;
4209
+ setPos({
4210
+ left: Math.max(VIEWPORT_MARGIN, Math.min(r.left, window.innerWidth - width - VIEWPORT_MARGIN)),
4211
+ top: onTop ? r.top - ANCHOR_GAP - Math.min(height, maxHeight) : belowTop,
4212
+ width,
4213
+ maxHeight
4214
+ });
4215
+ return;
4216
+ }
4155
4217
  const top = r.top + h > window.innerHeight - VIEWPORT_MARGIN ? window.innerHeight - h - VIEWPORT_MARGIN : r.top;
4156
4218
  setPos({
4157
4219
  left: r.right + ANCHOR_GAP,
@@ -4159,7 +4221,7 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
4159
4221
  });
4160
4222
  };
4161
4223
  place();
4162
- const observer = variant === "preview" && typeof ResizeObserver !== "undefined" ? new ResizeObserver(place) : null;
4224
+ const observer = (variant === "preview" || inline) && typeof ResizeObserver !== "undefined" ? new ResizeObserver(place) : null;
4163
4225
  for (const element of [
4164
4226
  cardRef.current,
4165
4227
  rootRef.current,
@@ -4176,10 +4238,11 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
4176
4238
  open,
4177
4239
  variant,
4178
4240
  widthAnchorRef,
4179
- positioned
4241
+ positioned,
4242
+ inline
4180
4243
  ]);
4181
4244
  useLayoutEffect(() => {
4182
- if (!open || pos === null || variant === "preview") return;
4245
+ if (!open || pos === null || variant === "preview" || inline) return;
4183
4246
  /* v8 ignore next -- the card is mounted whenever pos is set, so the ref is attached here. */
4184
4247
  const h = cardRef.current?.offsetHeight ?? 0;
4185
4248
  if (pos.top + h > window.innerHeight - VIEWPORT_MARGIN) setPos({
@@ -4189,7 +4252,8 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
4189
4252
  }, [
4190
4253
  open,
4191
4254
  pos,
4192
- variant
4255
+ variant,
4256
+ inline
4193
4257
  ]);
4194
4258
  const copy = async (text) => {
4195
4259
  if (copied || copyingRef.current) return;
@@ -4213,7 +4277,7 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
4213
4277
  };
4214
4278
  const card = open && pos !== null && jsx("div", {
4215
4279
  ref: cardRef,
4216
- className: clsx(css$13.card, variant === "preview" && css$13.preview, copyable && css$13.copyable, copied && css$13.feedback),
4280
+ className: clsx(css$13.card, variant === "preview" && css$13.preview, inline && css$13.media, copyable && css$13.copyable, copied && css$13.feedback),
4217
4281
  "data-closing": closing || void 0,
4218
4282
  style: {
4219
4283
  ...pos,
@@ -4243,9 +4307,22 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
4243
4307
  });
4244
4308
  return jsxs("span", {
4245
4309
  ref: rootRef,
4246
- className: css$13.root,
4247
- onPointerEnter: () => {
4248
- if (disabled) return;
4310
+ className: clsx(css$13.root, inline && css$13.inline),
4311
+ onFocus: inline ? (event) => {
4312
+ if (!disabled && event.target.matches(":focus-visible")) {
4313
+ cancelClose();
4314
+ setPhase("open");
4315
+ }
4316
+ } : void 0,
4317
+ onBlur: inline ? (event) => {
4318
+ if (!event.currentTarget.contains(event.relatedTarget)) {
4319
+ clearTimer();
4320
+ cancelClose();
4321
+ close();
4322
+ }
4323
+ } : void 0,
4324
+ onPointerEnter: (event) => {
4325
+ if (disabled || inline && event.pointerType === "touch") return;
4249
4326
  cancelClose();
4250
4327
  if (open) {
4251
4328
  setPhase("open");
@@ -4288,9 +4365,10 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
4288
4365
  * @param props.contentClassName - optional class for a scrollable content region.
4289
4366
  * @param props.headless - render children directly in the card (no default
4290
4367
  * header/close/body chrome); mask, card, Escape, and aria-label remain.
4368
+ * @param props.onKeyDownCapture - handle a nested dialog's keys before the document Escape listeners.
4291
4369
  * @returns null when closed; otherwise the overlay tree.
4292
4370
  */
4293
- function Modal({ open, onClose, title, closeLabel, description, children, footer, className, contentClassName, headless = false }) {
4371
+ function Modal({ open, onClose, title, closeLabel, description, children, footer, className, contentClassName, onKeyDownCapture, headless = false }) {
4294
4372
  useEffect(() => {
4295
4373
  if (!open) return;
4296
4374
  const onKeyDown = (e) => {
@@ -4305,6 +4383,7 @@ function Modal({ open, onClose, title, closeLabel, description, children, footer
4305
4383
  return createPortal(jsxs("div", {
4306
4384
  className: css$14.root,
4307
4385
  role: "presentation",
4386
+ onKeyDownCapture,
4308
4387
  children: [jsx("div", {
4309
4388
  className: css$14.mask,
4310
4389
  "aria-hidden": "true",
@@ -5896,7 +5975,7 @@ function projectUserText(text, sessionLabels, slashNames = [], slashKind = "skil
5896
5975
  }
5897
5976
  //#endregion
5898
5977
  //#region lib/types/Tooltip.js
5899
- /** Anchor-preserving tooltips with optional body portals for clipping containers. */
5978
+ /** Anchor-preserving tooltips; an optional body portal escapes clipping containers and stacking contexts that cap the bubble's z-index. */
5900
5979
  /**
5901
5980
  * Suppression channel from a tooltip to the tooltips above it: a tooltip hands
5902
5981
  * this setter to its own descendants, and a visible descendant bubble calls it
@@ -5919,8 +5998,11 @@ if (typeof window !== "undefined") {
5919
5998
  * @param props.align - horizontal anchor-edge alignment for 'bottom'/'top' bubbles: 'end' pins
5920
5999
  * the bubble's right edge to the anchor's (for anchors beside other hover surfaces the centered
5921
6000
  * bubble would overlap); default 'center'. Ignored for side 'right'.
5922
- * @param props.portal - render the bubble under document.body to escape containing blocks and clipping ancestors.
6001
+ * @param props.portal - render the bubble under document.body, so an ancestor's clipping or its
6002
+ * stacking context (which confines the bubble's z-index to that context) cannot hide it.
5923
6003
  * @param props.delayMs - hover delay in milliseconds; keyboard focus remains immediate.
6004
+ * @param props.gap - anchor-to-bubble distance in pixels for 'bottom'/'top' bubbles (default 8);
6005
+ * ignored for side 'right'.
5924
6006
  * @param props.disabled - suppress the bubble while true; the anchor renders identically so
5925
6007
  * toggling never remounts it (which would cut its CSS transitions).
5926
6008
  * @param props.maxWidth - bubble width cap in pixels, for labels long enough that the default
@@ -5931,7 +6013,7 @@ if (typeof window !== "undefined") {
5931
6013
  * anchor dismisses the bubble until the next trigger, and focus arriving after a pointer
5932
6014
  * interaction (a closing menu refocusing its trigger) never raises it.
5933
6015
  */
5934
- function Tooltip({ label, side = "right", align = "center", delayMs = 0, disabled = false, portal = false, maxWidth, children }) {
6016
+ function Tooltip({ label, side = "right", align = "center", delayMs = 0, gap = 8, disabled = false, portal = false, maxWidth, children }) {
5935
6017
  const anchor = useRef(null);
5936
6018
  const childRef = children.ref;
5937
6019
  const mergedRef = useCallback((el) => {
@@ -5942,7 +6024,7 @@ function Tooltip({ label, side = "right", align = "center", delayMs = 0, disable
5942
6024
  const [pos, setPos] = useState(null);
5943
6025
  const bubble = useRef(null);
5944
6026
  const resolvedLabel = pos === null ? null : typeof label === "function" ? label() : label;
5945
- const y = pos === null ? 0 : side === "right" ? pos.top + (pos.bottom - pos.top) / 2 : side === "top" ? pos.top - 8 : pos.bottom + 8;
6027
+ const y = pos === null ? 0 : side === "right" ? pos.top + (pos.bottom - pos.top) / 2 : side === "top" ? pos.top - gap : pos.bottom + gap;
5946
6028
  const showTimer = useRef(null);
5947
6029
  const triggers = useRef({
5948
6030
  hover: false,
@@ -5965,12 +6047,12 @@ function Tooltip({ label, side = "right", align = "center", delayMs = 0, disable
5965
6047
  const { inlineSize: width, blockSize: height } = size;
5966
6048
  const offset = side === "right" ? 0 : align === "end" ? width : width / 2;
5967
6049
  const left = Math.max(edgeMargin, Math.min(pos.x - offset, window.innerWidth - edgeMargin - width));
5968
- const fitsBelow = pos.bottom + 8 + height <= window.innerHeight - edgeMargin;
5969
- const fitsAbove = pos.top - 8 - height >= edgeMargin;
6050
+ const fitsBelow = pos.bottom + gap + height <= window.innerHeight - edgeMargin;
6051
+ const fitsAbove = pos.top - gap - height >= edgeMargin;
5970
6052
  if (placement === "bottom" && !fitsBelow && fitsAbove) placement = "top";
5971
6053
  else if (placement === "top" && !fitsAbove && fitsBelow) placement = "bottom";
5972
6054
  el.style.left = `${left + offset}px`;
5973
- el.style.top = `${placement === "right" ? (pos.top + pos.bottom) / 2 : placement === "top" ? pos.top - 8 : pos.bottom + 8}px`;
6055
+ el.style.top = `${placement === "right" ? (pos.top + pos.bottom) / 2 : placement === "top" ? pos.top - gap : pos.bottom + gap}px`;
5974
6056
  el.dataset.side = placement;
5975
6057
  el.style.visibility = "visible";
5976
6058
  };
@@ -5986,6 +6068,7 @@ function Tooltip({ label, side = "right", align = "center", delayMs = 0, disable
5986
6068
  };
5987
6069
  }, [
5988
6070
  align,
6071
+ gap,
5989
6072
  pos,
5990
6073
  side,
5991
6074
  suppressed,
@@ -9626,6 +9709,32 @@ var IncrementalMarkdownParser = class {
9626
9709
  }
9627
9710
  };
9628
9711
  //#endregion
9712
+ //#region lib/types/markdown/local-image-syntax.js
9713
+ /**
9714
+ * Recover only unambiguous, unescaped image-only paragraphs containing a local path with spaces.
9715
+ * @param root - Parsed Markdown tree, modified in place.
9716
+ * @param source - Original source used to distinguish authored syntax from escaped examples.
9717
+ * @returns The same root with recovered image nodes.
9718
+ */
9719
+ function recoverLocalImages(root, source) {
9720
+ const visit = (node) => {
9721
+ if (node.type === "paragraph" && node.children.length === 1) {
9722
+ const [child] = node.children;
9723
+ if (child.type === "text" && child.position !== void 0 && source.slice(child.position.start.offset, child.position.end.offset) === child.value) {
9724
+ const match = /^!\[([^\]\n]*)\]\(((?:\/(?!\/)|\.{1,2}\/|[a-z]:[\\/])[^\n<>()[\]"']+\.[a-z\d]+)\)$/iu.exec(child.value);
9725
+ if (match !== null && match[2].includes(" ") && classifyFileType(match[2]) === "image") node.children = [{
9726
+ type: "image",
9727
+ alt: match[1],
9728
+ url: match[2],
9729
+ position: child.position
9730
+ }];
9731
+ }
9732
+ } else if ("children" in node) for (const child of node.children) visit(child);
9733
+ };
9734
+ visit(root);
9735
+ return root;
9736
+ }
9737
+ //#endregion
9629
9738
  //#region lib/types/markdown/cjkFriendlyStrong.js
9630
9739
  /** Let asterisk strong emphasis close after punctuation when CJK prose continues without whitespace. */
9631
9740
  const cjkCharacter = new RegExp([
@@ -9972,10 +10081,10 @@ function mathCompatibility() {
9972
10081
  * @returns The mdast root.
9973
10082
  */
9974
10083
  function parseGfm(text) {
9975
- return fromMarkdown(text, {
10084
+ return recoverLocalImages(fromMarkdown(text, {
9976
10085
  extensions: [gfm(), cjkFriendlyStrong()],
9977
10086
  mdastExtensions: [gfmFromMarkdown()]
9978
- });
10087
+ }), text);
9979
10088
  }
9980
10089
  /**
9981
10090
  * Parse GFM markdown plus TeX math with the compatibility delimiters
@@ -9984,7 +10093,7 @@ function parseGfm(text) {
9984
10093
  * @returns The mdast root.
9985
10094
  */
9986
10095
  function parseGfmWithMath(text) {
9987
- return fromMarkdown(text, {
10096
+ return recoverLocalImages(fromMarkdown(text, {
9988
10097
  extensions: [
9989
10098
  gfm(),
9990
10099
  cjkFriendlyStrong(),
@@ -9992,7 +10101,7 @@ function parseGfmWithMath(text) {
9992
10101
  math()
9993
10102
  ],
9994
10103
  mdastExtensions: [gfmFromMarkdown(), mathFromMarkdown()]
9995
- });
10104
+ }), text);
9996
10105
  }
9997
10106
  //#endregion
9998
10107
  //#region lib/types/markdown/CodeBlock.js
@@ -10292,11 +10401,16 @@ const MarkdownDelegateContext = createContext({});
10292
10401
  * @param props - Child tree and its file and HTTP(S) link handlers.
10293
10402
  * @returns the scoped child tree.
10294
10403
  */
10295
- function MarkdownDelegateProvider({ children, openExternalLink, openFile }) {
10404
+ function MarkdownDelegateProvider({ children, openExternalLink, openFile, fileImages }) {
10296
10405
  const delegate = useMemo(() => ({
10297
10406
  openExternalLink,
10298
- openFile
10299
- }), [openExternalLink, openFile]);
10407
+ openFile,
10408
+ fileImages
10409
+ }), [
10410
+ openExternalLink,
10411
+ openFile,
10412
+ fileImages
10413
+ ]);
10300
10414
  return jsx(MarkdownDelegateContext.Provider, {
10301
10415
  value: delegate,
10302
10416
  children
@@ -10310,6 +10424,111 @@ function useMarkdownDelegate() {
10310
10424
  return useContext(MarkdownDelegateContext);
10311
10425
  }
10312
10426
  //#endregion
10427
+ //#region lib/types/ImageLightbox.js
10428
+ /**
10429
+ * Document-level original-image preview opened by clicking a thumbnail.
10430
+ * Closes on Escape, backdrop press, or the close control, and restores focus
10431
+ * to the opener on unmount. Rendered through a body portal: an opener inside
10432
+ * a transformed or filtered ancestor would otherwise trap the fixed backdrop
10433
+ * in that ancestor's box instead of covering the viewport.
10434
+ *
10435
+ * @param props.src - the original image URL.
10436
+ * @param props.alt - the image's alt text.
10437
+ * @param props.labels - dialog and close-control strings.
10438
+ * @param props.onClose - dismiss callback owned by the opener.
10439
+ * @returns the modal preview dialog.
10440
+ */
10441
+ function ImageLightbox({ src, alt, labels, onClose }) {
10442
+ const closeRef = useRef(null);
10443
+ const restoreRef = useRef(null);
10444
+ useEffect(() => {
10445
+ restoreRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
10446
+ closeRef.current?.focus();
10447
+ const onKeyDown = (event) => {
10448
+ if (event.key === "Escape") {
10449
+ event.stopPropagation();
10450
+ onClose();
10451
+ }
10452
+ if (event.key === "Tab") {
10453
+ event.preventDefault();
10454
+ closeRef.current?.focus();
10455
+ }
10456
+ };
10457
+ window.addEventListener("keydown", onKeyDown, true);
10458
+ return () => {
10459
+ window.removeEventListener("keydown", onKeyDown, true);
10460
+ restoreRef.current?.focus();
10461
+ };
10462
+ }, [onClose]);
10463
+ return createPortal(jsxs("div", {
10464
+ className: css$30.backdrop,
10465
+ role: "dialog",
10466
+ "aria-modal": "true",
10467
+ "aria-label": labels.dialog,
10468
+ children: [
10469
+ jsx("div", {
10470
+ className: css$30.mask,
10471
+ "aria-hidden": "true",
10472
+ onMouseDown: onClose
10473
+ }),
10474
+ jsx("img", {
10475
+ className: css$30.image,
10476
+ src,
10477
+ alt
10478
+ }),
10479
+ jsx("button", {
10480
+ ref: closeRef,
10481
+ type: "button",
10482
+ className: css$30.close,
10483
+ "aria-label": labels.close,
10484
+ onClick: onClose,
10485
+ children: jsx(IconCloseOutlineRegular, { size: 16 })
10486
+ })
10487
+ ]
10488
+ }), document.body);
10489
+ }
10490
+ //#endregion
10491
+ //#region lib/types/ImagePreview.js
10492
+ /** Passive, contained image preview for Markdown image links. */
10493
+ /**
10494
+ * Render an image without introducing a second activation target.
10495
+ * @param props - Source, accessible description, localized status.
10496
+ * @returns A contained preview with loading or failure status.
10497
+ */
10498
+ function ImagePreview({ src, alt, loadingLabel, failedLabel }) {
10499
+ return jsx(Preview, {
10500
+ src,
10501
+ alt,
10502
+ loadingLabel,
10503
+ failedLabel
10504
+ }, src);
10505
+ }
10506
+ function Preview({ src, alt, loadingLabel, failedLabel }) {
10507
+ const [state, setState] = useState("loading");
10508
+ return jsxs("span", {
10509
+ className: css$31.frame,
10510
+ children: [state !== "failed" && jsx("img", {
10511
+ src,
10512
+ alt,
10513
+ loading: "lazy",
10514
+ decoding: "async",
10515
+ referrerPolicy: "no-referrer",
10516
+ className: css$31.image,
10517
+ "data-ready": state === "ready" || void 0,
10518
+ onLoad: () => {
10519
+ setState("ready");
10520
+ },
10521
+ onError: () => {
10522
+ setState("failed");
10523
+ }
10524
+ }), state !== "ready" && jsxs("span", {
10525
+ className: css$31.status,
10526
+ role: "status",
10527
+ children: [state === "loading" && jsx(IconLoadingOutlineRegular, { size: 16 }), jsx("span", { children: state === "loading" ? loadingLabel : failedLabel })]
10528
+ })]
10529
+ });
10530
+ }
10531
+ //#endregion
10313
10532
  //#region lib/types/markdown/render.js
10314
10533
  /**
10315
10534
  * Direct mdast→React markdown renderer. Replaces the react-markdown /
@@ -10631,12 +10850,14 @@ function renderAnchor(url, children, key, glyph = true, streaming = false) {
10631
10850
  return renderSafeLink(normalizeUri(url), children, key, glyph);
10632
10851
  }
10633
10852
  function MarkdownFileLink({ file, glyph, children }) {
10634
- const { openFile } = useMarkdownDelegate();
10853
+ const { openFile, fileImages } = useMarkdownDelegate();
10635
10854
  if (openFile === void 0) return jsx(Fragment, { children });
10636
- return jsxs("button", {
10855
+ const preview = glyph && classifyLinkPath(file.path) === "image" ? fileImages : void 0;
10856
+ const src = preview?.resolve(file.path);
10857
+ const anchor = jsxs("button", {
10637
10858
  type: "button",
10638
10859
  className: clsx(markdownCss.fileMention, markdownCss.fileLink),
10639
- title: file.path,
10860
+ title: src === void 0 ? file.path : void 0,
10640
10861
  onClick: () => {
10641
10862
  openFile(file.path, file.line === void 0 ? void 0 : { line: file.line });
10642
10863
  },
@@ -10645,6 +10866,20 @@ function MarkdownFileLink({ file, glyph, children }) {
10645
10866
  className: markdownCss.linkIcon
10646
10867
  }), children]
10647
10868
  });
10869
+ if (src === void 0 || preview === void 0) return anchor;
10870
+ return jsx(HoverCard, {
10871
+ inline: true,
10872
+ anchor,
10873
+ content: jsxs(Fragment, { children: [jsx(ImagePreview, {
10874
+ src,
10875
+ alt: file.path,
10876
+ loadingLabel: preview.labels.loading,
10877
+ failedLabel: preview.labels.failed
10878
+ }), jsx("span", {
10879
+ className: markdownCss.previewName,
10880
+ children: file.path.split(/[\\/]/u).pop()
10881
+ })] })
10882
+ });
10648
10883
  }
10649
10884
  /**
10650
10885
  * The complete inline-code value when it is exactly an absolute HTTP(S) URL
@@ -10660,25 +10895,40 @@ function inlineCodeHttpUrl(value) {
10660
10895
  }
10661
10896
  }
10662
10897
  function renderImage(url, alt, key, context) {
10663
- const imageSrc = imageSource(url, context.pathImages);
10664
- if (imageSrc === void 0) return jsx("span", {
10898
+ return jsx(MarkdownImage, {
10899
+ destination: url,
10900
+ alt,
10901
+ pathImages: context.pathImages,
10902
+ streaming: context.streaming,
10903
+ inLink: context.inLink === true
10904
+ }, `${key}:${url}`);
10905
+ }
10906
+ function MarkdownImage({ destination, alt, pathImages, streaming, inLink }) {
10907
+ const { fileImages } = useMarkdownDelegate();
10908
+ const file = streaming ? void 0 : parseFileLink(destination);
10909
+ const src = (file === void 0 ? void 0 : fileImages?.resolve(file.path)) ?? imageSource(destination, pathImages);
10910
+ if (src === void 0) return jsx("span", {
10665
10911
  className: markdownCss.imageAlt,
10666
10912
  children: alt
10667
- }, key);
10668
- return jsx(MarkdownImage, {
10669
- src: imageSrc,
10913
+ });
10914
+ return jsx(LoadedMarkdownImage, {
10915
+ src,
10670
10916
  alt,
10671
- destination: url
10672
- }, `${key}:${imageSrc}`);
10917
+ destination,
10918
+ preview: inLink ? void 0 : fileImages
10919
+ }, src);
10673
10920
  }
10674
- /** Failed loads retain the authored alt or destination; a new source remounts the image. */
10675
- function MarkdownImage({ src, alt, destination }) {
10921
+ function LoadedMarkdownImage({ src, alt, destination, preview }) {
10676
10922
  const [failed, setFailed] = useState(false);
10677
- if (failed) return jsx("span", {
10923
+ const [open, setOpen] = useState(false);
10924
+ const close = useCallback(() => {
10925
+ setOpen(false);
10926
+ }, []);
10927
+ if (failed) return jsxs("span", {
10678
10928
  className: markdownCss.imageAlt,
10679
- children: alt || destination
10929
+ children: [preview === void 0 ? "" : `${preview.labels.failed} · `, alt || destination]
10680
10930
  });
10681
- return jsx("img", {
10931
+ const img = jsx("img", {
10682
10932
  className: markdownCss.image,
10683
10933
  src,
10684
10934
  alt,
@@ -10689,6 +10939,22 @@ function MarkdownImage({ src, alt, destination }) {
10689
10939
  decoding: "async",
10690
10940
  referrerPolicy: "no-referrer"
10691
10941
  });
10942
+ if (preview === void 0) return img;
10943
+ return jsxs(Fragment, { children: [jsx("button", {
10944
+ type: "button",
10945
+ className: markdownCss.imageButton,
10946
+ title: preview.labels.open,
10947
+ "aria-label": alt ? `${preview.labels.open}: ${alt}` : preview.labels.open,
10948
+ onClick: () => {
10949
+ setOpen(true);
10950
+ },
10951
+ children: img
10952
+ }), open && jsx(ImageLightbox, {
10953
+ src,
10954
+ alt,
10955
+ labels: preview.labels,
10956
+ onClose: close
10957
+ })] });
10692
10958
  }
10693
10959
  /** The bracketed source text a reference reverts to when its definition is missing. */
10694
10960
  function referenceSuffix(node) {
@@ -10995,7 +11261,7 @@ function SafeLink({ url, label, className }) {
10995
11261
  children: [jsx(LinkIconMedium, {
10996
11262
  kind: "url",
10997
11263
  href,
10998
- className: css$30.linkIcon
11264
+ className: css$32.linkIcon
10999
11265
  }), label]
11000
11266
  });
11001
11267
  }
@@ -11010,20 +11276,20 @@ function SafeLink({ url, label, className }) {
11010
11276
  */
11011
11277
  function SourceItem({ source, ordinal }) {
11012
11278
  return jsxs("li", {
11013
- className: css$30.source,
11279
+ className: css$32.source,
11014
11280
  value: ordinal,
11015
11281
  children: [
11016
11282
  jsx(SafeLink, {
11017
11283
  url: source.url,
11018
11284
  label: linkLabel(source.url, source.title),
11019
- className: css$30.sourceLink
11285
+ className: css$32.sourceLink
11020
11286
  }),
11021
11287
  source.snippet !== void 0 && source.snippet !== "" && jsx("div", {
11022
- className: css$30.snippet,
11288
+ className: css$32.snippet,
11023
11289
  children: source.snippet
11024
11290
  }),
11025
11291
  source.publishedAt !== void 0 && source.publishedAt !== "" && jsx("div", {
11026
- className: css$30.published,
11292
+ className: css$32.published,
11027
11293
  children: source.publishedAt
11028
11294
  })
11029
11295
  ]
@@ -11038,28 +11304,28 @@ function SourceItem({ source, ordinal }) {
11038
11304
  function WebSearchBlock({ answer, sources, truncated, labels, className }) {
11039
11305
  const empty = (answer === void 0 || answer === "") && sources.length === 0;
11040
11306
  return jsxs("div", {
11041
- className: clsx(css$30.block, className),
11307
+ className: clsx(css$32.block, className),
11042
11308
  "data-web": "search",
11043
11309
  children: [
11044
11310
  answer !== void 0 && answer !== "" && jsx("div", {
11045
- className: css$30.answer,
11311
+ className: css$32.answer,
11046
11312
  children: jsx(MarkdownText, {
11047
11313
  text: answer,
11048
11314
  labels: labels.markdown
11049
11315
  })
11050
11316
  }),
11051
11317
  empty ? jsx("div", {
11052
- className: css$30.empty,
11318
+ className: css$32.empty,
11053
11319
  children: labels.noResults
11054
11320
  }) : jsx("ol", {
11055
- className: css$30.sources,
11321
+ className: css$32.sources,
11056
11322
  children: sources.map((source, index) => jsx(SourceItem, {
11057
11323
  source,
11058
11324
  ordinal: index + 1
11059
11325
  }, index))
11060
11326
  }),
11061
11327
  truncated && jsx("div", {
11062
- className: css$30.truncated,
11328
+ className: css$32.truncated,
11063
11329
  children: labels.sourcesTruncated
11064
11330
  })
11065
11331
  ]
@@ -11072,23 +11338,23 @@ function WebSearchBlock({ answer, sources, truncated, labels, className }) {
11072
11338
  */
11073
11339
  function WebFetchBlock({ url, statusCode, truncated, labels, className }) {
11074
11340
  return jsxs("div", {
11075
- className: clsx(css$30.block, css$30.fetch, className),
11341
+ className: clsx(css$32.block, css$32.fetch, className),
11076
11342
  "data-web": "fetch",
11077
11343
  children: [jsx(SafeLink, {
11078
11344
  url,
11079
11345
  label: url,
11080
- className: css$30.fetchUrl
11346
+ className: css$32.fetchUrl
11081
11347
  }), jsxs("div", {
11082
- className: css$30.fetchMeta,
11348
+ className: css$32.fetchMeta,
11083
11349
  children: [jsxs("span", {
11084
- className: css$30.status,
11350
+ className: css$32.status,
11085
11351
  children: [
11086
11352
  labels.http,
11087
11353
  " ",
11088
11354
  statusCode
11089
11355
  ]
11090
11356
  }), truncated && jsx("span", {
11091
- className: css$30.truncated,
11357
+ className: css$32.truncated,
11092
11358
  children: labels.contentTruncated
11093
11359
  })]
11094
11360
  })]
@@ -11122,10 +11388,10 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel }) {
11122
11388
  truncatedLabel
11123
11389
  ]);
11124
11390
  return jsxs("div", {
11125
- className: css$31.root,
11391
+ className: css$33.root,
11126
11392
  children: [jsxs("button", {
11127
11393
  type: "button",
11128
- className: css$31.toggle,
11394
+ className: css$33.toggle,
11129
11395
  onClick: () => {
11130
11396
  setOpen((v) => !v);
11131
11397
  },
@@ -11135,7 +11401,7 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel }) {
11135
11401
  label
11136
11402
  ]
11137
11403
  }), open && jsx("pre", {
11138
- className: css$31.body,
11404
+ className: css$33.body,
11139
11405
  children: body
11140
11406
  })]
11141
11407
  });
@@ -11411,6 +11677,6 @@ const PluginArtworkDefault = ({ size = 36, className }) => {
11411
11677
  });
11412
11678
  };
11413
11679
  //#endregion
11414
- export { BrandWordmark, Button, CODE_HIGHLIGHT_EXTENSIONS, Checkbox, CodeBlock, ConnectionIndicator, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DiffBlock, DisclosureRow, FISH_LOGO_PATH, FISH_LOGO_VIEWBOX, FileTypeIcon, FishLogo, HoverCard, ICON_MEDIUM_STROKE, ICON_REGULAR_STROKE, IconAgentPresetOutlineMedium, IconAgentPresetOutlineRegular, IconAlarmClockOutlineMedium, IconAlarmClockOutlineRegular, IconApiOutlineMedium, IconApiOutlineRegular, IconArchiveCheckOutlineMedium, IconArchiveCheckOutlineRegular, IconArchiveOutlineMedium, IconArchiveOutlineRegular, IconBranchOutlineMedium, IconBranchOutlineRegular, IconBrowseOutlineMedium, IconBrowseOutlineRegular, IconCheckCircleFillMedium, IconCheckCircleFillRegular, IconCheckCircleOutlineMedium, IconCheckCircleOutlineRegular, IconCheckOutlineMedium, IconCheckOutlineRegular, IconChecklistOutlineMedium, IconChecklistOutlineRegular, IconChevronDownOutlineMedium, IconChevronDownOutlineRegular, IconChevronLeftOutlineMedium, IconChevronLeftOutlineRegular, IconChevronRightOutlineMedium, IconChevronRightOutlineRegular, IconChevronUpOutlineMedium, IconChevronUpOutlineRegular, IconChevronsUpDownOutlineMedium, IconChevronsUpDownOutlineRegular, IconClockOutlineMedium, IconClockOutlineRegular, IconCloseCircleFillMedium, IconCloseCircleFillRegular, IconCloseFillMedium, IconCloseFillRegular, IconCloseOutlineMedium, IconCloseOutlineRegular, IconCodeOutlineMedium, IconCodeOutlineRegular, IconCompactOutlineMedium, IconCompactOutlineRegular, IconCompareSplitOutlineMedium, IconCompareSplitOutlineRegular, IconContextInjectionOutlineMedium, IconContextInjectionOutlineRegular, IconCopyOutlineMedium, IconCopyOutlineRegular, IconCordisPluginOutlineMedium, IconCordisPluginOutlineRegular, IconDarkOutlineMedium, IconDarkOutlineRegular, IconDataOutlineMedium, IconDataOutlineRegular, IconDatabaseOutlineMedium, IconDatabaseOutlineRegular, IconDeliverDocMedium, IconDeliverDocRegular, IconDislikeFillMedium, IconDislikeFillRegular, IconDislikeOutlineMedium, IconDislikeOutlineRegular, IconDownloadOutlineMedium, IconDownloadOutlineRegular, IconEditOutlineMedium, IconEditOutlineRegular, IconEllipsisOutlineMedium, IconEllipsisOutlineRegular, IconEnhanceOutlineMedium, IconEnhanceOutlineRegular, IconFlatListOutlineMedium, IconFlatListOutlineRegular, IconFolderCloseMedium, IconFolderCloseRegular, IconFolderOpenMedium, IconFolderOpenOutlineMedium, IconFolderOpenOutlineRegular, IconFolderOpenRegular, IconFollowsystemOutlineMedium, IconFollowsystemOutlineRegular, IconFullscreenOutlineMedium, IconFullscreenOutlineRegular, IconGaugeOutlineMedium, IconGaugeOutlineRegular, IconGlobeOutlineMedium, IconGlobeOutlineRegular, IconGoalOutlineMedium, IconGoalOutlineRegular, IconInfoOutlineMedium, IconInfoOutlineRegular, IconInspectOutlineMedium, IconInspectOutlineRegular, IconLightOutlineMedium, IconLightOutlineRegular, IconLikeFillMedium, IconLikeFillRegular, IconLikeOutlineMedium, IconLikeOutlineRegular, IconLinkOutlineMedium, IconLinkOutlineRegular, IconListPenOutlineMedium, IconListPenOutlineRegular, IconLoadingOutlineMedium, IconLoadingOutlineRegular, IconMicrophoneOutlineMedium, IconMicrophoneOutlineRegular, IconNewChatOutlineMedium, IconNewChatOutlineRegular, IconNowrapFillMedium, IconNowrapFillRegular, IconPanelLeftOutlineMedium, IconPanelLeftOutlineRegular, IconPaperPlaneOutlineMedium, IconPaperPlaneOutlineRegular, IconPaperclipOutlineMedium, IconPaperclipOutlineRegular, IconPauseOutlineMedium, IconPauseOutlineRegular, IconPersonalizationOutlineMedium, IconPersonalizationOutlineRegular, IconPinFillMedium, IconPinFillRegular, IconPinOutlineMedium, IconPinOutlineRegular, IconPlanOutlineMedium, IconPlanOutlineRegular, IconPlayOutlineMedium, IconPlayOutlineRegular, IconPluginPinwheelOutlineMedium, IconPluginPinwheelOutlineRegular, IconPlusOutlineMedium, IconPlusOutlineRegular, IconProjectAddOutlineMedium, IconProjectAddOutlineRegular, IconQuestionOutlineMedium, IconQuestionOutlineRegular, IconQueueOutlineMedium, IconQueueOutlineRegular, IconRefreshOutlineMedium, IconRefreshOutlineRegular, IconRightUpOutlineMedium, IconRightUpOutlineRegular, IconSearchOutlineMedium, IconSearchOutlineRegular, IconSendOutlineMedium, IconSendOutlineRegular, IconSettingsOutlineMedium, IconSettingsOutlineRegular, IconShareOutlineMedium, IconShareOutlineRegular, IconShieldOutlineMedium, IconShieldOutlineRegular, IconSkillOutlineMedium, IconSkillOutlineRegular, IconSlidersTwoOutlineMedium, IconSlidersTwoOutlineRegular, IconSparkleMedium, IconSparkleRegular, IconStopFillMedium, IconStopFillRegular, IconThinkOutlineMedium, IconThinkOutlineRegular, IconTrashOutlineMedium, IconTrashOutlineRegular, IconTreeCornerMedium, IconTreeCornerRegular, IconTriangleRightFillMedium, IconTriangleRightFillRegular, IconUnarchiveOutlineMedium, IconUnarchiveOutlineRegular, IconUserOutlineMedium, IconUserOutlineRegular, IconWarningOutlineMedium, IconWarningOutlineRegular, IconWarningTriangleOutlineMedium, IconWarningTriangleOutlineRegular, IconWorkspaceTreeOutlineMedium, IconWorkspaceTreeOutlineRegular, IconWrapFillMedium, IconWrapFillRegular, IconWrapLinesOutlineMedium, IconWrapLinesOutlineRegular, Input, JsonBlock, JsonTree, LinkIconMedium, LinkIconRegular, MarkdownDelegateProvider, MarkdownText, Menu, MenuItemButton, Modal, OnboardingSurface, PathLabel, PermissionIconFullAccessMedium, PermissionIconFullAccessRegular, PermissionIconReadOnlyMedium, PermissionIconReadOnlyRegular, PermissionIconWorkspaceWriteMedium, PermissionIconWorkspaceWriteRegular, Pill, PluginArtworkDefault, PluginArtworkLoop, PluginArtworkSearch, PluginArtworkSubagent, PluginArtworkTerminal, ReadBlock, ReferenceIconMedium, ReferenceIconRegular, RiskConfirmation, SHIELD_OUTLINE_PATH, SearchBlock, SegmentedControl, SegmentedTabs, SettingsForm, SettingsFormModel, SettingsSecretField, SettingsValueField, StateDot, Switch, Tag, TerminalBlock, TextShimmer, Toast, Tooltip, WebBlock, classifyFileType, classifyLinkPath, diffTotals, extractMarkdownPlainText, fileExtension, fileSizeText, isDarwinDesktop, languageForPath, projectUserText, rankByName, relativeTime, settingsNumberField, settingsTextField, useAnchoredMaxHeight, useAnchoredPosition, useCodeHighlighter, useDismissOnOutsidePointer, writeClipboard };
11680
+ export { BrandWordmark, Button, CODE_HIGHLIGHT_EXTENSIONS, Checkbox, CodeBlock, ConnectionIndicator, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DiffBlock, DisclosureRow, FISH_LOGO_PATH, FISH_LOGO_VIEWBOX, FileTypeIcon, FishLogo, HoverCard, ICON_MEDIUM_STROKE, ICON_REGULAR_STROKE, IconAgentPresetOutlineMedium, IconAgentPresetOutlineRegular, IconAlarmClockOutlineMedium, IconAlarmClockOutlineRegular, IconApiOutlineMedium, IconApiOutlineRegular, IconArchiveCheckOutlineMedium, IconArchiveCheckOutlineRegular, IconArchiveOutlineMedium, IconArchiveOutlineRegular, IconBranchOutlineMedium, IconBranchOutlineRegular, IconBrowseOutlineMedium, IconBrowseOutlineRegular, IconCheckCircleFillMedium, IconCheckCircleFillRegular, IconCheckCircleOutlineMedium, IconCheckCircleOutlineRegular, IconCheckOutlineMedium, IconCheckOutlineRegular, IconChecklistOutlineMedium, IconChecklistOutlineRegular, IconChevronDownOutlineMedium, IconChevronDownOutlineRegular, IconChevronLeftOutlineMedium, IconChevronLeftOutlineRegular, IconChevronRightOutlineMedium, IconChevronRightOutlineRegular, IconChevronUpOutlineMedium, IconChevronUpOutlineRegular, IconChevronsUpDownOutlineMedium, IconChevronsUpDownOutlineRegular, IconClockOutlineMedium, IconClockOutlineRegular, IconCloseCircleFillMedium, IconCloseCircleFillRegular, IconCloseFillMedium, IconCloseFillRegular, IconCloseOutlineMedium, IconCloseOutlineRegular, IconCodeOutlineMedium, IconCodeOutlineRegular, IconCompactOutlineMedium, IconCompactOutlineRegular, IconCompareSplitOutlineMedium, IconCompareSplitOutlineRegular, IconContextInjectionOutlineMedium, IconContextInjectionOutlineRegular, IconCopyOutlineMedium, IconCopyOutlineRegular, IconCordisPluginOutlineMedium, IconCordisPluginOutlineRegular, IconDarkOutlineMedium, IconDarkOutlineRegular, IconDataOutlineMedium, IconDataOutlineRegular, IconDatabaseOutlineMedium, IconDatabaseOutlineRegular, IconDeliverDocMedium, IconDeliverDocRegular, IconDislikeFillMedium, IconDislikeFillRegular, IconDislikeOutlineMedium, IconDislikeOutlineRegular, IconDownloadOutlineMedium, IconDownloadOutlineRegular, IconEditOutlineMedium, IconEditOutlineRegular, IconEllipsisOutlineMedium, IconEllipsisOutlineRegular, IconEnhanceOutlineMedium, IconEnhanceOutlineRegular, IconFlatListOutlineMedium, IconFlatListOutlineRegular, IconFolderCloseMedium, IconFolderCloseRegular, IconFolderOpenMedium, IconFolderOpenOutlineMedium, IconFolderOpenOutlineRegular, IconFolderOpenRegular, IconFollowsystemOutlineMedium, IconFollowsystemOutlineRegular, IconFullscreenOutlineMedium, IconFullscreenOutlineRegular, IconGaugeOutlineMedium, IconGaugeOutlineRegular, IconGlobeOutlineMedium, IconGlobeOutlineRegular, IconGoalOutlineMedium, IconGoalOutlineRegular, IconInfoOutlineMedium, IconInfoOutlineRegular, IconInspectOutlineMedium, IconInspectOutlineRegular, IconLightOutlineMedium, IconLightOutlineRegular, IconLikeFillMedium, IconLikeFillRegular, IconLikeOutlineMedium, IconLikeOutlineRegular, IconLinkOutlineMedium, IconLinkOutlineRegular, IconListPenOutlineMedium, IconListPenOutlineRegular, IconLoadingOutlineMedium, IconLoadingOutlineRegular, IconMicrophoneOutlineMedium, IconMicrophoneOutlineRegular, IconNewChatOutlineMedium, IconNewChatOutlineRegular, IconNowrapFillMedium, IconNowrapFillRegular, IconPanelLeftOutlineMedium, IconPanelLeftOutlineRegular, IconPaperPlaneOutlineMedium, IconPaperPlaneOutlineRegular, IconPaperclipOutlineMedium, IconPaperclipOutlineRegular, IconPauseOutlineMedium, IconPauseOutlineRegular, IconPersonalizationOutlineMedium, IconPersonalizationOutlineRegular, IconPinFillMedium, IconPinFillRegular, IconPinOutlineMedium, IconPinOutlineRegular, IconPlanOutlineMedium, IconPlanOutlineRegular, IconPlayOutlineMedium, IconPlayOutlineRegular, IconPluginPinwheelOutlineMedium, IconPluginPinwheelOutlineRegular, IconPlusOutlineMedium, IconPlusOutlineRegular, IconProjectAddOutlineMedium, IconProjectAddOutlineRegular, IconQuestionOutlineMedium, IconQuestionOutlineRegular, IconQueueOutlineMedium, IconQueueOutlineRegular, IconRefreshOutlineMedium, IconRefreshOutlineRegular, IconRightUpOutlineMedium, IconRightUpOutlineRegular, IconSearchOutlineMedium, IconSearchOutlineRegular, IconSendOutlineMedium, IconSendOutlineRegular, IconSettingsOutlineMedium, IconSettingsOutlineRegular, IconShareOutlineMedium, IconShareOutlineRegular, IconShieldOutlineMedium, IconShieldOutlineRegular, IconSkillOutlineMedium, IconSkillOutlineRegular, IconSlidersTwoOutlineMedium, IconSlidersTwoOutlineRegular, IconSparkleMedium, IconSparkleRegular, IconStopFillMedium, IconStopFillRegular, IconThinkOutlineMedium, IconThinkOutlineRegular, IconTrashOutlineMedium, IconTrashOutlineRegular, IconTreeCornerMedium, IconTreeCornerRegular, IconTriangleRightFillMedium, IconTriangleRightFillRegular, IconUnarchiveOutlineMedium, IconUnarchiveOutlineRegular, IconUserOutlineMedium, IconUserOutlineRegular, IconUsersOutlineMedium, IconUsersOutlineRegular, IconWarningOutlineMedium, IconWarningOutlineRegular, IconWarningTriangleOutlineMedium, IconWarningTriangleOutlineRegular, IconWorkspaceTreeOutlineMedium, IconWorkspaceTreeOutlineRegular, IconWrapFillMedium, IconWrapFillRegular, IconWrapLinesOutlineMedium, IconWrapLinesOutlineRegular, ImageLightbox, Input, JsonBlock, JsonTree, LinkIconMedium, LinkIconRegular, MarkdownDelegateProvider, MarkdownText, Menu, MenuItemButton, Modal, OnboardingSurface, PathLabel, PermissionIconFullAccessMedium, PermissionIconFullAccessRegular, PermissionIconReadOnlyMedium, PermissionIconReadOnlyRegular, PermissionIconWorkspaceWriteMedium, PermissionIconWorkspaceWriteRegular, Pill, PluginArtworkDefault, PluginArtworkLoop, PluginArtworkSearch, PluginArtworkSubagent, PluginArtworkTerminal, ReadBlock, ReferenceIconMedium, ReferenceIconRegular, RiskConfirmation, SHIELD_OUTLINE_PATH, SearchBlock, SegmentedControl, SegmentedTabs, SettingsForm, SettingsFormModel, SettingsSecretField, SettingsValueField, StateDot, Switch, Tag, TerminalBlock, TextShimmer, Toast, Tooltip, WebBlock, classifyFileType, classifyLinkPath, diffTotals, extractMarkdownPlainText, fileExtension, fileSizeText, isDarwinDesktop, languageForPath, projectUserText, rankByName, relativeTime, settingsNumberField, settingsTextField, useAnchoredMaxHeight, useAnchoredPosition, useCodeHighlighter, useDismissOnOutsidePointer, writeClipboard };
11415
11681
 
11416
11682
  //# sourceMappingURL=index.js.map
@@ -442,3 +442,26 @@
442
442
  .compact :global(.katex-display) {
443
443
  margin: 4px 0;
444
444
  }
445
+
446
+ .imageButton {
447
+ display: inline-block;
448
+ max-width: 100%;
449
+ padding: 0;
450
+ border: 0;
451
+ border-radius: 8px;
452
+ background: transparent;
453
+ vertical-align: middle;
454
+ cursor: zoom-in;
455
+ }
456
+ .imageButton .image { max-width: min(100%, 640px); max-height: 360px; }
457
+ .imageButton:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 3px; }
458
+ .previewName {
459
+ display: block;
460
+ margin-top: 8px;
461
+ overflow: hidden;
462
+ text-overflow: ellipsis;
463
+ white-space: nowrap;
464
+ color: var(--dsw-alias-label-secondary);
465
+ font-size: 12px;
466
+ line-height: 18px;
467
+ }
@@ -8,6 +8,7 @@ import type { ReactNode, RefObject } from 'react';
8
8
  * @param props.variant - compact card beside the anchor, or a preview above/below it
9
9
  * with 24px side insets, a 420px height cap, frame-top clearance, and 100ms opacity transitions.
10
10
  * @param props.widthAnchorRef - optional element whose width and horizontal position size the preview.
11
+ * @param props.inline - keep the anchor in prose; show a contained preview on hover or keyboard focus.
11
12
  * @param props.disabled - suppress opening; turning true dismisses an open card.
12
13
  * @param props.copyText - optional primary value copied by activation and
13
14
  * included in the card's accessible name.
@@ -15,8 +16,10 @@ import type { ReactNode, RefObject } from 'react';
15
16
  * @param props.copiedLabel - localized visible success label.
16
17
  * @returns anchor wrapper with the conditional portaled card.
17
18
  */
18
- export declare function HoverCard({ anchor, content, openDelayMs, disabled, copyText, copyLabel, copiedLabel, variant, widthAnchorRef, }: {
19
+ export declare function HoverCard({ anchor, content, openDelayMs, disabled, copyText, copyLabel, copiedLabel, variant, widthAnchorRef, inline, }: {
19
20
  anchor: ReactNode;
21
+ /** Inline media preview using the shared menu material and keyboard focus. */
22
+ inline?: boolean;
20
23
  content: ReactNode;
21
24
  openDelayMs?: number;
22
25
  disabled?: boolean;
@@ -0,0 +1,27 @@
1
+ /** Lightbox strings the owner resolves from its own locale namespace. */
2
+ export interface ImageLightboxLabels {
3
+ /** Accessible name of the preview dialog. */
4
+ dialog: string;
5
+ /** Accessible label of the close control. */
6
+ close: string;
7
+ }
8
+ /**
9
+ * Document-level original-image preview opened by clicking a thumbnail.
10
+ * Closes on Escape, backdrop press, or the close control, and restores focus
11
+ * to the opener on unmount. Rendered through a body portal: an opener inside
12
+ * a transformed or filtered ancestor would otherwise trap the fixed backdrop
13
+ * in that ancestor's box instead of covering the viewport.
14
+ *
15
+ * @param props.src - the original image URL.
16
+ * @param props.alt - the image's alt text.
17
+ * @param props.labels - dialog and close-control strings.
18
+ * @param props.onClose - dismiss callback owned by the opener.
19
+ * @returns the modal preview dialog.
20
+ */
21
+ export declare function ImageLightbox({ src, alt, labels, onClose }: {
22
+ src: string;
23
+ alt: string;
24
+ labels: ImageLightboxLabels;
25
+ onClose: () => void;
26
+ }): import("react").ReactPortal;
27
+ //# sourceMappingURL=ImageLightbox.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Render an image without introducing a second activation target.
3
+ * @param props - Source, accessible description, localized status.
4
+ * @returns A contained preview with loading or failure status.
5
+ */
6
+ export declare function ImagePreview({ src, alt, loadingLabel, failedLabel }: {
7
+ src: string;
8
+ alt: string;
9
+ loadingLabel: string;
10
+ failedLabel: string;
11
+ }): import("react").JSX.Element;
12
+ //# sourceMappingURL=ImagePreview.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from 'react';
1
+ import type { KeyboardEventHandler, ReactNode } from 'react';
2
2
  interface ModalBaseProps {
3
3
  open: boolean;
4
4
  onClose: () => void;
@@ -8,6 +8,7 @@ interface ModalBaseProps {
8
8
  footer?: ReactNode;
9
9
  className?: string;
10
10
  contentClassName?: string;
11
+ onKeyDownCapture?: KeyboardEventHandler<HTMLDivElement>;
11
12
  }
12
13
  type ModalProps = ModalBaseProps & ({
13
14
  headless: true;
@@ -29,8 +30,9 @@ type ModalProps = ModalBaseProps & ({
29
30
  * @param props.contentClassName - optional class for a scrollable content region.
30
31
  * @param props.headless - render children directly in the card (no default
31
32
  * header/close/body chrome); mask, card, Escape, and aria-label remain.
33
+ * @param props.onKeyDownCapture - handle a nested dialog's keys before the document Escape listeners.
32
34
  * @returns null when closed; otherwise the overlay tree.
33
35
  */
34
- export declare function Modal({ open, onClose, title, closeLabel, description, children, footer, className, contentClassName, headless, }: ModalProps): import("react").ReactPortal | null;
36
+ export declare function Modal({ open, onClose, title, closeLabel, description, children, footer, className, contentClassName, onKeyDownCapture, headless, }: ModalProps): import("react").ReactPortal | null;
35
37
  export {};
36
38
  //# sourceMappingURL=Modal.d.ts.map
@@ -1,4 +1,4 @@
1
- /** Anchor-preserving tooltips with optional body portals for clipping containers. */
1
+ /** Anchor-preserving tooltips; an optional body portal escapes clipping containers and stacking contexts that cap the bubble's z-index. */
2
2
  import type { FocusEventHandler, MouseEventHandler, ReactElement, Ref } from 'react';
3
3
  /** Bubble placement relative to the anchor. */
4
4
  export type TooltipSide = 'right' | 'bottom' | 'top';
@@ -19,8 +19,11 @@ type TooltipLabel = string | (() => string);
19
19
  * @param props.align - horizontal anchor-edge alignment for 'bottom'/'top' bubbles: 'end' pins
20
20
  * the bubble's right edge to the anchor's (for anchors beside other hover surfaces the centered
21
21
  * bubble would overlap); default 'center'. Ignored for side 'right'.
22
- * @param props.portal - render the bubble under document.body to escape containing blocks and clipping ancestors.
22
+ * @param props.portal - render the bubble under document.body, so an ancestor's clipping or its
23
+ * stacking context (which confines the bubble's z-index to that context) cannot hide it.
23
24
  * @param props.delayMs - hover delay in milliseconds; keyboard focus remains immediate.
25
+ * @param props.gap - anchor-to-bubble distance in pixels for 'bottom'/'top' bubbles (default 8);
26
+ * ignored for side 'right'.
24
27
  * @param props.disabled - suppress the bubble while true; the anchor renders identically so
25
28
  * toggling never remounts it (which would cut its CSS transitions).
26
29
  * @param props.maxWidth - bubble width cap in pixels, for labels long enough that the default
@@ -31,11 +34,12 @@ type TooltipLabel = string | (() => string);
31
34
  * anchor dismisses the bubble until the next trigger, and focus arriving after a pointer
32
35
  * interaction (a closing menu refocusing its trigger) never raises it.
33
36
  */
34
- export declare function Tooltip({ label, side, align, delayMs, disabled, portal, maxWidth, children }: {
37
+ export declare function Tooltip({ label, side, align, delayMs, gap, disabled, portal, maxWidth, children }: {
35
38
  label: TooltipLabel;
36
39
  side?: TooltipSide;
37
40
  align?: 'center' | 'end';
38
41
  delayMs?: number;
42
+ gap?: number;
39
43
  disabled?: boolean;
40
44
  portal?: boolean;
41
45
  maxWidth?: number;
@@ -162,6 +162,10 @@ export declare const IconWarningTriangleOutlineMedium: (props: IconProps) => imp
162
162
  export declare const IconUserOutlineRegular: (props: IconProps) => import("react").JSX.Element;
163
163
  /** Medium IconUserOutline artwork with a 1.3px stroke. */
164
164
  export declare const IconUserOutlineMedium: (props: IconProps) => import("react").JSX.Element;
165
+ /** Regular one-pixel IconUsersOutline artwork: a front person with a second person behind. */
166
+ export declare const IconUsersOutlineRegular: (props: IconProps) => import("react").JSX.Element;
167
+ /** Medium IconUsersOutline artwork with a 1.3px stroke. */
168
+ export declare const IconUsersOutlineMedium: (props: IconProps) => import("react").JSX.Element;
165
169
  /** Regular one-pixel IconPaperPlaneOutline artwork. */
166
170
  export declare const IconPaperPlaneOutlineRegular: (props: IconProps) => import("react").JSX.Element;
167
171
  /** Medium IconPaperPlaneOutline artwork with a 1.3px stroke. */
@@ -85,4 +85,6 @@ export { extractMarkdownPlainText } from './markdown/plain-text.ts';
85
85
  export type { MarkdownPlainTextMode, MarkdownPlainTextOptions } from './markdown/plain-text.ts';
86
86
  export * from './icons/index.tsx';
87
87
  export { PluginArtworkTerminal, PluginArtworkLoop, PluginArtworkSubagent, PluginArtworkSearch, PluginArtworkDefault, } from './plugin-artwork.tsx';
88
+ export { ImageLightbox } from './ImageLightbox.tsx';
89
+ export type { ImageLightboxLabels } from './ImageLightbox.tsx';
88
90
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { ImageLightboxLabels } from '../ImageLightbox.tsx';
2
3
  /**
3
4
  * Handle one sanitized absolute HTTP(S) URL selected from Markdown.
4
5
  * @param href - destination URL.
@@ -6,6 +7,15 @@ import type { ReactNode } from 'react';
6
7
  export type MarkdownExternalLinkHandler = (href: string) => void;
7
8
  /** Navigation capabilities supplied by the nearest Markdown owner. */
8
9
  export interface MarkdownDelegate {
10
+ /** Image previews for decoded local paths in this owner's workspace. */
11
+ readonly fileImages?: {
12
+ resolve: (path: string) => string | undefined;
13
+ labels: ImageLightboxLabels & {
14
+ open: string;
15
+ loading: string;
16
+ failed: string;
17
+ };
18
+ } | undefined;
9
19
  /** Ordinary HTTP(S) activation; absent handlers retain native anchor behavior. */
10
20
  readonly openExternalLink?: MarkdownExternalLinkHandler | undefined;
11
21
  /**
@@ -27,7 +37,7 @@ export interface MarkdownDelegateProviderProps extends MarkdownDelegate {
27
37
  * @param props - Child tree and its file and HTTP(S) link handlers.
28
38
  * @returns the scoped child tree.
29
39
  */
30
- export declare function MarkdownDelegateProvider({ children, openExternalLink, openFile, }: MarkdownDelegateProviderProps): ReactNode;
40
+ export declare function MarkdownDelegateProvider({ children, openExternalLink, openFile, fileImages, }: MarkdownDelegateProviderProps): ReactNode;
31
41
  /**
32
42
  * Read the nearest Markdown navigation capabilities.
33
43
  * @returns Owner callbacks, or an empty delegate outside a provider.
@@ -0,0 +1,10 @@
1
+ /** Recover standalone local image references with bare spaces without changing code or source offsets. */
2
+ import type { Root } from 'mdast';
3
+ /**
4
+ * Recover only unambiguous, unescaped image-only paragraphs containing a local path with spaces.
5
+ * @param root - Parsed Markdown tree, modified in place.
6
+ * @param source - Original source used to distinguish authored syntax from escaped examples.
7
+ * @returns The same root with recovered image nodes.
8
+ */
9
+ export declare function recoverLocalImages(root: Root, source: string): Root;
10
+ //# sourceMappingURL=local-image-syntax.d.ts.map
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.7-alpha.2",
4
+ "version": "0.1.7-rc.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -47,9 +47,9 @@
47
47
  "micromark-extension-gfm": "^3.0.0",
48
48
  "micromark-util-sanitize-uri": "^2.0.1",
49
49
  "simple-icons": "16.31.0",
50
+ "@deepseek-ai/dsh-client-store": "0.1.7-rc.1",
50
51
  "@deepseek-ai/cordis": "~4.0.4",
51
- "@deepseek-ai/dsh-util-workspace-path": "0.1.7-alpha.2",
52
- "@deepseek-ai/dsh-client-store": "0.1.7-alpha.2"
52
+ "@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.1"
53
53
  },
54
54
  "files": [
55
55
  "lib/index.js",