@deepseek-ai/dsh-client-ui-sidebar-documentpreview 0.1.5-alpha.2 → 0.1.5-rc.2

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-sidebar-documentpreview/README.md
5
- README.md: fbfbfaf4d80e425e5fbc62c082509fb6c4a93962
6
- README.zh.md: c27a359b608d8b72ace0700c55c34a58a302ec60
5
+ README.md: e3659ed08a7c82f7c5ef314806ea07262f421caa
6
+ README.zh.md: 2c2fe2225a87b333f501e58f4988ff8034c14273
package/README.md CHANGED
@@ -27,10 +27,10 @@ Preview readable files in the right Sidebar and choose among registered renderer
27
27
  ## What it registers
28
28
 
29
29
  - **The type** — `ctx.sidebarRightTabs.register(...)` with id `@deepseek-ai/dsh-client-ui-sidebar-documentpreview` (this implementation's identity in the tab system, and the key its body registers under), kind `text`, pattern `dsh-resource://file/**`, band `fallback`. `canOpen` accepts only Session addresses, whose paths may be relative or absolute; bare `absolute` addresses are not claimed. A type registered at the `extension` or `builtin` band for a narrower pattern (say `*.png`) takes those addresses; other supported files land here. The whole address is the content identity, so two files with one name in different directories, or one path under two sessions, are two tabs; the decoded basename is the tab title, and the keyed `sidebar.right.pane.tab.title` seat places its extension-specific `FileTypeIcon` before that title.
30
- - **The body** — the keyed `sidebar.right.pane.tab` seat under the type's id. Its fixed header shows the Host's absolute path when available, otherwise the requested path; directories use tertiary label colour, the name uses primary label colour, and a clipped path retains and fades toward its final segment while its tooltip exposes the full value. A dropdown selects among matching renderers and plain text. A wrap toggle appears only when the selected renderer declares `wrap: true`; its glyph describes the mode the click selects, and the per-tab preference starts on. Reload stays in this header, not the Sidebar's tab strip. The shared body below owns document scrolling.
30
+ - **The body** — the keyed `sidebar.right.pane.tab` seat under the type's id. Its fixed header shows the Host's absolute path when available, otherwise the requested path; directories use tertiary label colour, the name uses primary label colour, and a clipped path retains and fades toward its final segment while its tooltip exposes the full value. A dropdown selects among matching renderers and plain text. A wrap toggle appears only when the selected renderer declares `wrap: true`; its glyph describes the mode the click selects, and the per-tab preference starts on. Reload stays in this header, not the Sidebar's tab strip. The body reaches every pane edge; each renderer owns its content inset and may own an inner scrollport. This intentionally differs from the Files tab's 2px right-side scrollbar offset: previews keep the full pane width so edge-to-edge HTML and code scrollports end at the pane edge.
31
31
  - **Shared loading and view state**, session-scoped and bucketed by tab id. The store holds accumulated pages or complete bytes, read and observed versions, loading/failure state, renderer choice, scroll offset, wrap, and the answered navigation revision. The ordinary inject face calls Remote readers and writes through declared store actions. Reloads and loading-mode changes retire older requests; the tab's abort signal forgets its state.
32
32
 
33
- Document implementations register metadata with `ctx.documentPreviews.register({ id, extensions, priority, title, loading, wrap? })` and a body under the same `id` in the keyed, Session-scoped `sidebar.right.tab.document` child slot. Own both registrations with effects and wait for the child slot through `ctx.slots.inject`. Bodies receive `resourceAddress`, prepared `content`, `wrap`, and the standard `useTabInfo`/`useResource` hooks; they do not receive a custom resource loader. Metadata declares `loading: 'text-pages'` or `'bytes-complete'`. The registry retains all matching alternatives: `extension` (the default) ranks above `builtin`, then longer suffixes rank first, then registration order. The dropdown preserves a selected implementation while it remains available; removing it selects the next candidate. Builtin bodies use these same registrations.
33
+ Document implementations register metadata with `ctx.documentPreviews.register({ id, extensions, priority, title, loading, wrap? })` and a body under the same `id` in the keyed, Session-scoped `sidebar.right.tab.document` child slot. Own both registrations with effects and wait for the child slot through `ctx.slots.inject`. Bodies receive `resourceAddress`, prepared `content`, `wrap`, `scrollportRef`, and the standard `useTabInfo`/`useResource` hooks; they do not receive a custom resource loader. A renderer that owns an inner scrolling element attaches `scrollportRef` to it, and the owner returns to the shared body when that element unmounts. Metadata declares `loading: 'text-pages'` or `'bytes-complete'`. The registry retains all matching alternatives: `extension` (the default) ranks above `builtin`, then longer suffixes rank first, then registration order. The dropdown preserves a selected implementation while it remains available; removing it selects the next candidate. Builtin bodies use these same registrations.
34
34
 
35
35
  <a id="addresses"></a>
36
36
  ## Addresses
@@ -47,18 +47,18 @@ The body reads its record, navigation and lifetime through `useTabInfo().tab`. `
47
47
  - **Complete bytes** — PDF, HTML, and common images use an inject callback to `remote.workspaceFiles.readAll(sessionId, path, signal)`. `rpc.ts` decodes the wire base64 into `data: Uint8Array<ArrayBuffer>` for `{ kind: 'bytes', data }`. The Host's `maxFileBytes` cap rejects oversized files rather than truncating them. PDF copies retained bytes before worker transfer, keeping the Preview buffer usable. Bytes stay in transient view state, never persisted layouts or Session JSONL. Loading-mode changes retire previous results.
48
48
  - **Reload** — only the current Preview tab rereads through its Remote callbacks, preserving its scroll preference and retiring older requests. Its change notice compares the read version and the observation captured at read start with later `resource.value.version`; an already observed version does not become a new change after refresh. Reads neither refresh shared metadata nor clear another tab's notice.
49
49
 
50
- HTML runs in a Blob iframe with exactly `sandbox="allow-scripts"`, without `allow-same-origin`; scripts cannot access the parent application's origin or file reader. The renderer loads directly declared relative `.js` classic scripts and `.css` stylesheets through its ordinary inject callback to `remote.workspaceFiles.readRelated`, with fixed safety limits of 4 MiB per asset, 32 MiB total, and 64 distinct assets. Host code resolves the related path; `rpc.ts` decodes the returned bytes. Inside the renderer, base64 is used only to embed the iframe bootstrap payload in script text. A `<base href>` leaves dependency resolution to the browser, as do HTTPS resources. Local module imports, CSS `url()`/`@import`, and dynamic `fetch` do not use Host file access. Read failures, invalid UTF-8, or exceeded limits fail the preview rather than publishing a partial asset package. Replacing or unmounting the document releases its Blob URL.
50
+ HTML fills the body edge to edge in a Blob iframe with exactly `sandbox="allow-scripts"`, without `allow-same-origin`; scripts cannot access the parent application's origin or file reader. The renderer loads directly declared relative `.js` classic scripts and `.css` stylesheets through its ordinary inject callback to `remote.workspaceFiles.readRelated`, with fixed safety limits of 4 MiB per asset, 32 MiB total, and 64 distinct assets. Host code resolves the related path; `rpc.ts` decodes the returned bytes. Inside the renderer, base64 is used only to embed the iframe bootstrap payload in script text. A `<base href>` leaves dependency resolution to the browser, as do HTTPS resources. Local module imports, CSS `url()`/`@import`, and dynamic `fetch` do not use Host file access. Read failures, invalid UTF-8, or exceeded limits fail the preview rather than publishing a partial asset package. Replacing or unmounting the document releases its Blob URL.
51
51
 
52
52
  PNG, JPEG, GIF, WebP, BMP, ICO, and SVG render through Blob URLs in an `<img>` static-image context. The image keeps its intrinsic CSS-pixel dimensions; a smaller image centres in the shared scroller, and larger dimensions scroll on either axis. The renderer provides neither zoom nor drag-to-pan. SVG markup never enters the application DOM or an iframe, so its scripts cannot execute or reach the parent page. Replacing or unmounting the image revokes its Blob URL.
53
53
 
54
54
  Shared copy comes from `sidebarDocumentPreview`; each builtin renderer owns its localized labels.
55
55
 
56
- Initial reads, additional pages, and HTML/PDF/image preparation share a loading indicator that respects reduced-motion preferences. Loaded pages stay visible while another page loads. PDF pages form one vertical, width-fitted sequence and render lazily near the viewport. Code previews show source line numbers by default without including them in copied text; plain text uses the same font size and line height as code. Code sits on the pane's own background rather than the chat card's fill, and with wrap off both axes scroll in the shared document scroller while the copy banner stays pinned to the visible width.
56
+ Initial reads, additional pages, and HTML/PDF/image preparation share a loading indicator that respects reduced-motion preferences. Loaded pages stay visible while another page loads. PDF pages form one vertical, width-fitted sequence and render lazily near the viewport. Code previews show source line numbers by default without including them in copied text; plain text uses the same font size and line height as code. Code sits on the pane's own background rather than the chat card's fill; its banner is adjacent to a full-height inner scrollport, so both scrollbars begin below the copy control.
57
57
 
58
58
  <a id="navigation"></a>
59
59
  ## Navigation
60
60
 
61
- `ctx.sidebarRight.openResource(address, { params: { line } })` carries a 1-based source line through the `file` parameters. In `text-pages` mode, the owner loads sequential pages until that line or EOF. Plain-text and code renderers expose source-line anchors; Markdown does not. A navigation remains pending while its selected renderer has no anchor and runs if the user switches to plain text or code. Code navigation places the target below its sticky toolbar. Byte-mode renderers do not consume source-line navigation. Each completed navigation revision is answered once. Opening the same file without `revealIfOpened: false` focuses its existing tab and delivers a new revision.
61
+ `ctx.sidebarRight.openResource(address, { params: { line } })` carries a 1-based source line through the `file` parameters. In `text-pages` mode, the owner loads sequential pages until that line or EOF. Plain-text and code renderers expose source-line anchors; Markdown does not. A navigation remains pending while its selected renderer has no anchor and runs if the user switches to plain text or code. Code navigation scrolls the inner source viewport directly. Byte-mode renderers do not consume source-line navigation. Each completed navigation revision is answered once. Opening the same file without `revealIfOpened: false` focuses its existing tab and delivers a new revision.
62
62
 
63
63
  <a id="model-experience"></a>
64
64
  ## Model Experience
package/README.zh.md CHANGED
@@ -27,10 +27,10 @@ kind: "package-reference"
27
27
  ## 注册了什么
28
28
 
29
29
  - **类型** —— `ctx.sidebarRightTabs.register(...)`,id 为 `@deepseek-ai/dsh-client-ui-sidebar-documentpreview`(这个实现在 tab 系统里的唯一键,也是其体注册所用的 key),kind `text`,pattern `dsh-resource://file/**`,档位 `fallback`。`canOpen` 只接受 Session 地址,其中路径可为相对或绝对路径;不认领裸 `absolute` 地址。在 `extension` 或 `builtin` 档以更窄 pattern(比如 `*.png`)注册的类型接走那些地址;其他受支持文件落到这里。整个地址就是内容身份,所以不同目录下同名的两个文件、或同一路径在两个会话之下,是两个 tab;解码后的 basename 是 tab 标题,keyed slot `sidebar.right.pane.tab.title` 会在标题前放置按扩展名选择的 `FileTypeIcon`。
30
- - **正文** —— keyed slot `sidebar.right.pane.tab`,键为类型的 id。固定头部在可用时显示 Host 的绝对路径,否则显示请求路径;目录使用三级标签色,文件名使用一级标签色,路径过长时保留末段并向开头淡出,提示中仍提供完整值。下拉菜单可在匹配的渲染器与纯文本间切换。仅当所选渲染器声明 `wrap: true` 时显示换行开关;图标表示点击后切换到的模式,该偏好按 tab 保存,初始开启。重新载入仍在此头部,不放入 Sidebar 的 tab 条。下方的共享正文区域负责文档滚动。
30
+ - **正文** —— keyed slot `sidebar.right.pane.tab`,键为类型的 id。固定头部在可用时显示 Host 的绝对路径,否则显示请求路径;目录使用三级标签色,文件名使用一级标签色,路径过长时保留末段并向开头淡出,提示中仍提供完整值。下拉菜单可在匹配的渲染器与纯文本间切换。仅当所选渲染器声明 `wrap: true` 时显示换行开关;图标表示点击后切换到的模式,该偏好按 tab 保存,初始开启。重新载入仍在此头部,不放入 Sidebar 的 tab 条。正文贴合格的每条边,各渲染器自行提供内容留白,并可拥有内部滚动区。这与 Files tab 右侧预留 2px 滚动条间距的布局有意不同:Preview 使用格的完整宽度,使贴边 HTML 与代码滚动区终止于格的边缘。
31
31
  - **共享加载与视图状态**,会话作用域、按 tab id 分桶。store 持有累计页或完整字节、读取与观察版本、加载/失败状态、渲染器选择、滚动位置、换行和已响应的导航 revision。普通 inject face 调用 Remote 读取,并经声明的 store action 写入。重新载入和加载模式变化会淘汰旧请求;tab 的中止信号清理其状态。
32
32
 
33
- 文档实现在 `ctx.documentPreviews.register({ id, extensions, priority, title, loading, wrap? })` 注册元数据,并以相同 `id` 向 keyed、Session 作用域的子 slot `sidebar.right.tab.document` 注册正文。两处注册都由 effect 持有,通过 `ctx.slots.inject` 等待子 slot。正文接收 `resourceAddress`、准备好的 `content`、`wrap` 和标准 `useTabInfo`/`useResource` 钩子,不接收自定义资源加载器。元数据声明 `loading: 'text-pages'` 或 `'bytes-complete'`。注册表保留所有匹配备选:`extension`(默认)优先于 `builtin`,随后按更长的后缀、再按注册顺序排列。所选实现仍可用时,下拉选择保持不变;移除后选择下一个候选。内置正文也使用相同注册方式。
33
+ 文档实现在 `ctx.documentPreviews.register({ id, extensions, priority, title, loading, wrap? })` 注册元数据,并以相同 `id` 向 keyed、Session 作用域的子 slot `sidebar.right.tab.document` 注册正文。两处注册都由 effect 持有,通过 `ctx.slots.inject` 等待子 slot。正文接收 `resourceAddress`、准备好的 `content`、`wrap`、`scrollportRef` 和标准 `useTabInfo`/`useResource` 钩子,不接收自定义资源加载器。拥有内部滚动元素的渲染器把 `scrollportRef` 挂到该元素上;该元素卸载后,owner 恢复使用共享正文。元数据声明 `loading: 'text-pages'` 或 `'bytes-complete'`。注册表保留所有匹配备选:`extension`(默认)优先于 `builtin`,随后按更长的后缀、再按注册顺序排列。所选实现仍可用时,下拉选择保持不变;移除后选择下一个候选。内置正文也使用相同注册方式。
34
34
 
35
35
  <a id="addresses"></a>
36
36
  ## 地址
@@ -47,18 +47,18 @@ tab 使用 `fileAddressFor` 构造的 Session 地址,携带相对或绝对路
47
47
  - **完整字节** —— PDF、HTML 和常见图片通过 inject 回调调用 `remote.workspaceFiles.readAll(sessionId, path, signal)`。`rpc.ts` 将线路上的 base64 解码为 `data: Uint8Array<ArrayBuffer>`,供 `{ kind: 'bytes', data }` 使用。Host 的 `maxFileBytes` 上限拒绝超大文件,不截断。PDF 在传给 worker 前复制保留的字节,使 Preview 缓冲区仍可使用。字节仅保存在临时视图状态中,绝不进入持久布局或 Session JSONL。加载模式变化会淘汰先前结果。
48
48
  - **重新载入** —— 仅当前 Preview tab 通过自己的 Remote 回调重读,保留滚动偏好并淘汰旧请求。变更提示将读取版本及起读时的观察版本与后续 `resource.value.version` 比较;刷新前已观察到的版本不会被当成新变化。读取既不刷新共享元数据,也不清除其它 tab 的提示。
49
49
 
50
- HTML Blob iframe 中运行,沙箱属性严格为 `sandbox="allow-scripts"`,不含 `allow-same-origin`;脚本无法访问父应用的源或文件读取接口。渲染器通过普通 inject 回调调用 `remote.workspaceFiles.readRelated`,加载直接声明的相对 `.js` 经典脚本和 `.css` 样式表;固定安全上限为单个资源 4 MiB、总计 32 MiB、64 个不同资源。Host 代码解析关联路径,`rpc.ts` 解码返回的字节。在渲染器内部,base64 仅用于把 iframe 引导载荷嵌入脚本文本。`<base href>` 将依赖解析交给浏览器,HTTPS 资源也由浏览器处理。本地模块 import、CSS `url()`/`@import` 和动态 `fetch` 不使用 Host 文件访问。读取失败、无效 UTF-8 或超出上限都使预览失败,不发布部分资源包。替换或卸载文档会释放其 Blob URL。
50
+ HTML 以贴合正文四边的 Blob iframe 运行,沙箱属性严格为 `sandbox="allow-scripts"`,不含 `allow-same-origin`;脚本无法访问父应用的源或文件读取接口。渲染器通过普通 inject 回调调用 `remote.workspaceFiles.readRelated`,加载直接声明的相对 `.js` 经典脚本和 `.css` 样式表;固定安全上限为单个资源 4 MiB、总计 32 MiB、64 个不同资源。Host 代码解析关联路径,`rpc.ts` 解码返回的字节。在渲染器内部,base64 仅用于把 iframe 引导载荷嵌入脚本文本。`<base href>` 将依赖解析交给浏览器,HTTPS 资源也由浏览器处理。本地模块 import、CSS `url()`/`@import` 和动态 `fetch` 不使用 Host 文件访问。读取失败、无效 UTF-8 或超出上限都使预览失败,不发布部分资源包。替换或卸载文档会释放其 Blob URL。
51
51
 
52
52
  PNG、JPEG、GIF、WebP、BMP、ICO 和 SVG 通过 Blob URL 在 `<img>` 静态图片上下文中渲染。图片保持固有 CSS 像素尺寸;小图在共享滚动区内居中,大图可沿任一轴滚动。渲染器既不提供缩放,也不提供拖拽平移。SVG 标记绝不进入应用 DOM 或 iframe,因此其中的脚本无法执行,也无法访问父页面。替换或卸载图片会撤销其 Blob URL。
53
53
 
54
54
  共享文案来自 `sidebarDocumentPreview`;各内置渲染器拥有自己的本地化标签。
55
55
 
56
- 首次读取、追加页及 HTML/PDF/图片准备共用加载指示器,并遵循减少动态效果偏好。下一页加载期间保留已显示的内容。PDF 页面组成一个纵向、适配宽度的连续序列,并在接近视口时惰性渲染。代码预览默认显示源码行号,但复制文本不包含行号;纯文本与代码使用相同字号和行高。代码直接坐在分栏自身的背景上,而不是会话卡片的填充色;关闭折行时两个轴都在共享文档滚动区滚动,复制条钉在可视宽度内不随横向滚动偏移。
56
+ 首次读取、追加页及 HTML/PDF/图片准备共用加载指示器,并遵循减少动态效果偏好。下一页加载期间保留已显示的内容。PDF 页面组成一个纵向、适配宽度的连续序列,并在接近视口时惰性渲染。代码预览默认显示源码行号,但复制文本不包含行号;纯文本与代码使用相同字号和行高。代码直接坐在分栏自身的背景上,而不是会话卡片的填充色;复制条与占满剩余高度的内部滚动区相邻,因此横纵滚动条都从复制控件下方开始。
57
57
 
58
58
  <a id="navigation"></a>
59
59
  ## 导航
60
60
 
61
- `ctx.sidebarRight.openResource(address, { params: { line } })` 通过 `file` 参数携带 1 起算的源码行号。在 `text-pages` 模式下,owner 顺序加载到该行或 EOF。纯文本与代码渲染器提供源码行锚点;Markdown 不提供。所选渲染器没有锚点时,导航保持待处理;用户切换到纯文本或代码后执行。代码导航会把目标放在置顶工具栏下方。字节模式渲染器不消费源码行导航。每个完成的导航 revision 只响应一次。不带 `revealIfOpened: false` 打开同一文件时聚焦已有 tab,并送达新 revision。
61
+ `ctx.sidebarRight.openResource(address, { params: { line } })` 通过 `file` 参数携带 1 起算的源码行号。在 `text-pages` 模式下,owner 顺序加载到该行或 EOF。纯文本与代码渲染器提供源码行锚点;Markdown 不提供。所选渲染器没有锚点时,导航保持待处理;用户切换到纯文本或代码后执行。代码导航直接滚动内部源码视口。字节模式渲染器不消费源码行导航。每个完成的导航 revision 只响应一次。不带 `revealIfOpened: false` 打开同一文件时聚焦已有 tab,并送达新 revision。
62
62
 
63
63
  <a id="model-experience"></a>
64
64
  ## 模型体验
package/lib/client.js CHANGED
@@ -1259,23 +1259,22 @@ window.__ModuleLoader__.load({
1259
1259
  }
1260
1260
  /**
1261
1261
  * Reveal a plain-text or highlighted source line.
1262
- * @param body - scrolling document body.
1262
+ * @param body - scrolling document body or code-content viewport.
1263
1263
  * @param line - 1-based source line to reveal.
1264
1264
  * @returns Whether the current renderer exposes that line.
1265
1265
  */
1266
1266
  function scrollToLine(body, line) {
1267
+ const innerCode = body.hasAttribute("data-code-block-content");
1267
1268
  const plain = body.querySelector(`[data-textpreview-line="${line}"]`);
1268
- const code = body.querySelectorAll("[data-code-preview] pre .line").item(line - 1);
1269
+ const code = innerCode ? body.querySelectorAll("pre .line").item(line - 1) : null;
1269
1270
  const row = plain ?? code;
1270
1271
  if (!(row instanceof HTMLElement)) return false;
1271
- const toolbar = (code === row ? row.closest(".md-code-block") : null)?.firstElementChild;
1272
- const toolbarHeight = toolbar instanceof HTMLElement ? toolbar.offsetHeight : 0;
1273
- body.scrollTop = Math.max(0, row.offsetTop - toolbarHeight);
1272
+ body.scrollTop = Math.max(0, row.offsetTop);
1274
1273
  return true;
1275
1274
  }
1276
1275
  //#endregion
1277
1276
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/TextPreview.module.css.mjs
1278
- const css$5 = ".dhJKeW_preview{flex-direction:column;flex:auto;height:100%;min-height:0;display:flex}.dhJKeW_header{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l3);flex:none;align-items:center;gap:4px;height:38px;padding:0 6px 0 16px;display:flex}.dhJKeW_path{white-space:nowrap;flex:auto;justify-content:flex-end;min-width:0;margin-right:12px;font-size:12px;display:flex;overflow:hidden}.dhJKeW_path[data-textpreview-path-clipped]{mask-image:linear-gradient(90deg,#0000,#000 28px)}.dhJKeW_pathText{flex:none;margin-right:auto}.dhJKeW_pathDirectory{color:var(--dsw-alias-label-tertiary)}.dhJKeW_pathName{color:var(--dsw-alias-label-primary)}.dhJKeW_changed{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border-bottom:.5px solid var(--dsw-alias-border-l1);flex:none;align-items:center;gap:10px;margin:0;padding:6px 10px;font-size:12px;display:flex}.dhJKeW_body{scrollbar-gutter:stable;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);min-height:0;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-mono,ui-monospace, monospace);white-space:pre;flex:auto;margin-right:2px;padding:8px 0 8px 8px;line-height:1.6;position:relative;overflow:auto}.dhJKeW_body::-webkit-scrollbar-track{margin:2px}.dhJKeW_wrap{white-space:pre-wrap;word-break:break-word}.dhJKeW_body:has([data-code-preview],[data-document-markdown]){padding:0}.dhJKeW_body:has([data-code-preview]){container-type:inline-size}.dhJKeW_page{font:var(--dsw-font-markdown-code-block);white-space:inherit;margin:0}.dhJKeW_line{padding:0 10px}.dhJKeW_lineTarget{background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_empty{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-family);text-align:center;white-space:normal;flex-direction:column;justify-content:center;align-items:center;gap:16px;padding:0 24px;line-height:1.6;display:flex}.dhJKeW_empty:after{content:\"\";flex:0 12%}.dhJKeW_emptyIcon{opacity:.6;filter:grayscale();flex:none}.dhJKeW_emptyLine{margin:0}.dhJKeW_retry{height:32px;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:16px;flex:none;align-items:center;gap:6px;padding:0 14px 0 12px;font-family:inherit;display:inline-flex}.dhJKeW_retry:hover{background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_titleIcon{flex:none}.dhJKeW_statusLine{color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);white-space:normal;align-items:center;gap:10px;margin:0;padding:6px 10px;line-height:1.6;display:flex}.dhJKeW_status{flex-direction:column;align-items:flex-start;gap:8px;padding:12px 10px;display:flex}.dhJKeW_action{color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font,inherit);white-space:normal;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:6px;padding:4px 10px}.dhJKeW_action:hover{background:var(--dsw-alias-bg-layer-3)}.dhJKeW_more{color:var(--dsw-alias-label-secondary);font-size:12px;font-family:var(--dsw-font,inherit);white-space:normal;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:6px;margin:8px 10px;padding:4px 10px;display:block}.dhJKeW_more:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3)}.dhJKeW_more:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.dhJKeW_tool{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:28px;flex:none;justify-content:center;align-items:center;padding:6px;line-height:1;display:inline-flex}.dhJKeW_tool svg{width:15px;height:15px}.dhJKeW_tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_viewerTool{white-space:nowrap;text-overflow:ellipsis;width:auto;max-width:160px;padding:0 6px;font-size:12px;overflow:hidden}";
1277
+ const css$5 = ".dhJKeW_preview{flex-direction:column;flex:auto;height:100%;min-height:0;display:flex}.dhJKeW_header{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l3);flex:none;align-items:center;gap:4px;height:38px;padding:0 6px 0 16px;display:flex}.dhJKeW_path{white-space:nowrap;flex:auto;justify-content:flex-end;min-width:0;margin-right:12px;font-size:12px;display:flex;overflow:hidden}.dhJKeW_path[data-textpreview-path-clipped]{mask-image:linear-gradient(90deg,#0000,#000 28px)}.dhJKeW_pathText{flex:none;margin-right:auto}.dhJKeW_pathDirectory{color:var(--dsw-alias-label-tertiary)}.dhJKeW_pathName{color:var(--dsw-alias-label-primary)}.dhJKeW_changed{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border-bottom:.5px solid var(--dsw-alias-border-l1);flex:none;align-items:center;gap:10px;margin:0;padding:6px 10px;font-size:12px;display:flex}.dhJKeW_body{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);min-height:0;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-mono,ui-monospace, monospace);white-space:pre;flex:auto;margin:0;padding:0;line-height:1.6;position:relative;overflow:auto}.dhJKeW_body::-webkit-scrollbar-track{margin:2px}.dhJKeW_body:has([data-code-preview]){flex-direction:column;display:flex;overflow:hidden}.dhJKeW_wrap{white-space:pre-wrap;word-break:break-word}.dhJKeW_textDocument{box-sizing:border-box;min-width:100%;min-height:100%;padding:8px}.dhJKeW_page{font:var(--dsw-font-markdown-code-block);white-space:inherit;margin:0}.dhJKeW_line{padding:0 10px}.dhJKeW_lineTarget{background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_empty{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-family);text-align:center;white-space:normal;flex-direction:column;justify-content:center;align-items:center;gap:16px;padding:0 24px;line-height:1.6;display:flex}.dhJKeW_empty:after{content:\"\";flex:0 12%}.dhJKeW_emptyIcon{opacity:.6;filter:grayscale();flex:none}.dhJKeW_emptyLine{margin:0}.dhJKeW_retry{height:32px;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:16px;flex:none;align-items:center;gap:6px;padding:0 14px 0 12px;font-family:inherit;display:inline-flex}.dhJKeW_retry:hover{background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_titleIcon{flex:none}.dhJKeW_statusLine{color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);white-space:normal;align-items:center;gap:10px;margin:0;padding:6px 10px;line-height:1.6;display:flex}.dhJKeW_status{flex-direction:column;align-items:flex-start;gap:8px;padding:12px 10px;display:flex}.dhJKeW_action{color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font,inherit);white-space:normal;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:6px;padding:4px 10px}.dhJKeW_action:hover{background:var(--dsw-alias-bg-layer-3)}.dhJKeW_more{color:var(--dsw-alias-label-secondary);font-size:12px;font-family:var(--dsw-font,inherit);white-space:normal;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:6px;margin:8px 10px;padding:4px 10px;display:block}.dhJKeW_more:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3)}.dhJKeW_more:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.dhJKeW_tool{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:28px;flex:none;justify-content:center;align-items:center;padding:6px;line-height:1;display:inline-flex}.dhJKeW_tool svg{width:15px;height:15px}.dhJKeW_tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_viewerTool{white-space:nowrap;text-overflow:ellipsis;width:auto;max-width:160px;padding:0 6px;font-size:12px;overflow:hidden}";
1279
1278
  const tagId$5 = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/TextPreview.module.css";
1280
1279
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
1281
1280
  const tag = document.createElement("style");
@@ -1304,6 +1303,7 @@ window.__ModuleLoader__.load({
1304
1303
  "retry": "dhJKeW_retry",
1305
1304
  "status": "dhJKeW_status",
1306
1305
  "statusLine": "dhJKeW_statusLine",
1306
+ "textDocument": "dhJKeW_textDocument",
1307
1307
  "titleIcon": "dhJKeW_titleIcon",
1308
1308
  "tool": "dhJKeW_tool",
1309
1309
  "viewerTool": "dhJKeW_viewerTool",
@@ -1317,19 +1317,23 @@ window.__ModuleLoader__.load({
1317
1317
  const params = tab.navigation.params;
1318
1318
  const target = params !== void 0 && "line" in params ? params.line : void 0;
1319
1319
  if (content.kind !== "text") return null;
1320
- return content.pages.map((page) => (0, react_jsx_runtime.jsx)("pre", {
1321
- className: TextPreview_module_css_default.page,
1322
- "data-textpreview-page": page.offset,
1323
- children: linesOf(page).map((text, index) => {
1324
- const number = page.offset + index;
1325
- return (0, react_jsx_runtime.jsxs)("div", {
1326
- className: clsx(TextPreview_module_css_default.line, number === target && TextPreview_module_css_default.lineTarget),
1327
- "data-textpreview-line": number,
1328
- ...number === target ? { "data-textpreview-target": number } : {},
1329
- children: [text, "\n"]
1330
- }, number);
1331
- })
1332
- }, page.offset));
1320
+ return (0, react_jsx_runtime.jsx)("div", {
1321
+ className: TextPreview_module_css_default.textDocument,
1322
+ "data-textpreview-plain": true,
1323
+ children: content.pages.map((page) => (0, react_jsx_runtime.jsx)("pre", {
1324
+ className: TextPreview_module_css_default.page,
1325
+ "data-textpreview-page": page.offset,
1326
+ children: linesOf(page).map((text, index) => {
1327
+ const number = page.offset + index;
1328
+ return (0, react_jsx_runtime.jsxs)("div", {
1329
+ className: clsx(TextPreview_module_css_default.line, number === target && TextPreview_module_css_default.lineTarget),
1330
+ "data-textpreview-line": number,
1331
+ ...number === target ? { "data-textpreview-target": number } : {},
1332
+ children: [text, "\n"]
1333
+ }, number);
1334
+ })
1335
+ }, page.offset))
1336
+ });
1333
1337
  }
1334
1338
  //#endregion
1335
1339
  //#region lib/types/client/text/index.js
@@ -1419,6 +1423,8 @@ window.__ModuleLoader__.load({
1419
1423
  const mode = selected?.loading;
1420
1424
  const current = (state?.mode ?? "text-pages") === mode ? state : void 0;
1421
1425
  const bodyRef = (0, react.useRef)(null);
1426
+ const scrollportRef = (0, react.useRef)(null);
1427
+ const storedScrollTopRef = (0, react.useRef)(0);
1422
1428
  const pathRef = (0, react.useRef)(null);
1423
1429
  const pathTextRef = (0, react.useRef)(null);
1424
1430
  const [menuOpen, setMenuOpen] = (0, react.useState)(false);
@@ -1429,6 +1435,17 @@ window.__ModuleLoader__.load({
1429
1435
  const loaded = (0, react.useMemo)(() => loadedPages(pages ?? {}), [pages]);
1430
1436
  const loadedThrough = lastLineLoaded(loaded);
1431
1437
  const hasContent = loaded.length > 0 || current?.complete !== void 0;
1438
+ storedScrollTopRef.current = state?.scrollTop ?? 0;
1439
+ const bindBody = (0, react.useCallback)((body) => {
1440
+ const previous = bodyRef.current;
1441
+ bodyRef.current = body;
1442
+ if (scrollportRef.current === null || scrollportRef.current === previous) scrollportRef.current = body;
1443
+ }, []);
1444
+ const bindScrollport = (0, react.useCallback)((scrollport) => {
1445
+ const next = scrollport ?? bodyRef.current;
1446
+ scrollportRef.current = next;
1447
+ if (next !== null) next.scrollTop = storedScrollTopRef.current;
1448
+ }, []);
1432
1449
  const started = current !== void 0;
1433
1450
  (0, react.useEffect)(() => {
1434
1451
  if (started || !canRead || mode === void 0) return;
@@ -1446,11 +1463,11 @@ window.__ModuleLoader__.load({
1446
1463
  meta.value?.version
1447
1464
  ]);
1448
1465
  (0, react.useEffect)(() => {
1449
- const body = bodyRef.current;
1466
+ const body = scrollportRef.current;
1450
1467
  if (hasContent && body !== null && state !== void 0) body.scrollTop = state.scrollTop;
1451
- }, [hasContent]);
1468
+ }, [hasContent, selected?.id]);
1452
1469
  (0, react.useEffect)(() => {
1453
- const body = bodyRef.current;
1470
+ const body = scrollportRef.current;
1454
1471
  if (current === void 0 || body === null || current.revision === navigation.revision) return;
1455
1472
  if (line === void 0 || mode !== "text-pages") {
1456
1473
  actions.navigated(tab.id, navigation.revision);
@@ -1627,12 +1644,15 @@ window.__ModuleLoader__.load({
1627
1644
  ]
1628
1645
  }),
1629
1646
  (0, react_jsx_runtime.jsxs)("div", {
1630
- ref: bodyRef,
1647
+ ref: bindBody,
1631
1648
  className: clsx(TextPreview_module_css_default.body, state.wrap && TextPreview_module_css_default.wrap),
1632
1649
  "data-textpreview-body": true,
1633
1650
  "data-textpreview-wrap": state.wrap ? "" : void 0,
1634
- onScroll: (event) => {
1635
- const body = event.currentTarget;
1651
+ onScrollCapture: (event) => {
1652
+ const body = scrollportRef.current;
1653
+ /* v8 ignore next -- callback refs bind the scrollport during commit, before user input. */
1654
+ if (body === null) return;
1655
+ if (event.target !== body) return;
1636
1656
  actions.scrolled(tab.id, body.scrollTop);
1637
1657
  if (mode === "text-pages" && current?.failure === void 0 && body.clientHeight > 0 && body.scrollTop + body.clientHeight >= body.scrollHeight - 1) loadNext();
1638
1658
  },
@@ -1644,7 +1664,8 @@ window.__ModuleLoader__.load({
1644
1664
  content !== void 0 && renderSlot("sidebar.right.tab.document", {
1645
1665
  resourceAddress: tab.contentId,
1646
1666
  content,
1647
- wrap: state.wrap
1667
+ wrap: state.wrap,
1668
+ scrollportRef: bindScrollport
1648
1669
  }, {
1649
1670
  entryKey: selected.id,
1650
1671
  hookContext: useTabInfo,
@@ -2450,7 +2471,7 @@ document.open();document.write(html);document.close();
2450
2471
  }
2451
2472
  //#endregion
2452
2473
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/image/ImageBody.module.css.mjs
2453
- const css$2 = ".JFRUHG_frame{box-sizing:border-box;width:max-content;min-width:100%;height:max-content;min-height:100%;font-family:var(--dsw-font,sans-serif);white-space:normal;display:flex}.JFRUHG_image{user-select:none;flex:none;width:auto;max-width:none;height:auto;max-height:none;margin:auto;display:block}.JFRUHG_image[hidden]{display:none}.JFRUHG_status{box-sizing:border-box;width:100%;min-height:100%;color:var(--dsw-alias-label-secondary);white-space:normal;justify-content:center;align-items:center;margin:0;padding:10px;font-size:13px;line-height:1.5;display:flex}";
2474
+ const css$2 = ".JFRUHG_frame{box-sizing:border-box;width:max-content;min-width:100%;height:max-content;min-height:100%;font-family:var(--dsw-font,sans-serif);white-space:normal;padding:8px;display:flex}.JFRUHG_image{user-select:none;flex:none;width:auto;max-width:none;height:auto;max-height:none;margin:auto;display:block}.JFRUHG_image[hidden]{display:none}.JFRUHG_status{box-sizing:border-box;width:100%;min-height:100%;color:var(--dsw-alias-label-secondary);white-space:normal;justify-content:center;align-items:center;margin:0;padding:10px;font-size:13px;line-height:1.5;display:flex}";
2454
2475
  const tagId$2 = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/ImageBody.module.css";
2455
2476
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
2456
2477
  const tag = document.createElement("style");
@@ -26452,7 +26473,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
26452
26473
  }
26453
26474
  //#endregion
26454
26475
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/pdf/PdfBody.module.css.mjs
26455
- const css$1 = ".E0GbtG_body{width:100%;min-height:0;font-family:var(--dsw-font,sans-serif);white-space:normal;flex-direction:column;display:flex;overflow-x:hidden}.E0GbtG_page{box-sizing:border-box;width:100%;min-width:0;padding:12px;overflow-x:hidden}.E0GbtG_placeholder{aspect-ratio:3/4;justify-content:center;align-items:center;width:100%;display:flex}.E0GbtG_canvas{width:min(var(--pdf-page-width), 100%);max-width:100%;height:auto;margin-inline:auto;display:block}.E0GbtG_canvas[hidden]{display:none}.E0GbtG_status{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;padding:10px;font-size:13px;line-height:1.5;display:flex}";
26476
+ const css$1 = ".E0GbtG_body{box-sizing:border-box;width:100%;min-height:0;font-family:var(--dsw-font,sans-serif);white-space:normal;flex-direction:column;padding:8px;display:flex;overflow-x:hidden}.E0GbtG_page{box-sizing:border-box;width:100%;min-width:0;padding:12px;overflow-x:hidden}.E0GbtG_placeholder{aspect-ratio:3/4;justify-content:center;align-items:center;width:100%;display:flex}.E0GbtG_canvas{width:min(var(--pdf-page-width), 100%);max-width:100%;height:auto;margin-inline:auto;display:block}.E0GbtG_canvas[hidden]{display:none}.E0GbtG_status{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;padding:10px;font-size:13px;line-height:1.5;display:flex}";
26456
26477
  const tagId$1 = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/PdfBody.module.css";
26457
26478
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
26458
26479
  const tag = document.createElement("style");
@@ -26812,7 +26833,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
26812
26833
  }
26813
26834
  //#endregion
26814
26835
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/code/CodeBody.module.css.mjs
26815
- const css = ".Java6a_renderer{white-space:normal;min-width:0;min-height:100%}.Java6a_renderer .Java6a_code{--dsl-code-block-border-radius:0px;--dsl-code-block-line-white-space:pre;--dsl-code-block-background:transparent;min-width:0;margin:0;position:static}.Java6a_renderer .Java6a_code pre{box-sizing:border-box;white-space:pre;word-break:normal;overflow-wrap:normal;min-width:100%;overflow:visible}.Java6a_renderer[data-wrap=false]{width:max-content;min-width:100%}.Java6a_renderer[data-wrap=false] .Java6a_code [data-code-block-banner]{box-sizing:border-box;width:100cqw;position:sticky;left:0}.Java6a_renderer[data-wrap=true] .Java6a_code{--dsl-code-block-line-white-space:pre-wrap}.Java6a_renderer[data-wrap=true] .Java6a_code pre{white-space:pre-wrap;overflow-wrap:anywhere}";
26836
+ const css = ".Java6a_renderer{white-space:normal;flex-direction:column;flex:auto;width:100%;min-width:0;height:100%;min-height:0;display:flex;overflow:hidden}.Java6a_renderer .Java6a_code{--dsl-code-block-border-radius:0px;--dsl-code-block-line-white-space:pre;--dsl-code-block-background:transparent;flex-direction:column;flex:auto;min-width:0;height:100%;min-height:0;margin:0;display:flex;position:static}.Java6a_renderer .Java6a_code>[data-code-block-content]{flex:auto;min-width:0;min-height:0;display:block;position:relative;overflow:auto}.Java6a_renderer .Java6a_code>[data-code-block-content]::-webkit-scrollbar-track{margin:2px}.Java6a_renderer .Java6a_code pre{box-sizing:border-box;white-space:pre;word-break:normal;overflow-wrap:normal;min-width:100%;overflow:visible}.Java6a_renderer[data-wrap=true] .Java6a_code{--dsl-code-block-line-white-space:pre-wrap}.Java6a_renderer[data-wrap=true] .Java6a_code pre{white-space:pre-wrap;overflow-wrap:anywhere}";
26816
26837
  const tagId = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/CodeBody.module.css";
26817
26838
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
26818
26839
  const tag = document.createElement("style");
@@ -26828,7 +26849,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
26828
26849
  //#endregion
26829
26850
  //#region lib/types/client/code/CodeBody.js
26830
26851
  /** @param props - accumulated document contents and framework props. @returns one stable CodeBlock, or no body for byte contents. */
26831
- function CodeBody({ resourceAddress, content, wrap, t }) {
26852
+ function CodeBody({ resourceAddress, content, wrap, scrollportRef, t }) {
26832
26853
  if (content.kind !== "text") return null;
26833
26854
  const file = parseFileAddress(resourceAddress);
26834
26855
  if (file === void 0) throw new Error(`ui-sidebar-documentpreview: not a file address "${resourceAddress}"`);
@@ -26839,6 +26860,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
26839
26860
  "data-wrap": wrap,
26840
26861
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.CodeBlock, {
26841
26862
  className: CodeBody_module_css_default.code,
26863
+ contentRef: scrollportRef,
26842
26864
  code: content.text,
26843
26865
  lang: language,
26844
26866
  streaming: !content.eof,
@@ -5,5 +5,5 @@ import type { DocumentPreviewProps } from '../document/contract.ts';
5
5
  /** Document owner props and this renderer's localized controls. */
6
6
  export type CodeBodyProps = DocumentPreviewProps & PropsLocale<'sidebarCodePreview'>;
7
7
  /** @param props - accumulated document contents and framework props. @returns one stable CodeBlock, or no body for byte contents. */
8
- export declare function CodeBody({ resourceAddress, content, wrap, t }: CodeBodyProps): ReactNode;
8
+ export declare function CodeBody({ resourceAddress, content, wrap, scrollportRef, t }: CodeBodyProps): ReactNode;
9
9
  //# sourceMappingURL=CodeBody.d.ts.map
@@ -1,6 +1,7 @@
1
1
  /** Document renderer slot: the owner supplies shared file state, renderers own their presentation. */
2
2
  import type { PropsRuntime, SlotHookFactory } from '@deepseek-ai/dsh-client-ui-slots';
3
3
  import type { UseSidebarRightTabInfo } from '@deepseek-ai/dsh-client-ui-sidebar-right/client';
4
+ import type { RefCallback } from 'react';
4
5
  /** One loaded text window, retaining source line positions. */
5
6
  export interface DocumentTextPage {
6
7
  readonly offset: number;
@@ -33,6 +34,8 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
33
34
  readonly content: DocumentContent;
34
35
  /** The document toolbar's current wrapping preference. */
35
36
  readonly wrap: boolean;
37
+ /** Report a renderer-owned scrollport; passing `null` restores the shared body as the owner. */
38
+ readonly scrollportRef: RefCallback<HTMLElement>;
36
39
  };
37
40
  hookContext: UseSidebarRightTabInfo;
38
41
  inject: {
@@ -23,7 +23,7 @@ export declare function loadedPages(pages: Readonly<Record<number, TextPage>>):
23
23
  export declare function lastLineLoaded(pages: readonly LoadedPage[]): number;
24
24
  /**
25
25
  * Reveal a plain-text or highlighted source line.
26
- * @param body - scrolling document body.
26
+ * @param body - scrolling document body or code-content viewport.
27
27
  * @param line - 1-based source line to reveal.
28
28
  * @returns Whether the current renderer exposes that line.
29
29
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-sidebar-documentpreview",
3
3
  "description": "Extensible document previews for Sidebar files: Markdown, highlighted code, images, PDF, HTML, and plain text",
4
- "version": "0.1.5-alpha.2",
4
+ "version": "0.1.5-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -49,22 +49,22 @@
49
49
  "pdfjs-dist": "6.3.289",
50
50
  "react": "^18.2.0",
51
51
  "react-dom": "^18.2.0",
52
- "@deepseek-ai/dsh-api-remotes": "^0.1.5-alpha.2",
53
- "@deepseek-ai/dsh-api-workspace-files": "^0.1.5-alpha.2",
54
- "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.2",
55
- "@deepseek-ai/dsh-client-resources": "^0.1.5-alpha.2",
56
- "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.2",
57
52
  "@deepseek-ai/cordis": "^4.0.2",
58
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.2",
59
- "@deepseek-ai/dsh-client-ui-dockkit": "^0.1.5-alpha.2",
60
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.2",
61
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.2",
62
- "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.2",
63
- "@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.5-alpha.2",
64
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.2",
65
- "@deepseek-ai/dsh-util-workspace-path": "^0.1.5-alpha.2",
66
- "@deepseek-ai/dsh-session": "^0.1.5-alpha.2",
67
- "@deepseek-ai/dsh-api-gateway": "^0.1.5-alpha.2"
53
+ "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.2",
54
+ "@deepseek-ai/dsh-api-workspace-files": "^0.1.5-rc.2",
55
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
56
+ "@deepseek-ai/dsh-client-resources": "^0.1.5-rc.2",
57
+ "@deepseek-ai/dsh-api-gateway": "^0.1.5-rc.2",
58
+ "@deepseek-ai/dsh-client-store": "^0.1.5-rc.2",
59
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-rc.2",
60
+ "@deepseek-ai/dsh-client-ui-dockkit": "^0.1.5-rc.2",
61
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
62
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
63
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2",
64
+ "@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.5-rc.2",
65
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
66
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
67
+ "@deepseek-ai/dsh-util-workspace-path": "^0.1.5-rc.2"
68
68
  },
69
69
  "files": [
70
70
  "lib/index.js",