Browse Source

fix(documentpreview): draw pdf page placeholders on the skeleton token

Yif 1 month ago
parent
commit
adbe7e6395

+ 2 - 2
.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.md
-2026-09-11-sidebar-document-preview-polish.md: 483a530c603e7721b5ef3e66be77be2d56498bd1
-2026-09-11-sidebar-document-preview-polish.zh.md: f189cb7fe96d22320bb84f873cd5c1b6f923f44f
+2026-09-11-sidebar-document-preview-polish.md: 3854689f0ec75bc0ac0eed6984b6242ce7a4c427
+2026-09-11-sidebar-document-preview-polish.zh.md: 0e1c3b1fea0c1c95fca4641d3701a1cc0179dba0

+ 5 - 3
.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.md

@@ -6,7 +6,7 @@ English | [中文](2026-09-11-sidebar-document-preview-polish.zh.md)
 
 ## Problem
 
-The Sidebar document preview accumulated several experience defects ([#3974](https://github.com/deepseek-harness/deepseek-harness/issues/3974)). Images rendered at their intrinsic CSS-pixel size, so a wide image overflowed the pane and forced horizontal scrolling. The viewer dropdown always appended the plain-text fallback, so bitmap and PDF files offered a "Plain text" choice whose result is unreadable bytes, and files with one real renderer still showed a control with nothing meaningful to switch to. Binary containers with no renderer at all (video, archives, office documents) fell into the plain-text reader and surfaced a read error instead of a designed empty state. Each renderer carried its own loading copy and position, so opening a file flashed through several differently worded, differently placed indicators. PDF pages sat inside a double inset that shrank every page below the pane's width.
+The Sidebar document preview accumulated several experience defects ([#3974](https://github.com/deepseek-harness/deepseek-harness/issues/3974)). Images rendered at their intrinsic CSS-pixel size, so a wide image overflowed the pane and forced horizontal scrolling. The viewer dropdown always appended the plain-text fallback, so bitmap and PDF files offered a "Plain text" choice whose result is unreadable bytes, and files with one real renderer still showed a control with nothing meaningful to switch to. Binary containers with no renderer at all (video, archives, office documents) fell into the plain-text reader and surfaced a read error instead of a designed empty state. Each renderer carried its own loading copy and position, so opening a file flashed through several differently worded, differently placed indicators. PDF pages sat inside a double inset that shrank every page below the pane's width. Switching sidebar tabs remounted the file tree at scroll top, losing the reader's place.
 
 ## Decision
 
@@ -16,10 +16,12 @@ The Sidebar document preview accumulated several experience defects ([#3974](htt
 
 **Unsupported empty state.** The owner-side list in `document/unviewable.ts` names binary container suffixes (video, audio, archives, office documents, executables, fonts, disk images, design formats) that no renderer claims. The owner consults it only when no implementation matches, so any renderer registration always wins; a matching file shows the path header, the file-type icon, and one `unsupportedFile` line, and never issues a read. An uncertain suffix stays out of the list and keeps the plain-text fallback; a text-claimed file whose bytes fail the reader's checks reports the same copy through `error.notText`.
 
-**Unified loading.** `LoadingIndicator` gains `iconOnly`, which moves the label to `aria-label` and renders no visible text; every renderer's loading copy is the shared "Reading…". Every wait before content exists — the owner's first read, PDF parsing, HTML packaging, image decoding — centres the spinner in the pane, so opening a file shows one spinner in one position until the body appears. An unrendered PDF page holds its place as a static 3:4 placeholder block on `--dsw-alias-bg-layer-2` with no spinner; specialising the document-open wait into the first page's placeholder was tried and reverted because the owner's centred read spinner precedes it, and the handoff visibly jumped positions. A shimmer animation on placeholders was considered and dropped for per-page animation cost.
+**Unified loading.** `LoadingIndicator` gains `iconOnly`, which moves the label to `aria-label` and renders no visible text; every renderer's loading copy is the shared "Reading…". Every wait before content exists — the owner's first read, PDF parsing, HTML packaging, image decoding — centres the spinner in the pane, so opening a file shows one spinner in one position until the body appears. An unrendered PDF page holds its place as a static 3:4 placeholder block on the theme's skeleton token `--dsw-alias-bg-skeleton` with no spinner; specialising the document-open wait into the first page's placeholder was tried and reverted because the owner's centred read spinner precedes it, and the handoff visibly jumped positions. A shimmer animation on placeholders was considered and dropped for per-page animation cost.
 
 **PDF full-bleed and copy.** The PDF body and page insets are removed so pages fill the pane's width edge to edge; the image renderer keeps its own 12px inset. Chinese error and status lines across the preview dictionaries drop trailing full stops.
 
+**Files tree scroll restore.** `ui-sidebar-files` follows the document preview's own pattern: the tree store gains `scrollTop` with a `scrolled` action, the body records its scroll offset, and a remount restores it in a layout effect. Loaded levels already outlive the body in the store, so the remounted tree lays out at full height before the offset re-lands.
+
 ## Alternatives considered
 
 **A boolean `binary` flag per definition.** The image renderer covers both bitmaps and SVG under one registration, so binariness is a property of the suffix, not the renderer.
@@ -30,4 +32,4 @@ The Sidebar document preview accumulated several experience defects ([#3974](htt
 
 ## Consequences
 
-Images never scroll horizontally; the pane's width is the only layout input, so no zoom control was added. Bitmap and PDF tabs show no viewer control; SVG keeps the menu with the plain-text choice; unclaimed binary containers show the empty state without reading. From open to first content every preview shows one centred icon-only spinner, and PDF pages appear as quiet placeholder blocks. Registry unit tests pin `binaryDocumentPath` matching, toolbar tests pin the fallback/menu/empty-state branches, and the keyless Web document-preview scenario measures the width-fitted SVG against the pane and asserts the viewer control's absence per suffix.
+Images never scroll horizontally; the pane's width is the only layout input, so no zoom control was added. Bitmap and PDF tabs show no viewer control; SVG keeps the menu with the plain-text choice; unclaimed binary containers show the empty state without reading. From open to first content every preview shows one centred icon-only spinner, and PDF pages appear as quiet placeholder blocks. The file tree comes back where the reader left it after any tab switch. Registry unit tests pin `binaryDocumentPath` matching, toolbar tests pin the fallback/menu/empty-state branches, files-body tests pin scroll capture and restore across a remount, and the keyless Web document-preview scenario measures the width-fitted SVG against the pane and asserts the viewer control's absence per suffix.

+ 5 - 3
.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.zh.md

@@ -6,7 +6,7 @@ Status: implemented
 
 ## 问题
 
-侧边栏文档预览积累了多处体验缺陷([#3974](https://github.com/deepseek-harness/deepseek-harness/issues/3974))。图片按固有 CSS 像素尺寸渲染,宽图撑出面板、只能横向滚动。查看器下拉总是追加纯文本兜底,位图和 PDF 文件因此提供一个结果是不可读字节的「纯文本」选项,而只有一个真实渲染器的文件仍显示一个没有可切换项的控件。完全没有渲染器的二进制容器(视频、压缩包、office 文档)落入纯文本读取器,呈现的是读取报错而非设计过的空态。各渲染器自带不同的 loading 文案和位置,打开文件时会闪过多个措辞、位置都不同的指示器。PDF 页面被双层内边距包裹,每一页都窄于面板宽度。
+侧边栏文档预览积累了多处体验缺陷([#3974](https://github.com/deepseek-harness/deepseek-harness/issues/3974))。图片按固有 CSS 像素尺寸渲染,宽图撑出面板、只能横向滚动。查看器下拉总是追加纯文本兜底,位图和 PDF 文件因此提供一个结果是不可读字节的「纯文本」选项,而只有一个真实渲染器的文件仍显示一个没有可切换项的控件。完全没有渲染器的二进制容器(视频、压缩包、office 文档)落入纯文本读取器,呈现的是读取报错而非设计过的空态。各渲染器自带不同的 loading 文案和位置,打开文件时会闪过多个措辞、位置都不同的指示器。PDF 页面被双层内边距包裹,每一页都窄于面板宽度。切换侧栏 tab 会让文件树重挂载回到顶部,丢掉读者原来的位置。
 
 ## 决定
 
@@ -16,10 +16,12 @@ Status: implemented
 
 **不支持预览的空态。** owner 侧的 `document/unviewable.ts` 列出没有渲染器认领的二进制容器后缀(视频、音频、压缩包、office 文档、可执行文件、字体、磁盘镜像、设计格式)。owner 仅在没有实现匹配时查询该列表,因此任何渲染器注册始终优先;匹配的文件显示路径头部、文件类型图标和一行 `unsupportedFile` 说明,并且不会发起读取。不确定的后缀不进入列表、保留纯文本兜底;被文本认领但字节未通过读取器检查的文件通过 `error.notText` 报告同一句文案。
 
-**统一 loading。** `LoadingIndicator` 新增 `iconOnly`,把标签移入 `aria-label`、不渲染可见文字;所有渲染器的 loading 文案统一为共享的「正在读取…」。内容出现前的每个等待——owner 首次读取、PDF 解析、HTML 打包、图片解码——都把 spinner 居中在面板中,打开文件到正文出现始终是同一位置的一个 spinner。未渲染的 PDF 页以 `--dsw-alias-bg-layer-2` 上的静态 3:4 占位块保持位置、不带 spinner;曾尝试把文档打开等待特化进第一页的占位块,但 owner 的居中读取 spinner 在它之前,交接会明显跳位,因此回退。占位块的扫光动画因逐页动画的性能成本被考虑后放弃。
+**统一 loading。** `LoadingIndicator` 新增 `iconOnly`,把标签移入 `aria-label`、不渲染可见文字;所有渲染器的 loading 文案统一为共享的「正在读取…」。内容出现前的每个等待——owner 首次读取、PDF 解析、HTML 打包、图片解码——都把 spinner 居中在面板中,打开文件到正文出现始终是同一位置的一个 spinner。未渲染的 PDF 页以主题骨架色 `--dsw-alias-bg-skeleton` 上的静态 3:4 占位块保持位置、不带 spinner;曾尝试把文档打开等待特化进第一页的占位块,但 owner 的居中读取 spinner 在它之前,交接会明显跳位,因此回退。占位块的扫光动画因逐页动画的性能成本被考虑后放弃。
 
 **PDF 铺满与文案。** 移除 PDF 正文和页面的内边距,页面贴边占满面板宽度;图片渲染器保留自己的 12px 内边距。预览各字典的中文报错与状态文案去掉句尾句号。
 
+**文件树滚动恢复。** `ui-sidebar-files` 沿用文档预览自己的模式:树存储新增 `scrollTop` 与 `scrolled` action,正文记录自己的滚动偏移,重挂载时在 layout effect 中恢复。已加载的层本就在存储中比正文活得久,因此重挂载的树在偏移落回之前已按完整高度布局。
+
 ## 曾考虑的替代方案
 
 **按定义级布尔 `binary` 标志。** 图片渲染器在一个注册中同时覆盖位图和 SVG,二进制与否是后缀的属性,不是渲染器的属性。
@@ -30,4 +32,4 @@ Status: implemented
 
 ## 后果
 
-图片不再产生横向滚动;面板宽度是唯一布局输入,因此未增加缩放控件。位图和 PDF tab 不显示查看器控件;SVG 保留含纯文本选项的菜单;未被认领的二进制容器显示空态且不读取。从打开到首个内容,每种预览都只显示一个居中的纯图标 spinner,PDF 页面以安静的占位块出现。注册表单元测试固定 `binaryDocumentPath` 的匹配行为,工具栏测试固定兜底/菜单/空态分支,keyless Web document-preview 场景按面板测量适配宽度后的 SVG 并按后缀断言查看器控件的有无。
+图片不再产生横向滚动;面板宽度是唯一布局输入,因此未增加缩放控件。位图和 PDF tab 不显示查看器控件;SVG 保留含纯文本选项的菜单;未被认领的二进制容器显示空态且不读取。从打开到首个内容,每种预览都只显示一个居中的纯图标 spinner,PDF 页面以安静的占位块出现。任何 tab 切换之后,文件树都回到读者离开的位置。注册表单元测试固定 `binaryDocumentPath` 的匹配行为,工具栏测试固定兜底/菜单/空态分支,files-body 测试固定滚动捕获与跨重挂载的恢复,keyless Web document-preview 场景按面板测量适配宽度后的 SVG 并按后缀断言查看器控件的有无。

+ 1 - 1
packages/client/ui-sidebar-documentpreview/src/client/pdf/PdfBody.module.css

@@ -21,7 +21,7 @@
 .placeholder {
   width: 100%;
   aspect-ratio: 3 / 4;
-  background: var(--dsw-alias-bg-layer-2);
+  background: var(--dsw-alias-bg-skeleton);
 }
 
 .canvas {