dsh-focus-overlay 0.12.3 → 0.12.4

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
@@ -10,10 +10,11 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version">
14
- <img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)">
15
- <img src="https://badgen.net/badge/license/MIT/green" alt="license">
16
- <img src="https://badgen.net/badge/dsh/%3E%3D0.1.2-rc.1/blue" alt="dsh version">
13
+ <a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version"></a>
14
+ <a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)"></a>
15
+ <a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license: MIT"></a>
16
+ <a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/package.json"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.2--rc.1-blue" alt="dsh version"></a>
17
+ <a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/awesome-dsh--plugin-orange" alt="awesome dsh plugin"></a>
17
18
  </p>
18
19
 
19
20
  ## Features
@@ -75,12 +76,13 @@ Click the line to expand that turn's full work detail (per-category one-line sum
75
76
  | Focus counter | A row at the bottom of the settings card totals your focus-mode entries (auto-entries included), persisted to `localStorage`; past 100 entries a Star nudge appears — after the first use it is remembered and replaced by a direct "open the project page" button |
76
77
  | Esc peels layer by layer | Answer card → input bar → focus mode, one layer per press; Esc mid-IME-composition cancels the composition first |
77
78
  | i18n | Chinese / English copy registered under the `focus` namespace, follows the UI language |
78
- | Plugin configuration card | A collapsible card under "Settings → Plugins → Plugin configuration", preferences persisted to `localStorage`; the Node half registers the same settings namespace (schemastery schema) so the tab dispatches it |
79
- | First-run onboarding | After installing, the welcome page shows a one-time "Focus Mode" intro (feature overview + in-place configuration) as a `settings.onboarding` step with its own versioned seen-flag |
80
- | Official preference sync | Reads the official "transcript view" preference (normal / compact); flipping it applies immediately |
79
+ | Plugin configuration | On dsh 0.2 and newer, open this plugin's detail page from the sidebar "Plugins" page — the configuration renders between the package description and its component rows (`plugins.bundle.config`). On dsh ≤ 0.1.x it stays the collapsible "Settings → Plugins → Plugin configuration" card (`settings.plugin.item`). Preferences persist to `localStorage`; the Node half registers the same settings namespace (schemastery schema) |
80
+ | First-run onboarding | After installing, the welcome page shows a one-time "Focus Mode" intro (feature overview + in-place configuration) as a `settings.onboarding` step with its own versioned seen-flag; its "open settings" action jumps to whichever configuration surface the running dsh declares |
81
+ | Official preference sync | Reads the official "transcript view" preference (normal / compact on dsh ≤ 0.1.x; compact / standard / detailed / verbose on dsh 0.2); flipping it applies immediately |
81
82
  | Render error boundary | The overlay content is wrapped in an error boundary: one render crash never bricks the Focus button — the next open retries |
82
83
  | DSH-better-sidebar compatibility | While in focus, a `<body>` marker makes the package stylesheet hide better-sidebar's top-right panel toggles and any open right/bottom panels (and release the squeezed layout); restored on exit |
83
84
  | Legacy compatibility mode | On dsh < 0.1.2 missing the new services, the overlay degrades gracefully (legacy snapshot adapters) with a one-time console notice — no crash |
85
+ | Cross-release primitive compatibility | Official primitives renamed between major releases fall back by availability (dsh 0.2 removed the fixed-size `…14` / `…16` icons in favour of `…Regular` + `size`), so an `undefined` component can never throw React #130 |
84
86
 
85
87
  ## Install
86
88
 
@@ -119,7 +121,7 @@ dsh web
119
121
 
120
122
  Restart `dsh web` after install.
121
123
 
122
- > **First run**: after restarting, the welcome page shows a one-time "Focus Mode" intro that explains the features and lets you configure them in place; all of these settings can be changed anytime in "Settings → Plugins → Plugin configuration".
124
+ > **First run**: after restarting, the welcome page shows a one-time "Focus Mode" intro that explains the features and lets you configure them in place; all of these settings can be changed anytime — on newer dsh open this plugin's detail page from the sidebar "Plugins" page, on older dsh use "Settings → Plugins → Plugin configuration".
123
125
 
124
126
  ## Usage
125
127
 
@@ -133,7 +135,11 @@ Restart `dsh web` after install.
133
135
 
134
136
  ## Settings
135
137
 
136
- In the sidebar "Settings → Plugins → Plugin configuration", expand the "Focus Mode" card:
138
+ **On dsh 0.2 and newer**: sidebar "Plugins" page → find `dsh-focus-overlay` → the configuration shows on its detail page, between the package description and the component rows.
139
+
140
+ **On dsh ≤ 0.1.x**: sidebar "Settings → Plugins → Plugin configuration" → expand the "Focus Mode" card.
141
+
142
+ Both locations present the same options:
137
143
 
138
144
  | Option | Description |
139
145
  | --- | --- |
package/README.md CHANGED
@@ -8,10 +8,11 @@
8
8
  </p>
9
9
 
10
10
  <p align="center">
11
- <img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version">
12
- <img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)">
13
- <img src="https://badgen.net/badge/license/MIT/green" alt="license">
14
- <img src="https://badgen.net/badge/dsh/%3E%3D0.1.2-rc.1/blue" alt="dsh version">
11
+ <a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version"></a>
12
+ <a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)"></a>
13
+ <a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license: MIT"></a>
14
+ <a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/package.json"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.2--rc.1-blue" alt="dsh version"></a>
15
+ <a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/awesome-dsh--plugin-orange" alt="awesome dsh plugin"></a>
15
16
  </p>
16
17
 
17
18
  ## 功能
@@ -73,12 +74,13 @@
73
74
  | 专注计数 | 设置卡片最底部一行累计进入专注模式的次数(含自动进入),持久化到 `localStorage`;超过 100 次出现 Star 引导按钮,点击前往仓库页面并记住状态,之后改为「前往 GitHub 项目页面」直达按钮 |
74
75
  | Esc 逐层退出 | 答题卡 → 输入条 → 专注模式,一层一层退;输入法组词中的 Esc 优先取消组词 |
75
76
  | i18n | 中 / 英文案,注册到 `focus` 命名空间,跟随界面语言 |
76
- | 插件配置卡片 | 「设置 → 插件 → 插件配置」中的可折叠卡片,偏好持久化到 `localStorage`;Node 半区以 schemastery schema 注册同名设置 namespace 供其分发 |
77
- | 首次引导 | 首次安装后欢迎页弹出一次「专注模式」引导(功能一览 + 就地配置),`settings.onboarding` 步骤自带已读标记与版本号 |
78
- | 官方偏好联动 | 读取官方「对话显示」(normal / compact)偏好,切换即时生效 |
77
+ | 插件配置卡片 | dsh 0.2 起在侧栏「Plugins」页面打开本插件详情页,配置直接渲染在包描述与组件列表之间(`plugins.bundle.config`);dsh ≤ 0.1.x 仍为「设置 → 插件 → 插件配置」中的可折叠卡片(`settings.plugin.item`)。偏好持久化到 `localStorage`;Node 半区以 schemastery schema 注册同名设置 namespace |
78
+ | 首次引导 | 首次安装后欢迎页弹出一次「专注模式」引导(功能一览 + 就地配置),`settings.onboarding` 步骤自带已读标记与版本号;「打开设置」按当前 dsh 版本跳到对应配置页 |
79
+ | 官方偏好联动 | 读取官方「对话显示」偏好(dsh ≤ 0.1.x:normal / compact;dsh 0.2:compact / standard / detailed / verbose),切换即时生效 |
79
80
  | 渲染错误兜底 | 遮罩内容包裹错误边界:一次渲染崩溃不会永久打死「专注」按钮,下次打开自动重试 |
80
81
  | 兼容 DSH-better-sidebar | 进入专注时在 `<body>` 打标记,样式层自动隐藏其右上角的收放面板按钮与已开启的右侧 / 底部面板,并释放被挤压的布局;退出专注后按原状态还原 |
81
82
  | 旧版兼容模式 | dsh < 0.1.2 缺少新服务时降级运行(legacy 快照适配),控制台一次性提示,不会崩溃 |
83
+ | 跨版本构件兼容 | 图标等官方构件在两个大版本间改名时按可用性回退(dsh 0.2 移除了 `…14` / `…16` 定尺图标,改用 `…Regular` + `size`),不会因为 `undefined` 组件触发 React #130 |
82
84
 
83
85
  ## 安装
84
86
 
@@ -117,7 +119,7 @@ dsh web
117
119
 
118
120
  安装后重启 `dsh web` 生效。
119
121
 
120
- > **首次安装**:重启后欢迎页会弹出一次「专注模式」引导,说明功能并让你就地完成配置;其中所有设置随时可在「设置 → 插件 → 插件配置」中更改。
122
+ > **首次安装**:重启后欢迎页会弹出一次「专注模式」引导,说明功能并让你就地完成配置;其中所有设置随时可以更改——新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。
121
123
 
122
124
  ## 使用
123
125
 
@@ -131,7 +133,11 @@ dsh web
131
133
 
132
134
  ## 设置
133
135
 
134
- 侧栏「设置 → 插件 → 插件配置」中,展开「专注模式」卡片:
136
+ **dsh 0.2 起**:侧栏「Plugins」页面 → 找到 `dsh-focus-overlay` → 详情页中,配置直接显示在包描述与组件列表之间。
137
+
138
+ **dsh ≤ 0.1.x**:侧栏「设置 → 插件 → 插件配置」→ 展开「专注模式」卡片。
139
+
140
+ 两种位置呈现同一组选项:
135
141
 
136
142
  | 选项 | 说明 |
137
143
  | --- | --- |
package/lib/client.js CHANGED
@@ -3,9 +3,62 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  var module = { exports: {} };
5
5
  module.exports;
6
+ //#region \0rolldown/runtime.js
7
+ var __create = Object.create;
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __getProtoOf = Object.getPrototypeOf;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
15
+ key = keys[i];
16
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
17
+ get: ((k) => from[k]).bind(null, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
26
+ }) : target, mod));
27
+ //#endregion
6
28
  let react = require("react");
7
29
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
30
+ _deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
8
31
  let react_jsx_runtime = require("react/jsx-runtime");
32
+ //#region src/client/icons.ts
33
+ /** Cross-release product-icon resolution.
34
+ *
35
+ * The official primitives package renamed its icon set in dsh 0.2: the
36
+ * fixed-size artwork variants (`IconCheckOutline14`, `IconChevronDownOutline14`,
37
+ * `IconEditOutline16`, `IconSendOutline14`) were removed, leaving one
38
+ * `…OutlineRegular` / `…OutlineMedium` component per glyph that takes an
39
+ * explicit `size` prop. A stale named import therefore resolves to
40
+ * `undefined`, and React throws #130 ("element type is invalid") the moment
41
+ * the component renders — the same failure mode the 0.1.3 `MessageText`
42
+ * removal produced.
43
+ *
44
+ * Import the namespace once and pick the first name the running build exports,
45
+ * so one bundle serves both generations. Each fallback carries the size the
46
+ * removed variant was drawn at, keeping layout identical across releases. */
47
+ function pick(names, size) {
48
+ for (const name of names) {
49
+ const component = _deepseek_ai_dsh_client_ui_primitives[name];
50
+ if (typeof component === "function") return (props) => component({
51
+ size,
52
+ ...props
53
+ });
54
+ }
55
+ return () => null;
56
+ }
57
+ const IconCheck = pick(["IconCheckOutline14", "IconCheckOutlineRegular"], 14);
58
+ const IconChevronDown = pick(["IconChevronDownOutline14", "IconChevronDownOutlineRegular"], 14);
59
+ const IconEdit = pick(["IconEditOutline16", "IconEditOutlineRegular"], 16);
60
+ const IconSend = pick(["IconSendOutline14", "IconSendOutlineRegular"], 14);
61
+ //#endregion
9
62
  //#region src/client/model.ts
10
63
  function legacySliceOf(chatView, snap) {
11
64
  const legacy = chatView && chatView.legacy ? chatView.legacy : null;
@@ -25,6 +78,61 @@ window.__ModuleLoader__.load({
25
78
  } catch {}
26
79
  return null;
27
80
  }
81
+ /** dsh 0.2 moved the pending interaction off the uiSession
82
+ * `pendingInteractions` map and onto the unified `sessionStatus` snapshot
83
+ * (`ReadonlyMap<sessionId, { running, pendingInteraction, completionUnread }>`).
84
+ * Both carriers answer with the same interaction face, so the overlay's answer
85
+ * card resolves through either. Null when nothing is pending. */
86
+ function pendingInteractionFromStatus(status, sessionId) {
87
+ try {
88
+ const entry = status && typeof status.get === "function" ? status.get(sessionId) : null;
89
+ return entry && entry.pendingInteraction || null;
90
+ } catch {}
91
+ return null;
92
+ }
93
+ /** The current session id, resolved across dsh generations.
94
+ *
95
+ * dsh <= 0.1.x carried the viewer's selection on the session-list snapshot
96
+ * (`sessions.list.getSnapshot().current`). dsh 0.2 dropped that field: the
97
+ * selection is the session the main view retains, published on the list rows
98
+ * themselves (`byId[id].retainedBy.mainView > 0`) — the same public read the
99
+ * official cordis inspector uses. `mainBinding` is the uiSession main-binding
100
+ * snapshot, kept as a guarded last resort: it carries the same id, but that
101
+ * member is typed private, so the public list data stays primary. */
102
+ function currentSessionIdOf(listState, mainBinding) {
103
+ const fromList = listState ? listState.current : void 0;
104
+ if (fromList !== void 0 && fromList !== null) return fromList;
105
+ const byId = listState && listState.byId;
106
+ if (byId && typeof byId === "object") {
107
+ for (const row of Object.values(byId)) if (row && (row.retainedBy && row.retainedBy.mainView || 0) > 0) return row.id;
108
+ }
109
+ if (mainBinding) {
110
+ const id = mainBinding.key ?? (mainBinding.props ? mainBinding.props.sessionId : void 0);
111
+ if (id !== void 0 && id !== null) return id;
112
+ }
113
+ }
114
+ /** The overlay fold mode derived from the official "对话显示" preference.
115
+ *
116
+ * dsh <= 0.1.x served it through the `settingsScope` service bound to the
117
+ * `ui-chat` namespace (`{ transcriptView: 'normal' | 'compact' }`). dsh 0.2
118
+ * retired `settingsScope`; the same preference is a Host settings field read
119
+ * through `configForms.get('ui-chat')`, whose snapshot nests it under `value`
120
+ * (and widened the accepted set to compact | standard | detailed | verbose,
121
+ * with `normal` / `expanded` kept as legacy saved values). Only `compact`
122
+ * folds the tool flow; every other explicit mode reads as the expanded
123
+ * presentation. Absence keeps the overlay's own compact default. */
124
+ function transcriptViewOf(scopeOrForm) {
125
+ try {
126
+ if (!scopeOrForm || typeof scopeOrForm.getSnapshot !== "function") return "compact";
127
+ const snapshot = scopeOrForm.getSnapshot();
128
+ const section = snapshot && snapshot.value && typeof snapshot.value === "object" ? snapshot.value : snapshot;
129
+ const mode = section ? section.transcriptView : null;
130
+ if (mode === void 0 || mode === null) return "compact";
131
+ return mode === "compact" ? "compact" : "normal";
132
+ } catch {
133
+ return "compact";
134
+ }
135
+ }
28
136
  /** Legacy pending interaction — pre-0.1.2 dsh carried a flat `pending` array
29
137
  * on the session snapshot, with a different carrier shape (`payload`
30
138
  * envelope, answers via `wait.respond({ok, value})` returning a `receipt`).
@@ -70,6 +178,28 @@ window.__ModuleLoader__.load({
70
178
  });
71
179
  return shaped;
72
180
  }
181
+ /** Build the `dsh-resource://file/session/<id>/<path>` address that dsh 0.2's
182
+ * Sidebar resource controller opens a file through.
183
+ *
184
+ * The official `fileAddressFor` lives in `@deepseek-ai/dsh-util-workspace-path`,
185
+ * but the client bundle purity gate keeps feature bundles from importing other
186
+ * plugins' values across the boundary — the official Chat package inlines its
187
+ * own copy of the same grammar, and so does this one. Absolute paths inside the
188
+ * session workspace are reduced to workspace-relative; every id and path
189
+ * segment is component-encoded, with `:` kept literal for Windows drives. */
190
+ function sessionFileAddressOf(sessionId, cwd, path) {
191
+ const encode = (segment) => encodeURIComponent(segment).replace(/%3A/gi, ":");
192
+ const normalized = String(path).replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
193
+ const absolute = normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
194
+ let relative = normalized;
195
+ if (absolute) {
196
+ const root = cwd === void 0 || cwd === null ? "" : String(cwd).replace(/\\/g, "/").replace(/\/+$/, "");
197
+ if (root !== "" && normalized === root) relative = "";
198
+ else if (root !== "" && normalized.startsWith(root + "/")) relative = normalized.slice(root.length + 1);
199
+ }
200
+ const body = relative.split("/").map(encode).join("/");
201
+ return `dsh-resource://file/session/${encode(String(sessionId))}/${body}`;
202
+ }
73
203
  function flattenText(content) {
74
204
  if (!content || !content.length) return "";
75
205
  let out = "";
@@ -933,7 +1063,7 @@ window.__ModuleLoader__.load({
933
1063
  "onboarding.feature.autoFocus": "自动专注与提醒 —— 回复完成自动进入专注,AI 提问 / 等待时提醒",
934
1064
  "onboarding.feature.hotkey": "快捷键 F —— 任意界面按 F 一键进入专注,可在设置中关闭",
935
1065
  "onboarding.configure.title": "快速配置",
936
- "onboarding.laterNote": "这些设置随时可在「设置 → 插件 → 插件配置」中更改。",
1066
+ "onboarding.laterNote": "这些设置随时可以更改:新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。",
937
1067
  "onboarding.openSettings": "打开设置",
938
1068
  "onboarding.done": "完成"
939
1069
  };
@@ -1019,7 +1149,7 @@ window.__ModuleLoader__.load({
1019
1149
  "onboarding.feature.autoFocus": "Auto-focus & reminders — auto-enter after a reply, notify while the AI waits",
1020
1150
  "onboarding.feature.hotkey": "Hotkey F — press F anywhere to enter focus mode (toggleable in settings)",
1021
1151
  "onboarding.configure.title": "Quick setup",
1022
- "onboarding.laterNote": "You can change these anytime in Settings → Plugins → Plugin configuration.",
1152
+ "onboarding.laterNote": "Change these anytime: on newer dsh open this plugin’s detail page from the sidebar Plugins page; on older dsh use Settings → Plugins → Plugin configuration.",
1023
1153
  "onboarding.openSettings": "Open settings",
1024
1154
  "onboarding.done": "Done"
1025
1155
  };
@@ -1109,7 +1239,7 @@ window.__ModuleLoader__.load({
1109
1239
  "aria-pressed": on,
1110
1240
  children: [multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1111
1241
  className: "fm-opt-check" + (on ? " fm-opt-check-on" : ""),
1112
- children: on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline14, {}) : null
1242
+ children: on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheck, {}) : null
1113
1243
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1114
1244
  className: "fm-opt-num",
1115
1245
  children: index + 1
@@ -1220,7 +1350,7 @@ window.__ModuleLoader__.load({
1220
1350
  title: t("answer.close"),
1221
1351
  "aria-label": t("answer.close"),
1222
1352
  onClick: onClose,
1223
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
1353
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, {})
1224
1354
  })]
1225
1355
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1226
1356
  className: "fm-card-head",
@@ -1236,7 +1366,7 @@ window.__ModuleLoader__.load({
1236
1366
  title: t("answer.close"),
1237
1367
  "aria-label": t("answer.close"),
1238
1368
  onClick: onClose,
1239
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
1369
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, {})
1240
1370
  })
1241
1371
  ]
1242
1372
  }),
@@ -1434,7 +1564,7 @@ window.__ModuleLoader__.load({
1434
1564
  disabled: empty,
1435
1565
  onMouseDown: (e) => e.preventDefault(),
1436
1566
  onClick: onSend,
1437
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSendOutline14, {})
1567
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSend, {})
1438
1568
  })
1439
1569
  ]
1440
1570
  })
@@ -1471,7 +1601,7 @@ window.__ModuleLoader__.load({
1471
1601
  title: t("composer.expand"),
1472
1602
  "aria-label": t("composer.expand"),
1473
1603
  onClick: onExpand,
1474
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {}), hasDraft ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "fm-pill-dot" }) : null]
1604
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEdit, {}), hasDraft ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "fm-pill-dot" }) : null]
1475
1605
  });
1476
1606
  }
1477
1607
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -1480,7 +1610,7 @@ window.__ModuleLoader__.load({
1480
1610
  title: t("backToLatest"),
1481
1611
  "aria-label": t("backToLatest"),
1482
1612
  onClick: onJumpBottom,
1483
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 16 })
1613
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { size: 16 })
1484
1614
  });
1485
1615
  }
1486
1616
  //#endregion
@@ -1813,6 +1943,9 @@ window.__ModuleLoader__.load({
1813
1943
  .fm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}
1814
1944
  .fm-plugin-card-chevron-open{transform:rotate(180deg)}
1815
1945
  .fm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:16px}
1946
+ /* dsh 0.2 Plugins-page bundle configuration: the page draws its own title,
1947
+ icon and crumb, so this surface is body-only — no card border or collapse. */
1948
+ .fm-plugin-page{flex-direction:column;display:flex}
1816
1949
  .fm-plugin-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
1817
1950
  .fm-plugin-field+.fm-plugin-field{border-top:1px solid var(--dsw-alias-border-l2)}
1818
1951
  /* The stats row ends the card body, so it carries NO bottom padding of its
@@ -2301,13 +2434,16 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
2301
2434
  return snap;
2302
2435
  }
2303
2436
  /** The session's pending user interaction (question / plan review / approval),
2304
- * read from the uiSession pending-interactions map — dsh 0.1.2 moved the old
2305
- * snapshot `pending` array into this root-level observable. When the service
2306
- * is absent (older dsh), falls back to the session snapshot's flat `pending`
2307
- * array through the `legacyPendingOf` adapter. Returns null when this session
2308
- * has nothing pending. */
2437
+ * read from the uiSession services. dsh 0.1.2 exposed the root-level
2438
+ * `pendingInteractions` map; dsh 0.2 unified it into `sessionStatus`
2439
+ * (`ReadonlyMap<sessionId, { running, pendingInteraction, completionUnread }>`).
2440
+ * When neither service is present (older dsh), falls back to the session
2441
+ * snapshot's flat `pending` array through the `legacyPendingOf` adapter.
2442
+ * Returns null when this session has nothing pending. */
2309
2443
  function usePendingInteraction(uiSession, sessionId, snap) {
2310
- const source = uiSession && uiSession.pendingInteractions;
2444
+ const legacyMap = uiSession && uiSession.pendingInteractions;
2445
+ const statusMap = uiSession && uiSession.sessionStatus;
2446
+ const source = legacyMap || statusMap;
2311
2447
  const read = () => {
2312
2448
  try {
2313
2449
  return source ? source.getSnapshot() : null;
@@ -2328,7 +2464,47 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
2328
2464
  return;
2329
2465
  }
2330
2466
  }, [source]);
2331
- return pendingInteractionOf(map, sessionId) || legacyPendingOf(snap);
2467
+ return (legacyMap ? pendingInteractionOf(map, sessionId) : pendingInteractionFromStatus(map, sessionId)) || legacyPendingOf(snap);
2468
+ }
2469
+ /** The viewed session id, resolved across dsh generations. dsh <= 0.1.x carried
2470
+ * the selection on the session-list snapshot (`listState.current`); dsh 0.2
2471
+ * dropped it and publishes the main view's retained session as the uiSession
2472
+ * main binding. Subscribes to whichever source exists (both, when both do), so
2473
+ * switching sessions re-resolves either way. */
2474
+ function useCurrentSessionId(sessions, uiSession, listState) {
2475
+ const read = () => {
2476
+ if (!sessions || typeof sessions.list?.getSnapshot !== "function") return void 0;
2477
+ let mainBinding = null;
2478
+ try {
2479
+ mainBinding = uiSession && uiSession.current ? uiSession.current.getSnapshot() : null;
2480
+ } catch {
2481
+ mainBinding = null;
2482
+ }
2483
+ return currentSessionIdOf(sessions.list.getSnapshot(), mainBinding);
2484
+ };
2485
+ const [id, setId] = (0, react.useState)(read);
2486
+ (0, react.useEffect)(() => {
2487
+ if (!sessions || typeof sessions.list?.subscribe !== "function") {
2488
+ setId(void 0);
2489
+ return;
2490
+ }
2491
+ const sync = () => setId(read());
2492
+ sync();
2493
+ const unsubs = [];
2494
+ try {
2495
+ unsubs.push(sessions.list.subscribe(sync));
2496
+ } catch {}
2497
+ const main = uiSession && uiSession.current;
2498
+ if (main && typeof main.subscribe === "function") try {
2499
+ unsubs.push(main.subscribe(sync));
2500
+ } catch {}
2501
+ return () => {
2502
+ for (const u of unsubs) try {
2503
+ u();
2504
+ } catch {}
2505
+ };
2506
+ }, [sessions, uiSession]);
2507
+ return id;
2332
2508
  }
2333
2509
  /** The session's Chat view snapshot (dsh 0.1.2 `ChatSnapshot`) from the
2334
2510
  * uiConversation assembly — the first subscriber activates the chat target,
@@ -2355,20 +2531,25 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
2355
2531
  }, [uiConversation, sessionId]);
2356
2532
  return chat;
2357
2533
  }
2358
- /** The official "对话显示" preference (`ui-chat.transcriptView`, values
2359
- * normal | compact), read reactively from the settings scope the official
2360
- * TranscriptViewPolicy binds. Defaults to compact — the official default —
2361
- * when the scope or the section has not arrived yet. */
2362
- function useTranscriptViewMode(settingsScope) {
2363
- const source = settingsScope;
2364
- const read = () => {
2534
+ /** The official "对话显示" preference (`ui-chat.transcriptView`), read
2535
+ * reactively. dsh <= 0.1.x serves it through the settings scope the official
2536
+ * TranscriptViewPolicy binds (values normal | compact); dsh 0.2 retired that
2537
+ * service and serves the same field through the `configForms` controller for
2538
+ * the `ui-chat` namespace (compact | standard | detailed | verbose, with the
2539
+ * legacy normal | expanded values still accepted). Both are read through the
2540
+ * shared `transcriptViewOf` projection; only `compact` folds the tool flow.
2541
+ * Defaults to compact when no preference has arrived yet. */
2542
+ function useTranscriptViewMode(settingsScope, configForms) {
2543
+ const form = (0, react.useMemo)(() => {
2544
+ if (settingsScope) return settingsScope;
2365
2545
  try {
2366
- const section = source ? source.getSnapshot().value : null;
2367
- return (section ? section.transcriptView : null) === "normal" ? "normal" : "compact";
2546
+ return configForms && typeof configForms.get === "function" ? configForms.get("ui-chat") : null;
2368
2547
  } catch {
2369
- return "compact";
2548
+ return null;
2370
2549
  }
2371
- };
2550
+ }, [settingsScope, configForms]);
2551
+ const source = form && typeof form.getSnapshot === "function" ? form : null;
2552
+ const read = () => transcriptViewOf(source);
2372
2553
  const [mode, setMode] = (0, react.useState)(read);
2373
2554
  (0, react.useEffect)(() => {
2374
2555
  if (!source) {
@@ -2433,13 +2614,13 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
2433
2614
  return seat ? seat.querySelector("[contenteditable=\"true\"]") : null;
2434
2615
  }
2435
2616
  function FocusContent(props) {
2436
- const { useSessions, sessions, workspaces, conversation, chatFileMentions, uiSession, uiConversation, settingsScope, t } = props;
2617
+ const { useSessions, sessions, workspaces, conversation, chatFileMentions, uiSession, uiConversation, settingsScope, configForms, sidebarRight, t } = props;
2437
2618
  const prefs = usePrefs();
2438
2619
  const listState = useSessions((s) => s);
2439
- const currentId = listState ? listState.current : void 0;
2620
+ const currentId = useCurrentSessionId(sessions, uiSession, listState);
2440
2621
  const snap = useSessionSnapshot(sessions, currentId);
2441
2622
  const chatView = useChatView(uiConversation, currentId);
2442
- const transcriptView = useTranscriptViewMode(settingsScope);
2623
+ const transcriptView = useTranscriptViewMode(settingsScope, configForms);
2443
2624
  const legacy = (0, react.useMemo)(() => legacySliceOf(chatView, snap), [chatView, snap]);
2444
2625
  const stable = legacy.partial == null;
2445
2626
  const mdLabels = (0, react.useMemo)(() => markdownLabels(t), [t]);
@@ -2765,6 +2946,24 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
2765
2946
  if (conversation && typeof conversation.resolveImage === "function") return conversation.resolveImage(currentId, attachment);
2766
2947
  return Promise.reject(/* @__PURE__ */ new Error("dsh-focus-overlay: image resolver unavailable"));
2767
2948
  }
2949
+ const openMentionedFile = (0, react.useCallback)((path, options) => {
2950
+ try {
2951
+ if (workspaces && typeof workspaces.openPath === "function") {
2952
+ workspaces.openPath(path).catch(() => {});
2953
+ return;
2954
+ }
2955
+ const cwd = currentId != null ? sessions?.list?.getSnapshot?.()?.byId?.[currentId]?.cwd : void 0;
2956
+ if (sidebarRight && currentId != null && typeof sidebarRight.openResource === "function") {
2957
+ const address = sessionFileAddressOf(String(currentId), cwd, path);
2958
+ sidebarRight.openResource(address, options && options.line !== void 0 ? { params: { line: options.line } } : void 0);
2959
+ }
2960
+ } catch {}
2961
+ }, [
2962
+ sidebarRight,
2963
+ workspaces,
2964
+ sessions,
2965
+ currentId
2966
+ ]);
2768
2967
  const fileMentionsFor = (0, react.useCallback)((node) => {
2769
2968
  if (!chatFileMentions) return void 0;
2770
2969
  try {
@@ -2774,14 +2973,16 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
2774
2973
  step: node.step
2775
2974
  },
2776
2975
  seq: node.seq,
2777
- openFile: (path) => {
2778
- if (workspaces) workspaces.openPath(path).catch(() => {});
2779
- }
2780
- });
2976
+ openFile: (path, options) => openMentionedFile(path, options)
2977
+ }, currentId);
2781
2978
  } catch {
2782
2979
  return;
2783
2980
  }
2784
- }, [chatFileMentions, workspaces]);
2981
+ }, [
2982
+ chatFileMentions,
2983
+ currentId,
2984
+ openMentionedFile
2985
+ ]);
2785
2986
  const binding = (0, react.useMemo)(() => sessions && currentId != null ? sessions.binding(currentId) : void 0, [sessions, currentId]);
2786
2987
  const inputFace = useInputFace(conversation, binding && binding.ctx);
2787
2988
  const inputState = useInputState(inputFace);
@@ -3037,7 +3238,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3037
3238
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3038
3239
  className: "fm-process-label",
3039
3240
  children: processLabel(it.counts, t)
3040
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "fm-process-chevron" })]
3241
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "fm-process-chevron" })]
3041
3242
  }) : null, open ? it.workItems.map((wi, wi2) => renderItem(wi, key + "-w" + wi2, false)) : null] }, key);
3042
3243
  }
3043
3244
  return null;
@@ -3371,6 +3572,15 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3371
3572
  })
3372
3573
  });
3373
3574
  }
3575
+ /** The settings body both plugin-settings surfaces share: the preference
3576
+ * fields plus the focus-entry counter. Rendered by the legacy Settings card
3577
+ * and by the dsh 0.2 Plugins-page bundle page. */
3578
+ function FocusSettingsBody({ t }) {
3579
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusPrefsFields, { t }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusEntryStat, { t })] });
3580
+ }
3581
+ /** Legacy surface (dsh <= 0.1.x): a collapsible `<li>` card in the Settings
3582
+ * → Plugins tab, registered into the keyed `settings.plugin.item` slot. dsh
3583
+ * 0.2 removed that slot, so this only renders where the slot is declared. */
3374
3584
  function FocusSettingsCard({ t }) {
3375
3585
  const [open, setOpen] = (0, react.useState)(false);
3376
3586
  const title = t("settings.label");
@@ -3391,13 +3601,24 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3391
3601
  className: "fm-plugin-card-desc",
3392
3602
  children: t("settings.description")
3393
3603
  })]
3394
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "fm-plugin-card-chevron" + (open ? " fm-plugin-card-chevron-open" : "") })]
3395
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3604
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "fm-plugin-card-chevron" + (open ? " fm-plugin-card-chevron-open" : "") })]
3605
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3396
3606
  className: "fm-plugin-card-body",
3397
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusPrefsFields, { t }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusEntryStat, { t })]
3607
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusSettingsBody, { t })
3398
3608
  }) : null]
3399
3609
  });
3400
3610
  }
3611
+ /** dsh 0.2 surface: the bundle's own configuration on the sidebar Plugins
3612
+ * page (`plugins.bundle.config`, keyed by the bundle's package name). The page
3613
+ * draws the title, icon, and crumb itself and always asks for `view: 'page'`,
3614
+ * so this renders the body alone — no card chrome, no collapse. */
3615
+ function FocusBundleConfig({ t, view }) {
3616
+ if (view !== void 0 && view !== "page") return null;
3617
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3618
+ className: "fm-plugin-page",
3619
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusSettingsBody, { t })
3620
+ });
3621
+ }
3401
3622
  function FocusOnboarding(props) {
3402
3623
  const { complete, openSection, t } = props;
3403
3624
  const [done, setDone] = (0, react.useState)(() => onboardingStore.isDone());
@@ -3429,7 +3650,13 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3429
3650
  onboardingStore.markDone();
3430
3651
  setDone(true);
3431
3652
  finish();
3432
- openSection("plugins");
3653
+ let opened = false;
3654
+ if (typeof props.openSettings === "function") try {
3655
+ opened = props.openSettings() !== false;
3656
+ } catch {
3657
+ opened = false;
3658
+ }
3659
+ if (!opened && typeof openSection === "function") openSection("plugins");
3433
3660
  };
3434
3661
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3435
3662
  open: true,
@@ -3494,13 +3721,20 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3494
3721
  /**
3495
3722
  * Focus Mode, browser half: a full-screen reading overlay in `shell.overlay`
3496
3723
  * (additive, never replaces shipped UI), a "专注" button in the session header
3497
- * action row, and a collapsible plugin card in the Plugins settings tab
3498
- * (`settings.plugin.item`). Rendering reuses the official client UI primitives
3499
- * module (`@deepseek-ai/dsh-client-ui-primitives` runtime id) — Markdown /
3500
- * projectUserText / Tooltip / Button — plus the conversation service's composer
3501
- * registry and the uiSession pending-interaction service (dsh 0.1.2).
3724
+ * action row, and the plugin's settings card. The settings surface lives where
3725
+ * the running dsh keeps it: the Settings → Plugins tab's keyed
3726
+ * `settings.plugin.item` slot on dsh <= 0.1.x, and the sidebar Plugins page's
3727
+ * `plugins.bundle.config` slot (keyed by this bundle's package name) on dsh
3728
+ * 0.2, which retired both that slot and the `settingsScope` service. Rendering
3729
+ * reuses the official client UI primitives module
3730
+ * (`@deepseek-ai/dsh-client-ui-primitives` runtime id) — Markdown /
3731
+ * projectUserText / Button — plus the conversation service's composer registry
3732
+ * and the uiSession pending-interaction / main-binding service.
3502
3733
  */
3503
3734
  const NS = "focus";
3735
+ /** This bundle's package name — the key `plugins.bundle.config` and the
3736
+ * `pluginNavigation.openBundle` deep link address this plugin by (dsh 0.2). */
3737
+ const PACKAGE_NAME = "dsh-focus-overlay";
3504
3738
  let warnedLegacy = false;
3505
3739
  function warnLegacyOnce() {
3506
3740
  if (warnedLegacy) return;
@@ -3516,8 +3750,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3516
3750
  "locale",
3517
3751
  "workspaces",
3518
3752
  "uiSession",
3519
- "uiConversation",
3520
- "settingsScope"
3753
+ "uiConversation"
3521
3754
  ],
3522
3755
  apply(ctx) {
3523
3756
  const style = document.createElement("style");
@@ -3550,6 +3783,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3550
3783
  const workspaces = ctx.workspaces;
3551
3784
  const conversation = ctx.get("conversation");
3552
3785
  const chatFileMentions = ctx.get("chatFileMentions");
3786
+ const sidebarRight = ctx.get("sidebarRight");
3553
3787
  const uiSession = ctx.get("uiSession");
3554
3788
  const uiConversation = ctx.get("uiConversation");
3555
3789
  if (!uiSession || !uiConversation) warnLegacyOnce();
@@ -3560,6 +3794,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3560
3794
  } catch {
3561
3795
  settingsScope = null;
3562
3796
  }
3797
+ const configForms = ctx.get("configForms");
3563
3798
  ctx.effect(() => {
3564
3799
  let currentId = void 0;
3565
3800
  let unsubSession = null;
@@ -3622,18 +3857,27 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3622
3857
  } catch {}
3623
3858
  if (!unsubChat) chatSlice = legacySliceOf(null, face.getSnapshot());
3624
3859
  };
3860
+ const mainBinding = () => {
3861
+ try {
3862
+ return uiSession && uiSession.current ? uiSession.current.getSnapshot() : null;
3863
+ } catch {
3864
+ return null;
3865
+ }
3866
+ };
3625
3867
  const onList = () => {
3626
- const ls = sessions.list.getSnapshot();
3627
- const next = ls ? ls.current : void 0;
3868
+ const next = currentSessionIdOf(sessions.list.getSnapshot(), mainBinding());
3628
3869
  if (next !== currentId) {
3629
3870
  currentId = next;
3630
3871
  watch(next);
3631
- }
3872
+ } else if (next != null && !unsubSession) watch(next);
3632
3873
  };
3633
3874
  onList();
3634
3875
  const unsubList = sessions.list.subscribe(onList);
3876
+ const mainSource = uiSession && uiSession.current;
3877
+ const unsubMain = mainSource && typeof mainSource.subscribe === "function" ? mainSource.subscribe(onList) : null;
3635
3878
  return () => {
3636
3879
  if (unsubList) unsubList();
3880
+ if (unsubMain) unsubMain();
3637
3881
  if (unsubSession) unsubSession();
3638
3882
  if (unsubChat) unsubChat();
3639
3883
  };
@@ -3668,6 +3912,8 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3668
3912
  uiSession,
3669
3913
  uiConversation,
3670
3914
  settingsScope,
3915
+ configForms,
3916
+ sidebarRight,
3671
3917
  t
3672
3918
  })));
3673
3919
  ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
@@ -3682,13 +3928,30 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3682
3928
  id: "dsh-focus-overlay",
3683
3929
  order: 1e3
3684
3930
  }, () => (0, react.createElement)(FocusSettingsCard, { t })));
3931
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
3932
+ name: "plugins.bundle.config",
3933
+ key: PACKAGE_NAME
3934
+ }, (props) => (0, react.createElement)(FocusBundleConfig, {
3935
+ ...props,
3936
+ t
3937
+ })));
3938
+ const pluginNavigation = ctx.get("pluginNavigation");
3939
+ const openSettings = pluginNavigation && typeof pluginNavigation.openBundle === "function" ? () => {
3940
+ try {
3941
+ pluginNavigation.openBundle(PACKAGE_NAME);
3942
+ return true;
3943
+ } catch {
3944
+ return false;
3945
+ }
3946
+ } : void 0;
3685
3947
  ctx.slots.inject("settings.onboarding", () => ctx.slots.register({
3686
3948
  name: "settings.onboarding",
3687
3949
  id: "dsh-focus-overlay",
3688
3950
  order: 10
3689
3951
  }, (props) => (0, react.createElement)(FocusOnboarding, {
3690
3952
  ...props,
3691
- t
3953
+ t,
3954
+ openSettings
3692
3955
  })));
3693
3956
  }
3694
3957
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-focus-overlay",
3
- "version": "0.12.3",
3
+ "version": "0.12.4",
4
4
  "description": "Focus Mode for the dsh web GUI: a full-screen reading overlay that hides chrome and folds the AI tool-call flow into summaries, reusing the official Markdown / image primitives.",
5
5
  "type": "module",
6
6
  "main": "lib/index.mjs",
@@ -31,7 +31,8 @@
31
31
  "0.1.2-alpha.5": "compatible",
32
32
  "0.1.2-rc.1": "compatible",
33
33
  "0.1.3-alpha.1": "compatible",
34
- "0.1.5-rc.1": "compatible"
34
+ "0.1.5-rc.1": "compatible",
35
+ "0.2.0-rc.2": "compatible"
35
36
  }
36
37
  }
37
38
  },