dsh-snap-line 1.3.0 → 1.3.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.en.md CHANGED
@@ -17,6 +17,7 @@ SnapLine is a UI bundle for [DeepSeek Harness](https://github.com/deepseek-ai).
17
17
  ## Table of contents
18
18
 
19
19
  - [Features](#features)
20
+ - [Screenshots](#screenshots)
20
21
  - [Install](#install)
21
22
  - [Use your own photos](#use-your-own-photos)
22
23
  - [How it works](#how-it-works)
@@ -33,18 +34,39 @@ SnapLine is a UI bundle for [DeepSeek Harness](https://github.com/deepseek-ai).
33
34
  - **Five built-in illustrations**: the line is never empty when `photos/` has no images;
34
35
  - **Your own photos**: `.jpg` `.jpeg` `.png` `.webp` `.gif` `.avif`, natural filename order, live refresh within five seconds after adding, removing or replacing files;
35
36
  - **Adaptive layout**: up to six photos, evenly sampled when there are more; the more photos, the tighter they sit, each with its own tilt and sway rhythm;
36
- - **Restrained interaction**: hovering gives a photo a small nudge that settles back; clicking opens a lightbox. Only CSS `:hover`, `transform` and a `react-dom` portal — no JS listeners, no extra re-renders;
37
+ - **Restrained interaction**: hovering only uses CSS `:hover` and `transform` to give a photo a small nudge that settles back; clicking opens a lightbox — no mouse listeners, no extra re-renders;
38
+ - **Keyboard friendly**: photos are focusable with Tab and open the lightbox with Enter or Space; while the lightbox is open Tab stays on its close button, Escape / backdrop / close button all dismiss it, and focus returns to the photo that opened it;
39
+ - **Named for screen readers**: the clothesline, every photo, the lightbox and its close button carry bilingual Chinese/English accessible names;
37
40
  - **Respects system preferences**: `prefers-reduced-motion` disables the sway animation;
38
41
  - **Zero runtime dependencies**: plain ESM, no build step; `react` / `react-dom` come from the Harness module table;
39
42
  - **Photos never leave your machine**: the Host half serves them over the Harness web server's same-origin route; the plugin makes no external requests.
40
43
 
44
+ ## Screenshots
45
+
46
+ | New Session hero | Lightbox |
47
+ | --- | --- |
48
+ | ![New Session hero with the enlarged clothesline](docs/images/hero.png) | ![Lightbox with an enlarged photo](docs/images/lightbox.png) |
49
+
41
50
  ## Install
42
51
 
43
- SnapLine is a Harness bundle, so installation is done by an agent inside Harness through `plugin_manager`.
52
+ SnapLine is a Harness bundle, so it can be installed from npm, GitHub or a local directory — an agent inside Harness does it through `plugin_manager`.
53
+
54
+ ### Option 1: npm (recommended)
55
+
56
+ Published on npm as [`dsh-snap-line`](https://www.npmjs.com/package/dsh-snap-line).
57
+
58
+ - Ask the Harness agent to install `dsh-snap-line` with `plugin_manager`'s `install_bundle`; or
59
+ - run this in a terminal:
60
+
61
+ ```bash
62
+ dsh plugin --profile <profile> add dsh-snap-line
63
+ ```
44
64
 
45
- ### Option 1: let the Harness agent install it (recommended)
65
+ DSH installs it from npm, composes the Host half from `dsh.bundle.patch` and loads the Client half from `dsh.client`. Once the plugin is listed in the community plugin market (awesome-dsh-plugin), it can also be installed by searching for it there.
46
66
 
47
- Send the repository URL to the Harness agent and ask it to install the bundle with `plugin_manager`'s `install_bundle`:
67
+ ### Option 2: install from GitHub
68
+
69
+ Send the repository URL to the Harness agent and ask it to install the bundle with `install_bundle`:
48
70
 
49
71
  ```text
50
72
  https://github.com/LMhuo/dsh-snap-line.git
@@ -54,7 +76,7 @@ The `github:LMhuo/dsh-snap-line` shorthand works too. DSH installs it into the c
54
76
 
55
77
  When it finishes, the plugin appears in Harness's plugin list (the bundle list in `~/.dsh/profiles/<profile>/package.json`) and starts rendering under the composer.
56
78
 
57
- ### Option 2: clone locally, then install (best for development)
79
+ ### Option 3: clone locally, then install (best for development)
58
80
 
59
81
  ```bash
60
82
  git clone https://github.com/LMhuo/dsh-snap-line.git
@@ -62,14 +84,6 @@ git clone https://github.com/LMhuo/dsh-snap-line.git
62
84
 
63
85
  Then ask the Harness agent to install that directory's **absolute path** with `install_bundle`. It writes a `link:` dependency into the current profile's `package.json` and adds it to the bundle list — no manual profile editing.
64
86
 
65
- ### Option 3: npm
66
-
67
- Published on npm as [`dsh-snap-line`](https://www.npmjs.com/package/dsh-snap-line). Ask the Harness agent to install `dsh-snap-line` with `plugin_manager`'s `install_bundle` — DSH installs it from npm and loads both the Host and Client halves automatically. Or from a terminal:
68
-
69
- ```bash
70
- dsh plugin --profile <profile> add dsh-snap-line
71
- ```
72
-
73
87
  ### Disable and remove
74
88
 
75
89
  - Toggle the plugin off in Harness's plugin settings; or
@@ -82,7 +96,9 @@ The photo folder is **`photos/` inside the plugin package**:
82
96
  | Install style | Photo folder |
83
97
  | --- | --- |
84
98
  | Local directory / `link:` | `photos/` in this repository |
85
- | GitHub / npm install | `~/.dsh/profiles/<profile>/node_modules/dsh-snap-line/photos/` |
99
+ | npm / GitHub install | `~/.dsh/profiles/<profile>/node_modules/dsh-snap-line/photos/` |
100
+
101
+ > npm and GitHub installs ship without a `photos/` folder (private photos are never packed), so create that folder yourself.
86
102
 
87
103
  Drop images in and that's it:
88
104
 
@@ -103,6 +119,7 @@ photos/<name> ── GET /dsh-snap-line/photos/<name> ────────
103
119
 
104
120
  - **Host half** (`index.js`): registers a `kind: 'prefix'` route at `/dsh-snap-line` for the photo manifest and the images. `lib/photos.js` is plain Node and rejects path traversal, hidden filenames and unknown media types, so it is unit-testable on its own;
105
121
  - **Client half** (`client.js`): registers in the `conversation.composer.dock` slot (`order: 2`) and polls the manifest every five seconds, rendering user photos or the built-in illustrations;
122
+ - **Lightbox**: open it by clicking a photo, or by focusing one with Tab and pressing Enter / Space; Escape, the backdrop or the close button dismiss it, focus returns to the photo, and the page behind it does not scroll while it is open.
106
123
  - **New Session hero**: the Host renders `conversation.composer.dock` only for an active Session, so the blank hero has just an empty `[data-composer-dock]` container. The Client half also registers `HeroClothesline` on `shell.overlay`, checks every 250 ms for `[data-content-phase="hero"] [data-composer-dock]`, and portals the same clothesline into it via `react-dom`'s `createPortal`, scaled to 2× the measured composer width (capped at 600px). Once a real conversation mounts its dock entry, the hero copy steps aside; if `react-dom` or `shell.overlay` is unavailable it silently skips, leaving the normal dock rendering intact;
107
124
  - the dock slot is a fit-content flex row, so an SVG with `width: 100%` collapses to its intrinsic 300px — the conversation clothesline stays ~300px wide. The hero copy therefore uses an explicit pixel width instead of `max-width`.
108
125
 
@@ -115,7 +132,7 @@ npm run check # syntax check + full test suite
115
132
 
116
133
  No build step and no runtime dependencies. Editing `client.js` hot-reloads the browser half via HMR; editing `index.js` needs the plugin entry to be disabled and re-enabled once.
117
134
 
118
- For local debugging, install this repository as a `link:` bundle (option 2) so edits take effect immediately.
135
+ For local debugging, install this repository as a `link:` bundle (option 3) so edits take effect immediately.
119
136
 
120
137
  ### Repository layout
121
138
 
@@ -126,9 +143,10 @@ dsh-snap-line/
126
143
  ├── client.js # Client half: clothesline SVG, slot registration, lightbox
127
144
  ├── cordis.patch.yml # Host plugin row inserted by the bundle
128
145
  ├── locale/ # English and Chinese display metadata for the plugin manager
129
- ├── icon.svg # Plugin icon
146
+ ├── icon.svg # Vector icon shown in the plugin list
147
+ ├── screenshots.json # Market screenshot list (points at docs/images)
130
148
  ├── photos/ # Your photos (not committed by default)
131
- ├── docs/images/ # Static assets such as the README preview
149
+ ├── docs/ # README previews, market screenshots and the PNG icon
132
150
  ├── test/ # node:test suites
133
151
  └── .github/workflows/ # GitHub Actions CI
134
152
  ```
@@ -161,7 +179,10 @@ Yes — edit `MAX_PHOTOS` (count), `HERO_SVG_WIDTH` (hero width) and `TILTS` / `
161
179
  Currently it uses a fixed warm paper palette and does not follow the Harness theme. PRs welcome.
162
180
 
163
181
  **How do I upgrade?**
164
- Git installs have no version list: ask the agent to `install_bundle` the latest commit again, or `remove_bundle` first and reinstall. Every release is documented in [CHANGELOG.md](CHANGELOG.md).
182
+ npm installs: check for updates in the plugin market, or ask the agent to `install_bundle` again. Git installs: ask the agent to install the latest commit again, or `remove_bundle` first and reinstall. Every release is documented in [CHANGELOG.md](CHANGELOG.md).
183
+
184
+ **Can I use it with a keyboard?**
185
+ Yes. Photos are focusable with Tab and Enter / Space opens the lightbox; Tab stays on the close button while it is open, Escape closes it, and focus returns to the photo.
165
186
 
166
187
  **Is commercial use allowed?**
167
188
  Yes, under the MIT license.
package/README.md CHANGED
@@ -17,6 +17,7 @@ SnapLine 是 [DeepSeek Harness](https://github.com/deepseek-ai) 的界面插件
17
17
  ## 目录
18
18
 
19
19
  - [特性](#特性)
20
+ - [截图](#截图)
20
21
  - [安装](#安装)
21
22
  - [使用自己的照片](#使用自己的照片)
22
23
  - [工作原理](#工作原理)
@@ -33,18 +34,39 @@ SnapLine 是 [DeepSeek Harness](https://github.com/deepseek-ai) 的界面插件
33
34
  - **自带五张手绘插画**:`photos/` 为空时使用内置插画,不会出现空荡荡的绳子;
34
35
  - **换成自己的照片**:支持 `.jpg` `.jpeg` `.png` `.webp` `.gif` `.avif`,按文件名自然排序,增删后 5 秒内自动刷新;
35
36
  - **排版自适应**:最多显示 6 张,超过时均匀抽样;照片越多排得越密,每张有自己的倾斜角度与摆动节奏;
36
- - **交互克制**:悬停时轻轻晃一下再回正,点击打开灯箱预览;只用 CSS `:hover`、`transform` 与 `react-dom` portal,没有 JS 监听、没有多余重渲染;
37
+ - **交互克制**:悬停只靠 CSS `:hover` 与 `transform` 让照片轻轻晃一下再回正,点击打开灯箱预览,没有鼠标监听、没有多余重渲染;
38
+ - **键盘友好**:照片可以用 Tab 聚焦,Enter / 空格打开灯箱;灯箱打开后 Tab 被约束在关闭按钮上,Esc、点击背景或关闭按钮都能退出,关闭后焦点回到打开它的照片;
39
+ - **读屏器可读**:晾衣绳、每张照片、灯箱与关闭按钮都有中英双语的无障碍名称;
37
40
  - **尊重系统偏好**:`prefers-reduced-motion` 开启时不做摆动动画;
38
41
  - **零运行时依赖**:纯 ESM、无构建步骤,`react` / `react-dom` 复用 Harness 内置模块;
39
42
  - **照片不出本机**:Host 半通过 Harness web server 的同源路由提供图片,插件不发起任何外部请求。
40
43
 
44
+ ## 截图
45
+
46
+ | 新会话首页 | 灯箱预览 |
47
+ | --- | --- |
48
+ | ![新会话首页:晾衣绳会自动放大](docs/images/hero.png) | ![灯箱预览:点击照片放大](docs/images/lightbox.png) |
49
+
41
50
  ## 安装
42
51
 
43
- SnapLine 是 Harness 的 bundle 插件,安装由 Harness 里的 Agent 通过 `plugin_manager` 完成。
52
+ SnapLine 是 Harness 的 bundle 插件,可以走 npm、GitHub 或本地目录;安装由 Harness 里的 Agent 通过 `plugin_manager` 完成。
53
+
54
+ ### 方式一:npm(推荐)
55
+
56
+ 已发布到 npm:[`dsh-snap-line`](https://www.npmjs.com/package/dsh-snap-line)。
57
+
58
+ - 让 Harness Agent 用 `plugin_manager` 的 `install_bundle` 安装 `dsh-snap-line`;
59
+ - 或在终端运行:
60
+
61
+ ```bash
62
+ dsh plugin --profile <profile> add dsh-snap-line
63
+ ```
44
64
 
45
- ### 方式一:交给 Harness Agent 安装(推荐)
65
+ DSH 会从 npm 装好,并按 `dsh.bundle.patch` 组合 Host 半、按 `dsh.client` 加载 Client 半。收录到社区插件市场(awesome-dsh-plugin)后,也可以直接在 Harness 的插件市场里搜索安装。
46
66
 
47
- 把仓库地址发给 Harness Agent,并让它用 `plugin_manager` 的 `install_bundle` 安装:
67
+ ### 方式二:从 GitHub 安装
68
+
69
+ 把仓库地址发给 Harness Agent,并让它用 `install_bundle` 安装:
48
70
 
49
71
  ```text
50
72
  https://github.com/LMhuo/dsh-snap-line.git
@@ -54,7 +76,7 @@ https://github.com/LMhuo/dsh-snap-line.git
54
76
 
55
77
  安装成功后,插件会出现在 Harness 的插件列表(`~/.dsh/profiles/<profile>/package.json` 的 bundle 列表)里,并自动在输入框下方生效。
56
78
 
57
- ### 方式二:本地克隆后安装(开发 / 调试推荐)
79
+ ### 方式三:本地克隆后安装(开发 / 调试推荐)
58
80
 
59
81
  ```bash
60
82
  git clone https://github.com/LMhuo/dsh-snap-line.git
@@ -62,14 +84,6 @@ git clone https://github.com/LMhuo/dsh-snap-line.git
62
84
 
63
85
  然后让 Harness Agent 用 `plugin_manager` 的 `install_bundle` 安装这个目录的**绝对路径**。`install_bundle` 会在当前 profile 的 `package.json` 中写入 `link:` 依赖并加入 bundle 列表,无需手动改 profile 文件。
64
86
 
65
- ### 方式三:npm
66
-
67
- 已发布到 npm:[`dsh-snap-line`](https://www.npmjs.com/package/dsh-snap-line)。让 Harness Agent 用 `plugin_manager` 的 `install_bundle` 安装 `dsh-snap-line` 即可,DSH 会从 npm 装好并自动加载 Host 半与 Client 半;也可以直接在终端运行:
68
-
69
- ```bash
70
- dsh plugin --profile <profile> add dsh-snap-line
71
- ```
72
-
73
87
  ### 停用与卸载
74
88
 
75
89
  - 在 Harness 的插件设置里停用;
@@ -82,7 +96,9 @@ dsh plugin --profile <profile> add dsh-snap-line
82
96
  | 安装方式 | 照片目录 |
83
97
  | --- | --- |
84
98
  | 本地目录 / `link:` 安装 | 本仓库的 `photos/` |
85
- | GitHub / npm 安装 | `~/.dsh/profiles/<profile>/node_modules/dsh-snap-line/photos/` |
99
+ | npm / GitHub 安装 | `~/.dsh/profiles/<profile>/node_modules/dsh-snap-line/photos/` |
100
+
101
+ > npm 与 GitHub 安装的包默认不带 `photos/` 目录(私人照片不会被打进包),需要自己新建这个文件夹。
86
102
 
87
103
  把图片放进去即可:
88
104
 
@@ -103,6 +119,7 @@ photos/<name> ── GET /dsh-snap-line/photos/<name> ────────
103
119
 
104
120
  - **Host 半**(`index.js`):用 `webServer.register({ kind: 'prefix', path: '/dsh-snap-line' })` 提供照片清单与图片。`lib/photos.js` 是纯 Node 实现,拒绝目录穿越、隐藏文件名和未知媒体类型,可独立测试;
105
121
  - **Client 半**(`client.js`):注册到 `conversation.composer.dock` 插槽(`order: 2`),每 5 秒轮询一次清单,按清单渲染照片或内置插画;
122
+ - **灯箱**:点击照片(或 Tab 聚焦后按 Enter / 空格)打开;Esc、点击背景或关闭按钮退出,关闭后焦点回到打开它的照片,灯箱打开期间背景不滚动。
106
123
  - **新会话首页**:Host 只在有 Session 时渲染 `conversation.composer.dock`,空白 Hero 里只有一个空的 `[data-composer-dock]` 容器。Client 半额外向 `shell.overlay` 注册 `HeroClothesline`,每 250ms 检查页面里是否存在 `[data-content-phase="hero"] [data-composer-dock]`,若有则通过 `react-dom` 的 `createPortal` 把同一条晾衣绳画进该容器,并按量到的输入框宽度放大到 2 倍(600px 封顶)。进入正常会话、dock 版出现后自动让位,不会重复显示;拿不到 `react-dom` 或 `shell.overlay` 时静默跳过,不影响 dock 渲染;
107
124
  - dock 插槽本身是 fit-content 的 flex 行,SVG 的 `width: 100%` 会退回固有 300px,所以会话页晾衣绳一直是 300px 左右;Hero 版因此使用显式像素宽度,而不是改 `max-width`。
108
125
 
@@ -115,7 +132,7 @@ npm run check # 语法检查 + 全部测试
115
132
 
116
133
  没有构建步骤,也没有运行时依赖。修改 `client.js` 会通过 HMR 自动刷新浏览器半;修改 `index.js` 后需要在插件设置里停用再启用一次。
117
134
 
118
- 本地调试时推荐用上面的「方式二」以 `link:` 方式安装本仓库,这样改代码即时生效。
135
+ 本地调试时推荐用上面的「方式三」以 `link:` 方式安装本仓库,这样改代码即时生效。
119
136
 
120
137
  ### 目录结构
121
138
 
@@ -126,9 +143,10 @@ dsh-snap-line/
126
143
  ├── client.js # Client 半:晾衣绳 SVG、插槽注册、灯箱
127
144
  ├── cordis.patch.yml # bundle 插入的 Host 插件行
128
145
  ├── locale/ # 插件管理器中显示的中英文标题与描述
129
- ├── icon.svg # 插件图标
146
+ ├── icon.svg # 插件列表里显示的矢量图标
147
+ ├── screenshots.json # 插件市场卡片用的截图清单(指向 docs/images)
130
148
  ├── photos/ # 你自己的照片(默认不提交)
131
- ├── docs/images/ # README 预览图等静态资源
149
+ ├── docs/ # README 预览图、市场截图与 PNG 图标
132
150
  ├── test/ # node:test 测试
133
151
  └── .github/workflows/ # GitHub Actions CI
134
152
  ```
@@ -161,7 +179,10 @@ dsh-snap-line/
161
179
  目前使用固定的暖色纸张配色,不随 Harness 主题切换;欢迎提交 PR。
162
180
 
163
181
  **怎么升级到新版本?**
164
- Git 方式安装没有版本列表:让 Agent 重新用 `install_bundle` 安装最新提交,或先 `remove_bundle` 再安装。每个版本的变更见 [CHANGELOG.md](CHANGELOG.md)。
182
+ npm 安装:在插件市场里检查更新,或让 Agent 重新用 `install_bundle` 安装;Git 安装:让 Agent 重新安装最新提交,或先 `remove_bundle` 再安装。每个版本的变更见 [CHANGELOG.md](CHANGELOG.md)。
183
+
184
+ **可以用键盘操作吗?**
185
+ 可以。照片可以用 Tab 聚焦,Enter / 空格打开灯箱;灯箱打开后 Tab 被约束在关闭按钮上,Esc 关闭,关闭后焦点回到照片。
165
186
 
166
187
  **可以商用吗?**
167
188
  可以,MIT 协议。
package/client.js CHANGED
@@ -48,6 +48,15 @@ window.__ModuleLoader__.load({
48
48
  .sl-photo {
49
49
  pointer-events: auto;
50
50
  touch-action: manipulation;
51
+ cursor: pointer;
52
+ }
53
+ .sl-photo:focus {
54
+ outline: none;
55
+ }
56
+ .sl-photo:focus-visible {
57
+ outline: 2px solid #a98d63;
58
+ outline-offset: 2px;
59
+ border-radius: 4px;
51
60
  }
52
61
  @media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
53
62
  .sl-photo:hover .sl-wobble {
@@ -111,6 +120,13 @@ window.__ModuleLoader__.load({
111
120
  .sl-lightbox-close:hover {
112
121
  background: #ffffff;
113
122
  }
123
+ .sl-lightbox-close:focus {
124
+ outline: none;
125
+ }
126
+ .sl-lightbox-close:focus-visible {
127
+ outline: 2px solid #f0e2c4;
128
+ outline-offset: 2px;
129
+ }
114
130
  @keyframes sl-lightbox-fade {
115
131
  from { opacity: 0; }
116
132
  to { opacity: 1; }
@@ -333,11 +349,20 @@ window.__ModuleLoader__.load({
333
349
  }
334
350
 
335
351
  function Photo(props) {
336
- const { x, tilt, delay, duration, content, onOpen } = props;
352
+ const { x, tilt, delay, duration, content, onOpen, label } = props;
353
+ const onKeyDown = (event) => {
354
+ if (event.key !== 'Enter' && event.key !== ' ' && event.key !== 'Spacebar') return;
355
+ event.preventDefault();
356
+ onOpen();
357
+ };
337
358
  return h('g', {
338
359
  className: 'sl-photo',
339
360
  transform: 'translate(' + x + ' ' + ropeY(x).toFixed(2) + ')',
361
+ role: 'button',
362
+ tabIndex: 0,
363
+ 'aria-label': label,
340
364
  onClick: onOpen,
365
+ onKeyDown,
341
366
  },
342
367
  h('g', { transform: 'rotate(' + tilt + ')' },
343
368
  h('g', { transform: 'scale(' + PHOTO_SCALE + ')' },
@@ -358,19 +383,34 @@ window.__ModuleLoader__.load({
358
383
  /** Full-screen viewer opened by clicking a photo; closes on Esc, backdrop or the close button. */
359
384
  function Lightbox(props) {
360
385
  const { preview, onClose } = props;
386
+ const closeRef = React.useRef(null);
387
+ React.useEffect(() => {
388
+ if (preview === null) return;
389
+ const previous = document.activeElement;
390
+ const previousOverflow = document.body.style.overflow;
391
+ document.body.style.overflow = 'hidden';
392
+ if (closeRef.current !== null) closeRef.current.focus();
393
+ return () => {
394
+ document.body.style.overflow = previousOverflow;
395
+ if (previous !== null && typeof previous.focus === 'function') previous.focus();
396
+ };
397
+ }, [preview]);
361
398
  if (preview === null) return null;
399
+ const label = preview.kind === 'photo'
400
+ ? preview.name || '照片 / Photo'
401
+ : '照片插画 / Photo illustration';
362
402
  const card = preview.kind === 'photo'
363
403
  ? h('div', { className: 'sl-lightbox-photo' },
364
404
  h('img', {
365
405
  src: preview.url,
366
- alt: preview.name || '照片',
406
+ alt: label,
367
407
  draggable: 'false',
368
408
  }))
369
409
  : h('div', { className: 'sl-lightbox-polaroid' },
370
410
  h('svg', {
371
411
  viewBox: '-31 6 62 80',
372
412
  role: 'img',
373
- 'aria-label': '照片插画',
413
+ 'aria-label': label,
374
414
  focusable: 'false',
375
415
  },
376
416
  frame(preview.content)));
@@ -378,7 +418,12 @@ window.__ModuleLoader__.load({
378
418
  className: 'sl-lightbox',
379
419
  role: 'dialog',
380
420
  'aria-modal': 'true',
381
- 'aria-label': '照片预览',
421
+ 'aria-label': '照片预览 / Photo preview',
422
+ onKeyDown: (event) => {
423
+ if (event.key !== 'Tab') return;
424
+ event.preventDefault();
425
+ if (closeRef.current !== null) closeRef.current.focus();
426
+ },
382
427
  onClick: (event) => {
383
428
  if (event.target === event.currentTarget) onClose();
384
429
  },
@@ -387,8 +432,8 @@ window.__ModuleLoader__.load({
387
432
  h('button', {
388
433
  type: 'button',
389
434
  className: 'sl-lightbox-close',
390
- 'aria-label': '关闭预览',
391
- autoFocus: true,
435
+ 'aria-label': '关闭预览 / Close preview',
436
+ ref: closeRef,
392
437
  onClick: onClose,
393
438
  }, '×'),
394
439
  );
@@ -428,7 +473,8 @@ window.__ModuleLoader__.load({
428
473
  : ILLUSTRATIONS.map((scene) => ({ kind: 'illustration', scene }));
429
474
  const xs = photoPositions(items.length);
430
475
  return h('div', {
431
- 'aria-hidden': 'true',
476
+ role: 'group',
477
+ 'aria-label': '照片晾衣绳 / Photo clothesline',
432
478
  'data-dsh-snap-line': source,
433
479
  style: {
434
480
  display: 'flex',
@@ -443,7 +489,8 @@ window.__ModuleLoader__.load({
443
489
  h('svg', {
444
490
  key: 'art',
445
491
  viewBox: '0 0 700 108',
446
- role: 'presentation',
492
+ role: 'group',
493
+ 'aria-label': '照片 / Photos',
447
494
  focusable: 'false',
448
495
  width: '100%',
449
496
  style: {
@@ -468,6 +515,9 @@ window.__ModuleLoader__.load({
468
515
  delay: DELAYS[index % DELAYS.length],
469
516
  duration: DURATIONS[index % DURATIONS.length],
470
517
  content,
518
+ label: item.kind === 'photo'
519
+ ? '照片 ' + (index + 1) + (item.name === '' ? '' : ':' + item.name) + ' / Photo ' + (index + 1)
520
+ : '内置插画 ' + (index + 1) + ' / Built-in illustration ' + (index + 1),
471
521
  onOpen: () => setPreview(
472
522
  item.kind === 'photo'
473
523
  ? { kind: 'photo', url: item.url, name: item.name }
package/docs/icon.png ADDED
Binary file
Binary file
Binary file
package/lib/photos.js CHANGED
@@ -87,6 +87,7 @@ export async function servePhoto(dir, encodedName) {
87
87
  return new Response(body, {
88
88
  headers: {
89
89
  'content-type': mediaType,
90
+ 'content-length': String(body.byteLength),
90
91
  'cache-control': 'private, max-age=60',
91
92
  'x-content-type-options': 'nosniff',
92
93
  },
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "dsh-snap-line",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
+ "icon": "icon.svg",
4
5
  "description": "Hangs a clothesline of polaroid photos under the message composer in DeepSeek Harness.",
5
6
  "type": "module",
6
7
  "license": "MIT",
@@ -40,11 +41,13 @@
40
41
  "cordis.patch.yml",
41
42
  "locale/*.json",
42
43
  "icon.svg",
43
- "docs/images"
44
+ "docs/images",
45
+ "docs/icon.png"
44
46
  ],
45
47
  "scripts": {
46
48
  "check": "node --check index.js && node --check client.js && node --check lib/photos.js && node --test",
47
- "test": "node --test"
49
+ "test": "node --test",
50
+ "prepublishOnly": "npm run check"
48
51
  },
49
52
  "dsh": {
50
53
  "manifestVersion": 1,