Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/specs/manual-management/implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
| --- | --- | --- |
| 后端 | 三表、多分类、字面名称检索、草稿/发布、混合资料、封面、幂等及 revision | Go 构建、完整主测试、DAL 和跨实例配额重复测试通过 |
| 权限与文件 | 精确 session 事务复核、应用 scope、各内容入口共用权限、格式/尺寸/配额、私有存储 | HTTP 权限矩阵、撤销/重复请求/删除竞态、EXIF/PDF 实际处理通过;故障注入边界见验证记录 |
| 前端 | 列表/编辑/阅读、移动布局、多次选图队列、局部重试、分类分页、保护路由 | Node 92/92、说明书浏览器 8/8、账号浏览器 6/6、生产构建通过;系统相册待实机验收 |
| 前端 | 列表/编辑/阅读、移动布局、多次选图队列、上传及排序缩略图、内嵌 PDF 阅读、局部重试、分类分页、保护路由 | Node 92/92、说明书浏览器 9/9、账号浏览器 6/6、生产构建通过;系统相册待实机验收 |
| Skill | 9 个说明书命令、批量输入、dry-run、写后回读、逐项稳定 ID 恢复 | 新旧 Python/HTTPS 33/33 与独立前向验证通过 |
| 集成与审查 | 导航/scopes、CI、双入口代理、部署及回滚说明、完整产物校验 | 独立静态审查无未处理 P1/P2;候选网关实际 Nginx 校验通过 |
| 发布 | 提交并推送、创建 PR、GitHub CI、按精确 SHA 更新 pi 与本地稳定 skill | 以 PR 和发布记录中的最终提交、CI 与现场验收为准;PR 保持打开由用户合并 |
Expand All @@ -34,3 +34,9 @@
验证结果单独记录于 `verification.md`,必须区分已运行、通过、失败和未覆盖。禁止把 Go 编译替代权限集成测试、把静态源码匹配替代浏览器行为、把 `/ping` 替代新功能验证。

部署记录只包含版本号、哈希、路径和状态,不包含私有域名凭据、DSN、AK/SK 或 Cookie。原仓库未跟踪的 `agents.md` 不纳入提交。

## 阅读与排序交互

编辑器对待上传图片使用页面生命周期内的本地缩略图,对 MIME 为 `application/pdf` 的待上传文件使用惰性首页预览;只带 `.pdf` 文件名但 MIME 不符的文件显示类型占位,不进入同源 iframe。移除资料、上传完成或离开页面时释放本地 object URL。已保存图片和 PDF 直接使用受权限保护的服务端缩略图,排序只移动资料对象,缩略图始终随对应资料移动。

详情页使用现有 `content_url` 嵌入浏览器原生 PDF 阅读器,并保留新窗口与下载入口作为兼容降级。资料序号、类型和原始文件名调整为辅助层级;自定义标题继续作为普通标题展示。该变化不增加依赖,也不修改 API、数据表、权限模型或文件响应规则。
38 changes: 38 additions & 0 deletions docs/specs/manual-management/interaction-optimization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 说明书阅读与排序交互优化

日期:2026-09-19。设计基线:`master` `1db4129`。

## 目标

降低用户上传、排序和阅读说明书时的识别成本。调整只涉及说明书前端页面,沿用现有资料 API、权限检查、原件与缩略图接口,不改变存储、数据库或访问控制。

| 场景 | 调整后行为 |
| --- | --- |
| 上传队列 | 图片显示本地缩略图;PDF 显示惰性加载的首页内嵌预览;文本和网址保留清晰的类型占位 |
| 已保存资料排序 | 图片和 PDF 使用服务端缩略图;缩略图不可用时显示类型占位,不影响排序与删除 |
| 说明书详情 | PDF 在页面内使用浏览器原生阅读器快速浏览,同时保留新窗口打开和下载入口 |
| 资料标题区域 | 序号、类型和原始文件名使用弱化的小号文字,图片或 PDF 内容成为卡片主体 |

## 方案选择

采用浏览器原生 PDF 阅读能力。它能直接复用现有支持登录校验和 Range 请求的 `content_url`,不增加依赖,也不增加服务端接口。待上传文件使用短生命周期的 `blob:` URL:图片通过 `img` 显示,PDF 通过禁用交互的小型内嵌阅读区显示首页;移除条目、上传完成或离开页面时释放 URL。

不引入 PDF.js。PDF.js 能提供统一工具栏和分页体验,但会增加依赖、构建体积与后续安全升级成本,当前“快速浏览”无需承担这些成本。也不新增服务端临时预览接口,避免在正式创建说明书前引入临时文件生命周期和权限状态。

## 页面结构

编辑页的待上传资料卡片和已保存资料卡片都采用“缩略图 + 信息 + 操作”的结构。桌面端缩略图位于左侧,移动端位于上方;顺序按钮仍使用现有数组移动逻辑,预览只跟随资料对象移动,不参与排序状态。

详情页取消高对比度圆形序号和大号文件名。每项顶部显示低对比度的“资料 N · 类型”,自定义标题保留为普通标题,原始文件名降为辅助说明。PDF 阅读区在桌面端提供接近视口高度的浏览区域,移动端压缩高度;阅读器不可用时,用户仍可通过原有链接打开或下载原件。

## 安全与兼容性

- PDF 和缩略图继续从原有受保护接口读取,不绕过 owner、authenticated、public 权限。
- 内嵌阅读器不执行站外内容,不改变 URL 资料的 `noopener noreferrer` 约束。
- 本地预览 URL 只存在于当前页面生命周期,不写入存储、日志或 API。
- Safari 或移动浏览器不能内嵌 PDF 时,保留的新窗口与下载入口作为可见降级路径。
- 缩略图加载失败只影响预览,不阻止上传、排序、保存或删除。

## 验收

浏览器测试覆盖待上传图片/PDF 预览、已保存资料缩略图、排序后预览与资料对应关系、详情 PDF 内嵌阅读器、弱化元信息、下载链接以及 320 px 宽度下无横向溢出。交付前运行前端单元测试、说明书浏览器测试、lint 和生产构建,并在 pi 上验证真实 PDF、图片、登录权限和既有页面回归。
4 changes: 3 additions & 1 deletion docs/specs/manual-management/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
| 独立文件边界 | pi 实际 pdftoppm 与格式解码 | PDF 预览最长边 ≤480、损坏 PNG 拒绝且无最终目录、损坏 PDF 保留原件并标记不可预览、超限文件拒绝均通过 |
| 上传预留并发 | 32 路并发、10 轮 | 每轮 1 成功、31 限流;该项与数据库配额测试分别验证 |
| 前端行为 | macOS,`npm run test:frontend` | 92/92 通过 |
| 说明书浏览器 | 实际 Vue 页面与 Chrome,`npm run test:manuals-browser` | 8/8 通过,覆盖 320 px、多次选图追加、慢上传期间写互斥、部分失败重试、409、revision 类型及混合资料阅读 |
| 说明书浏览器 | 实际 Vue 页面与 Chrome,`npm run test:manuals-browser` | 9/9 通过,覆盖 320 px、多次选图追加、上传及排序缩略图、object URL 释放、伪 PDF 脚本隔离、生产同款 CSP 下的内嵌 PDF 阅读、慢上传期间写互斥、部分失败重试、409、revision 类型及混合资料阅读 |
| 账号浏览器回归 | Chrome,`npm run test:account-center-browser` | 6/6 通过 |
| 前端构建与语法 | 变更文件 ESLint;`VUE_APP_API_BASE_URL=/ npm run build` | 通过;保留既有 bundle 大小及 caniuse-lite 警告 |
| Python 与 skill | 实际本机合成 HTTPS fixture,`test_manuals_skill`、`test_web_hosting_skill`、`test_home_server_api` | 33/33 通过:新 skill 5 项、旧 skill 15 项、客户端 13 项 |
| Skill 独立前向验证 | 合成 HTTPS、假应用凭据及临时 CA | 2 张图片、PDF、文本、URL、多分类、失败恢复、排序/封面/激活、筛选与旧命令兼容通过;未请求生产凭据 |
| 网关候选配置 | 实际 TLS 网关,备份原配置并对候选配置执行 `nginx -t` | 通过;提交前未替换生效配置或 reload |
| 文档与差异 | mmdc + Chrome、公开部署模板检查、`git diff --check` | 1 张 Mermaid 成功渲染,模板与格式检查通过 |

本次交互调整先运行新增浏览器用例并观察到 2 项预期失败:编辑器不存在缩略图节点,详情页不存在弱化元信息和 PDF iframe。独立审查随后发现仅凭 `.pdf` 后缀预览会把 HTML MIME 的 blob 放进同源 iframe;新增主动脚本载荷后旧实现为 8/9,通过按 `application/pdf` MIME 决定本地 PDF 预览关闭该链路,同时覆盖无扩展名的合法 PDF。有效 PDF fixture 返回与生产一致的 `Content-Security-Policy: sandbox`,测试确认 Chrome 内置 PDF 阅读器 frame 已加载;另用真实 Chrome 独立截图确认文档页实际可见。最终说明书浏览器测试 9/9 通过;完整前端 Node 测试 92/92 通过;变更文件 ESLint 通过;生产构建成功,只有既有 caniuse-lite 和 bundle 大小提示。全仓 `npm run lint` 仍会在未修改的 `Common.vue`、`AddBook.vue` 和 `ScanCodePage.vue` 报告 4 个基线错误,因此不把全仓 lint 记为通过。

完整主测试覆盖 DB 与 legacy/config 会话撤销、重复创建回放、上传中撤销、重复上传时撤销或删除、同说明书条目配额、跨说明书用户字节配额、分类二页游标、真实 pdftoppm PDF 最长边不超过 480 px,以及超过 4 KiB 的 APP1 EXIF 方向处理。测试中的真实权限矩阵同时检查原件、缩略图、HEAD、Range、所有者禁用和草稿/删除状态。

多个 pending 项恢复测试逐项保留原 request ID,断言无重复资料且最终顺序完整;不能把剩余多项重新组合成批次重编号。独立前向验证另外实际执行了 8 次 token exchange 和 16 次 HTTPS 业务调用。
Expand Down
121 changes: 121 additions & 0 deletions docs/superpowers/plans/2026-09-19-manual-preview-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Manual Preview UI Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add visual previews to manual upload/reorder cards, embed PDF reading in the detail page, and reduce the visual emphasis of item numbers and filenames.

**Architecture:** Keep the existing manuals API and data model unchanged. `ManualEditor.vue` owns short-lived local `blob:` preview URLs and reuses persisted `thumbnail_url` values; `ManualDetail.vue` uses the authenticated `content_url` in the browser-native PDF viewer and retains explicit open/download fallbacks.

**Tech Stack:** Vue 3 Options API, Element Plus, native object URLs and PDF iframe support, Node test runner, Playwright Chromium.

---

### Task 1: Specify editor previews and PDF reading behavior

**Files:**
- Modify: `front_vue/test/manuals-browser.cjs`

- [x] **Step 1: Add a failing editor browser test**

Add a test that opens `/manuals/500/edit?as=owner` with saved image and PDF items, selects one local PNG and one local PDF, then asserts:

```js
assert.equal(await page.locator('.queue-preview-image').count(), 1)
assert.equal(await page.locator('.queue-preview-pdf').count(), 1)
assert.equal(await page.locator('.saved-thumbnail').count(), 2)
await page.locator('.upload-queue-item').filter({hasText: 'local.pdf'}).getByRole('button', {name: '上移'}).click()
assert.match(await page.locator('.upload-queue-item').first().innerText(), /local\.pdf/)
```

- [x] **Step 2: Extend the detail browser test and verify RED**

Assert that the PDF item contains one `iframe.pdf-viewer`, its `src` is the protected `content_url`, the metadata uses `.item-meta`, and the former circular index selector is absent.

Run:

```bash
NODE_PATH=/Users/bytedance/go/src/github.com/mcoder2014/home_server/front_vue/node_modules node --test test/manuals-browser.cjs
```

Expected: FAIL because the preview and viewer selectors do not exist.

### Task 2: Implement editor thumbnails with bounded object URL lifetime

**Files:**
- Modify: `front_vue/src/views/ManualEditor.vue`
- Test: `front_vue/test/manuals-browser.cjs`

- [x] **Step 1: Render previews without changing queue semantics**

Add a preview block before each queue item's fields. Local images use an `img`; local PDFs use a lazy, non-interactive iframe. Saved image/PDF items use `thumbnail_url`; missing thumbnails render a type placeholder. Text and URL items keep compact type placeholders.

- [x] **Step 2: Manage local object URLs**

After `appendFileItems`, assign an object URL only to accepted image/PDF queue items. Revoke it when an item is removed, after a fully successful upload clears the queue, and in `beforeUnmount`. Moving an item must preserve its URL and identity.

- [x] **Step 3: Add responsive styling**

Use a fixed thumbnail column on desktop and a full-width preview above metadata below 640 px. Preview failures must not disable move, remove, upload, cover selection, or delete actions.

- [x] **Step 4: Run the browser test and verify GREEN**

Run the Task 1 command. Expected: all manuals browser tests pass.

### Task 3: Embed PDFs and quiet item metadata

**Files:**
- Modify: `front_vue/src/views/ManualDetail.vue`
- Test: `front_vue/test/manuals-browser.cjs`

- [x] **Step 1: Replace the prominent item heading**

Render `资料 N · 类型` as muted metadata. Render an explicit custom title as a modest heading and show `original_name` as small secondary text, avoiding duplicate title/filename output.

- [x] **Step 2: Add the native PDF viewer**

Render:

```vue
<iframe class="pdf-viewer" :src="item.content_url" :title="pdfViewerTitle(item)" loading="lazy"></iframe>
```

Keep the existing new-window and download links directly below it. Preserve the preview-unavailable notice and mobile fallback layout.

- [x] **Step 3: Run focused tests and verify GREEN**

Run the Task 1 command. Expected: all manuals browser tests pass, including mobile width and link-safety assertions.

### Task 4: Verify, document, review, and publish

**Files:**
- Modify: `docs/specs/manual-management/implementation.md`
- Modify: `docs/specs/manual-management/verification.md`

- [x] **Step 1: Record final behavior and verification evidence**

Update the existing implementation and verification documents with the editor thumbnails, embedded PDF reader, metadata hierarchy, exact commands, and observed results.

- [x] **Step 2: Run the full frontend verification**

Run from `front_vue` with the main checkout's dependency directory on `NODE_PATH`/`PATH`:

```bash
npm run test:frontend
npm run test:manuals-browser
npm run lint
npm run build
```

Expected: zero failures and a successful production build.

- [x] **Step 3: Commit and dispatch independent review**

Commit the implementation, then give the review agent the base SHA, head SHA, requirements, design document, and validation results. Fix every blocking or important finding and rerun the affected checks.

- [ ] **Step 4: Create and merge the PR**

Push `feat/cq/manuals_ui`, create a PR describing the visible behavior and tests, wait for GitHub checks, and merge only if the independent review and required checks are clean.

- [ ] **Step 5: Deploy the merged commit to pi**

Build and stage the exact merged commit using the existing release-directory process, retain the previous release for rollback, switch `current`, restart services, and verify the embedded PDF, editor previews, permissions, `/ping`, and existing modules before removing the feature worktree.
Loading
Loading