dsh-focus-overlay 0.12.4 → 0.13.0

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
@@ -5,7 +5,7 @@
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- A <b>focus mode</b> for the DeepSeek Harness (DSH) web GUI: one click to a full-screen reading overlay that hides the header, composer and sidebars, folding the AI tool-call flow into official-style count summaries — leaving only the conversation between you and the AI.<br>
8
+ A <b>focus mode</b> for the DeepSeek Harness (DSH) web GUI and the macOS / Windows desktop app: one click to a full-screen reading overlay that hides the header, composer and sidebars, folding the AI tool-call flow into official-style count summaries — leaving only the conversation between you and the AI.<br>
9
9
  Text, images, the turn rail, width handles and the answer card all reuse or 1:1 replicate official components, matching the chat view's look.
10
10
  </p>
11
11
 
@@ -26,6 +26,7 @@
26
26
  - **Auto-focus & reminders** — after a reply completes normally, optionally auto-enter focus at your question; while in focus, get a toast for a new reply or when the AI is waiting on you
27
27
  - **Width handles** — draggable strips beside the content column (official WidthHandle replica) that resize the reading column live and persist
28
28
  - **Hotkey F** — press `F` anywhere to enter focus mode instantly (toggleable in settings); typing in an input never triggers it
29
+ - **Desktop window chrome** — adapts to the dsh desktop app (macOS / Windows): the top of the overlay reserves the official frame clearance for the macOS traffic lights and the Windows native titlebar, that top band drags the window while focused, and the reservation shrinks automatically in native fullscreen
29
30
  - **Focus counter** — the settings card keeps a running total of your focus-mode entries; past 100 entries a GitHub Star nudge appears, is remembered after the first use, and then becomes a direct "open the project page" button
30
31
 
31
32
  These features are designed to give **small screens** more content space: tucking away the persistent header/composer and tool steps lets the conversation use as much of the screen as possible.
@@ -56,7 +57,9 @@ Click the line to expand that turn's full work detail (per-category one-line sum
56
57
  | Feature | Description |
57
58
  | --- | --- |
58
59
  | Full-screen overlay | Registered into `shell.overlay` (additive, `replaceRisk: none`) — covers header / composer / sidebar; the thin top bar keeps only the session title and an exit button |
59
- | Official rendering primitives | Assistant text through official `MarkdownText` (GFM + code highlighting + TeX + code copy buttons + footnotes); user messages through the official `projectUserText` projection; buttons, modals and icons are all official primitives |
60
+ | Official rendering primitives | Assistant text through official `MarkdownText` (GFM + code highlighting + TeX + code copy buttons + footnotes); user messages pick the official user-text projection the running dsh exports: `projectUserText` on 0.1.2–0.2.0 (plain runs plus `@`/`/` reference chips), and the official `UserTextRun` plain run after the 0.2.2 desktop runtime removed that function — a missing projection can never crash the overlay; buttons, modals and icons are all official primitives |
61
+ | Desktop window frame | Reserves top space from the official `--dsh-frame-overlay-top` / `--dsh-frame-top-clearance` / `--dsh-frame-chrome-top` variables for the macOS traffic lights (~48px) and the Windows native titlebar (40px), and provides a top drag strip matching the official drag region (`data-window-drag` + `-webkit-app-region: drag`) so the window stays draggable while focused; native fullscreen (`data-fullscreen`) drops the reservation to 20px; on Windows it also hides the official sidebar-collapse and new-session controls that the window chrome promotes into the titlebar layer when the sidebar is collapsed / the window is narrow — otherwise they painted above the focus layer and a stray click switched sessions while reading; both come back with the chrome on exit. In a plain browser the variables and markers are absent, so the 0px fallback keeps the old pixel-identical layout |
62
+ | File-mention chips | Inline-code paths in AI replies resolve through the official `chatFileMentions` into clickable chips: on dsh 0.2.x the overlay passes the same real Turn face (`deliverables` projection) the official chat turn tail uses and opens through the sidebar resource controller's `dsh-resource://file/session/…` address; on dsh ≤ 0.1.x it keeps the legacy owner shape and `workspaces.openPath`. No chip without projection data, and a resolver failure never crashes the overlay |
60
63
  | Image resolution | Assistant `image` blocks resolve through `uiConversation.imageUrl` (the dsh 0.1.2+ session-authorized image cache), falling back to the legacy resolver on older dsh |
61
64
  | Tool-call folding | Follows the official "transcript view" preference (normal / compact); compact mode folds each turn's work part into a "N tool calls · M messages · K subagents" disclosure line ("Thought for a while" when nothing is countable), clickable to expand the full detail; steering messages always stay top-level, never buried; the last turn stays unfolded while streaming |
62
65
  | Categorized summaries | The expanded detail counts tools by family: commands / edits / searches / reads / directory listings / subagents / todos / goals / workflows / skills / questions / plans / background jobs / context injections |
@@ -82,11 +85,11 @@ Click the line to expand that turn's full work detail (per-category one-line sum
82
85
  | Render error boundary | The overlay content is wrapped in an error boundary: one render crash never bricks the Focus button — the next open retries |
83
86
  | 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 |
84
87
  | 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 |
88
+ | 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`; the dsh 0.2.2 desktop runtime removed `projectUserText` in favour of `UserTextRun`), so an `undefined` component can never throw React #130 |
86
89
 
87
90
  ## Install
88
91
 
89
- Requires the `dsh` CLI (`>= 0.1.2-rc.1`) and Node `>= 22.19.0`.
92
+ Requires `dsh` (Web CLI or the desktop app, `>= 0.1.2-rc.1`) and, for source installs, Node `>= 22.19.0`.
90
93
 
91
94
  **From npm (recommended)**
92
95
 
@@ -121,6 +124,14 @@ dsh web
121
124
 
122
125
  Restart `dsh web` after install.
123
126
 
127
+ **DSH desktop app (macOS / Windows)**
128
+
129
+ The desktop app manages plugins itself, no CLI needed: **Plugins → Add plugin** in the sidebar, enter `dsh-focus-overlay` (or a local directory / `link:` path such as `link:/path/to/dsh-focus-overlay`), then configure it on this plugin's detail page. Reload the desktop window (or restart the app) after installing or updating.
130
+
131
+ Desktop support is verified against **dsh desktop 0.2.2-rc.1**: macOS and Windows share the same Electron renderer and client contracts; the window-chrome differences (macOS traffic lights / Windows native titlebar) are driven by the official `data-platform` / `data-windows-titlebar` / `data-fullscreen` markers, and the plugin adapts through the official frame variables — one install serves both platforms. In focus mode the top of the overlay is a draggable title band; native fullscreen (macOS green button / Windows maximize) shrinks the reservation automatically.
132
+
133
+ > The desktop app uses its own `desktop` profile: installing the plugin only into the Web CLI's `web` profile does not install it in the desktop app — add it once on the Plugins page there too.
134
+
124
135
  > **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".
125
136
 
126
137
  ## Usage
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <p align="center">中文 | <a href="README.en.md">English</a></p>
4
4
 
5
5
  <p align="center">
6
- 为 DeepSeek Harness(DSH)Web GUI 提供的<b>专注模式</b>:一键进入全屏阅读遮罩,隐藏标题区、输入区与侧栏,把 AI 的工具调用过程折叠成官方样式的计数摘要,只保留「你与 AI 的对话」本身。<br>
6
+ 为 DeepSeek Harness(DSH)Web GUI 与桌面版(macOS / Windows)提供的<b>专注模式</b>:一键进入全屏阅读遮罩,隐藏标题区、输入区与侧栏,把 AI 的工具调用过程折叠成官方样式的计数摘要,只保留「你与 AI 的对话」本身。<br>
7
7
  文本、图片、导航条、宽度调节与答题卡均复用或 1:1 复刻官方组件,观感与聊天视图一致。
8
8
  </p>
9
9
 
@@ -24,6 +24,7 @@
24
24
  - **自动专注与提醒** —— 回复正常完成后可自动进入专注并定位到你的提问;专注中收到新回复、或 AI 等待你回答时弹出提醒
25
25
  - **宽度调节** —— 正文两侧的拖拽把手(官方 WidthHandle 复刻),实时拖动阅读列宽并持久化
26
26
  - **快捷键 F** —— 任意界面按 `F` 一键进入专注模式,可在设置中关闭;输入框内打字不会误触发
27
+ - **桌面版窗口适配** —— 适配 dsh 桌面版(macOS / Windows):顶部按官方窗口框架变量为 macOS 红绿灯与 Windows 原生标题栏让出空间,专注时这段顶部空白条可直接拖动窗口,进入全屏后自动收起预留空间
27
28
  - **专注计数** —— 设置卡片最底部累计展示你进入专注模式的次数;超过 100 次后出现 GitHub Star 引导,点过一次即被记住,之后提供「前往 GitHub 项目页面」直达按钮
28
29
 
29
30
  这些功能专为**小屏幕**提供更多内容呈现空间:收起常驻的标题 / 输入区与工具步骤,让有限的屏幕尽可能多地展示对话。
@@ -54,7 +55,9 @@
54
55
  | 功能 | 说明 |
55
56
  | --- | --- |
56
57
  | 全屏专注遮罩 | 经 `shell.overlay` 注册(纯增量、`replaceRisk: none`),覆盖标题区 / 输入区 / 侧栏;顶部只留会话标题与「退出专注」按钮 |
57
- | 官方渲染原语 | AI 文本走官方 `MarkdownText`(GFM + 代码高亮 + TeX + 代码复制按钮 + 脚注),用户消息走官方 `projectUserText` 投影;按钮、模态、图标均为官方原语 |
58
+ | 官方渲染原语 | AI 文本走官方 `MarkdownText`(GFM + 代码高亮 + TeX + 代码复制按钮 + 脚注);用户消息按运行版本选择官方用户文本投影:dsh 0.1.2–0.2.0 用 `projectUserText`(纯文本 + `@`/`/` 引用 chip),dsh 0.2.2 桌面版移除了该函数后自动回退到官方 `UserTextRun`(纯文本 run),不会因 `undefined` 组件崩溃;按钮、模态、图标均为官方原语 |
59
+ | 桌面版窗口框架 | 按官方 `--dsh-frame-overlay-top` / `--dsh-frame-top-clearance` / `--dsh-frame-chrome-top` 变量为 macOS 红绿灯(≈48px)与 Windows 原生标题栏(40px)预留顶部空间,并提供一个与官方拖拽区一致的顶部拖拽条(`data-window-drag` + `-webkit-app-region: drag`),专注时可拖动窗口;原生全屏(`data-fullscreen`)时预留自动降为 20px;Windows 上还会隐藏被官方提升到标题栏层的「侧边栏开关」与「新建会话」按钮(侧栏折叠 / 窄窗口时出现),避免它们浮在专注层之上、误触后在你阅读时切换会话;退出专注后随窗口 chrome 一起恢复。普通浏览器中这些变量与标记不存在,回退 0px、与旧版逐像素一致 |
60
+ | 文件引用 chip | AI 回复中的行内代码路径由官方 `chatFileMentions` 解析为可点击 chip:dsh 0.2.x 传入官方同款的真实 Turn face(`deliverables` 投影),点击经右侧栏资源控制器以 `dsh-resource://file/session/…` 打开;dsh ≤ 0.1.x 继续使用旧的 owner 形状与 `workspaces.openPath`;无投影数据时不显示 chip,绝不因解析异常崩溃 |
58
61
  | 图片解析 | assistant 的 `image` 块经 `uiConversation.imageUrl`(dsh 0.1.2+ 的会话授权图片缓存)解析,旧版 dsh 自动回退到 legacy 解析器 |
59
62
  | 工具调用折叠 | 遵循官方「对话显示」偏好(normal / compact);compact 模式把每轮的工作部分折叠成「N 次工具调用 · M 条消息 · K 个 subagent」披露行(无内容时显示「已思考」),点击可展开完整明细;steering 消息始终独立成行不被折叠;流式中的最后一轮保持展开直到结束 |
60
63
  | 分类摘要明细 | 展开后按工具类别计数:命令 / 编辑 / 搜索 / 读取 / 列目录 / 子代理 / 待办 / 目标 / 工作流 / 技能 / 提问 / 计划 / 后台任务 / 上下文注入 |
@@ -80,11 +83,11 @@
80
83
  | 渲染错误兜底 | 遮罩内容包裹错误边界:一次渲染崩溃不会永久打死「专注」按钮,下次打开自动重试 |
81
84
  | 兼容 DSH-better-sidebar | 进入专注时在 `<body>` 打标记,样式层自动隐藏其右上角的收放面板按钮与已开启的右侧 / 底部面板,并释放被挤压的布局;退出专注后按原状态还原 |
82
85
  | 旧版兼容模式 | dsh < 0.1.2 缺少新服务时降级运行(legacy 快照适配),控制台一次性提示,不会崩溃 |
83
- | 跨版本构件兼容 | 图标等官方构件在两个大版本间改名时按可用性回退(dsh 0.2 移除了 `…14` / `…16` 定尺图标,改用 `…Regular` + `size`),不会因为 `undefined` 组件触发 React #130 |
86
+ | 跨版本构件兼容 | 图标等官方构件在两个大版本间改名时按可用性回退(dsh 0.2 移除了 `…14` / `…16` 定尺图标,改用 `…Regular` + `size`;dsh 0.2.2 桌面版移除了 `projectUserText`,回退到 `UserTextRun`),不会因为 `undefined` 组件触发 React #130 |
84
87
 
85
88
  ## 安装
86
89
 
87
- 需要 `dsh` CLI(`>= 0.1.2-rc.1`),Node `>= 22.19.0`。
90
+ 需要 `dsh`(Web CLI 或桌面版,`>= 0.1.2-rc.1`),Node `>= 22.19.0`(仅源码安装时需要 Node)。
88
91
 
89
92
  **从 npm(推荐)**
90
93
 
@@ -119,6 +122,14 @@ dsh web
119
122
 
120
123
  安装后重启 `dsh web` 生效。
121
124
 
125
+ **DSH 桌面版(macOS / Windows)**
126
+
127
+ dsh 桌面版自带插件管理,不需要 CLI:侧栏 **Plugins → 添加插件**,填入 `dsh-focus-overlay`(或本机目录 / `link:` 路径,例如 `link:/path/to/dsh-focus-overlay`),安装后在本插件详情页中配置。安装或更新后重新加载桌面窗口(或重启桌面应用)生效。
128
+
129
+ 桌面版适配基于 **dsh 桌面版 0.2.2-rc.1** 验证:macOS 与 Windows 共用同一套 Electron 渲染层与客户端契约,窗口框架差异(macOS 红绿灯 / Windows 原生标题栏)由官方 `data-platform` / `data-windows-titlebar` / `data-fullscreen` 标记驱动,插件按官方窗口框架变量自适应,无需按平台分别安装。专注模式下顶部为可拖动窗口的标题空白条;按原生全屏切换后(macOS 绿灯 / Windows 最大化按钮)自动收起预留空间。
130
+
131
+ > 桌面版使用独立的 `desktop` profile:如果只在 Web CLI 的 `web` profile 里装过本插件,桌面版仍需在 Plugins 页面安装一次。
132
+
122
133
  > **首次安装**:重启后欢迎页会弹出一次「专注模式」引导,说明功能并让你就地完成配置;其中所有设置随时可以更改——新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。
123
134
 
124
135
  ## 使用
package/lib/client.js CHANGED
@@ -29,6 +29,46 @@ window.__ModuleLoader__.load({
29
29
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
30
30
  _deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
31
31
  let react_jsx_runtime = require("react/jsx-runtime");
32
+ //#region src/client/primitives.ts
33
+ /** Cross-release primitives resolution.
34
+ *
35
+ * dsh keeps renaming/removing symbols in `@deepseek-ai/dsh-client-ui-primitives`
36
+ * between releases, and a stale named import resolves to `undefined` — React
37
+ * then throws #130 ("element type is invalid") the moment it renders. Two
38
+ * such breaks are already in this repo's history: `MessageText` (0.1.3) and
39
+ * the fixed-size icon variants (0.2). The 0.2.2 desktop runtime adds a third:
40
+ * `projectUserText` is gone.
41
+ *
42
+ * The official user-text pipeline changed shape in 0.2.2: the conversation
43
+ * surface now parses text itself and renders inline nodes, falling back to
44
+ * the `UserTextRun` primitive (a plain inline run) for unmatched text. This
45
+ * module picks whichever projection the running primitives module actually
46
+ * exports, so one client bundle serves 0.1.2 through 0.2.2:
47
+ *
48
+ * - dsh 0.1.2 … 0.2.0: `projectUserText(text, sessionLabels, …)` returns
49
+ * inline plain runs plus `@session` / `/skill` chips (the richer path).
50
+ * - dsh 0.2.2+: `UserTextRun` renders the text as a plain inline run; the
51
+ * overlay's bubble chrome still supplies `white-space: pre-wrap`, so the
52
+ * text keeps its shape. Reference chips in *user* text are the only
53
+ * cosmetic loss on that generation.
54
+ * - Anything else: return the raw string (React renders it as a text node).
55
+ *
56
+ * Import the namespace once (not named symbols) so the resolver can never
57
+ * crash at module-evaluation time. */
58
+ /** Render one user prompt's text through the projection the running dsh
59
+ * exports. `sessionLabels` feeds the older chip projection; newer
60
+ * generations ignore it. */
61
+ function projectUserTextCompat(text, sessionLabels = []) {
62
+ const primitives = _deepseek_ai_dsh_client_ui_primitives;
63
+ const project = primitives.projectUserText;
64
+ if (typeof project === "function") try {
65
+ return project(text, sessionLabels);
66
+ } catch {}
67
+ const UserTextRun = primitives.UserTextRun;
68
+ if (typeof UserTextRun === "function") return (0, react.createElement)(UserTextRun, { text });
69
+ return text;
70
+ }
71
+ //#endregion
32
72
  //#region src/client/icons.ts
33
73
  /** Cross-release product-icon resolution.
34
74
  *
@@ -200,6 +240,44 @@ window.__ModuleLoader__.load({
200
240
  const body = relative.split("/").map(encode).join("/");
201
241
  return `dsh-resource://file/session/${encode(String(sessionId))}/${body}`;
202
242
  }
243
+ /** Resolve the owner object the official `chatFileMentions.forClosing` expects.
244
+ *
245
+ * dsh 0.2.x's provider resolves a turn's produced / presented files through
246
+ * `owner.turn.data.get('deliverables')` — the owner the official ui-chat turn
247
+ * tail passes — and throws on the plain `{turn: {turn, step}}` owner shape the
248
+ * overlay used to pass; the caller's catch then silently disabled the inline
249
+ * file-mention chips (pre-existing on every 0.2 build, not a desktop
250
+ * regression). The Chat snapshot's `timeline.turns` carries the real Turn
251
+ * faces, so look one up by the assistant node's turn number. Chips belong to
252
+ * the settled closing reply only, so a missing or still-running turn resolves
253
+ * to null. Generations without a `timeline` (0.1.x) keep the legacy shape
254
+ * their provider accepts. */
255
+ function fileMentionOwnerOf(chatView, node, openFile) {
256
+ const turns = chatView && chatView.timeline ? chatView.timeline.turns : null;
257
+ if (turns && typeof turns.get === "function") {
258
+ const turnFace = turns.get(node && node.turn);
259
+ if (!turnFace || turnFace.status !== "closed") return null;
260
+ return {
261
+ owner: {
262
+ turn: turnFace,
263
+ seq: node.seq,
264
+ openFile
265
+ },
266
+ modern: true
267
+ };
268
+ }
269
+ return {
270
+ owner: {
271
+ turn: {
272
+ turn: node.turn,
273
+ step: node.step
274
+ },
275
+ seq: node.seq,
276
+ openFile
277
+ },
278
+ modern: false
279
+ };
280
+ }
203
281
  function flattenText(content) {
204
282
  if (!content || !content.length) return "";
205
283
  let out = "";
@@ -1783,7 +1861,34 @@ window.__ModuleLoader__.load({
1783
1861
  //#region src/client/styles.ts
1784
1862
  /** Package-owned stylesheet text (injected by the client apply, cleaned up on unload). */
1785
1863
  const FOCUS_CSS = `
1786
- .fm-overlay{position:fixed;inset:0;z-index:2147483000;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#1a1a1a);display:flex;flex-direction:column;pointer-events:auto;font-family:var(--dsw-font-family,sans-serif);outline:none}
1864
+ .fm-overlay{position:fixed;inset:0;z-index:2147483000;box-sizing:border-box;padding-top:var(--dsh-frame-overlay-top,0px);background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#1a1a1a);display:flex;flex-direction:column;pointer-events:auto;font-family:var(--dsw-font-family,sans-serif);outline:none}
1865
+ /* Desktop window frame (dsh desktop 0.2.2+, macOS and Windows): the shell's
1866
+ hidden native titlebar area is web content owned by whatever fills the
1867
+ viewport, so this overlay reserves it via *.fm-overlay's padding-top and
1868
+ provides the drag surface itself. Heights come from the official layout
1869
+ variables set on <html> by dsh-client-ui-layout (+ the desktop preload for
1870
+ --dsh-windows-titlebar-height); the 0px fallback keeps the plain web
1871
+ generation pixel-identical. On macOS the shell's own
1872
+ [data-window-drag]{-webkit-app-region:drag} applies; the explicit
1873
+ app-region also makes the strip draggable in the Windows renderer.
1874
+ pointer-events stays enabled: a drag region must be hit-testable. */
1875
+ .fm-frame-strip{position:absolute;top:0;left:0;right:0;height:var(--dsh-frame-overlay-top,0px);flex:none;-webkit-app-region:drag;-webkit-user-select:none;user-select:none;z-index:0}
1876
+ /* Windows desktop: dsh-client-ui-sidebar promotes the sidebar collapse toggle
1877
+ to a fixed z-30 layer itself, above the official shell.overlay layer (z-20)
1878
+ that hosts this overlay — so without this rule the toggle floats above focus
1879
+ mode inside the titlebar band and steals clicks/drags from the drag strip
1880
+ (macOS keeps it behind the overlay). Hide it exactly while focus is on; it
1881
+ comes back with the rest of the chrome when focus exits. */
1882
+ html[data-windows-titlebar] body[data-fm-focus] [data-sidebar-toggle]{display:none!important}
1883
+ /* Same Windows titlebar band: when the official sidebar is collapsed (user
1884
+ collapsed it, or the window is narrower than 768px — Windows has no drawer),
1885
+ dsh-client-ui-sidebar promotes its "new session" control to the same fixed
1886
+ z-30 titlebar layer (left:48px, top:6px). It would sit above the overlay and
1887
+ click-through would start a new session under the reader, so hide it with
1888
+ the rest of the window chrome while focus is on. The hashed class name is
1889
+ the only stable hook the shipped CSS exposes; the e2e desktop-frame leak
1890
+ guard fails loudly if a future build renames it. */
1891
+ html[data-windows-titlebar] body[data-fm-focus] [class*="newSession"]{display:none!important}
1787
1892
  .fm-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.12));flex:0 0 auto}
1788
1893
  .fm-title{font-size:calc(14px + var(--dsh-content-font-delta,0px));font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1789
1894
  .fm-body-wrap{position:relative;flex:1 1 auto;display:flex;min-height:0}
@@ -2399,16 +2504,18 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
2399
2504
  }
2400
2505
  /** Body of a sent user message / steering row.
2401
2506
  *
2402
- * dsh 0.1.3-alpha.2 removed the `MessageText` component from the primitives
2403
- * module in favour of the `projectUserText` display projection (which has
2404
- * existed since 0.1.2 and is still exported by 0.1.5). Importing the removed
2405
- * name made it `undefined`, so the overlay threw React #130 the moment it
2406
- * rendered a session containing a user message — the "toggle does nothing"
2407
- * symptom. `projectUserText` returns inline plain runs plus `@session` /
2408
- * `/skill` chips; `.fm-user` still supplies the bubble chrome and
2409
- * `white-space: pre-wrap`, so the text keeps its shape. */
2507
+ * The official user-text projection has now changed twice: dsh 0.1.3-alpha.2
2508
+ * removed `MessageText` in favour of `projectUserText` (which this component
2509
+ * then imported as a named binding — React #130 the moment a user row
2510
+ * rendered when the later removal made it `undefined`), and dsh 0.2.2's
2511
+ * desktop runtime removed `projectUserText` itself (the conversation surface
2512
+ * parses user text and falls back to the `UserTextRun` primitive).
2513
+ * `projectUserTextCompat` resolves the projection the running primitives
2514
+ * module exports (`projectUserText` → `UserTextRun` → raw text), so one
2515
+ * bundle serves 0.1.2 through 0.2.2. `.fm-user` still supplies the bubble
2516
+ * chrome and `white-space: pre-wrap`, so the text keeps its shape. */
2410
2517
  function UserText({ text }) {
2411
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: (0, _deepseek_ai_dsh_client_ui_primitives.projectUserText)(text, []) });
2518
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: projectUserTextCompat(text) });
2412
2519
  }
2413
2520
  let bodyEl = null;
2414
2521
  const anchors = {};
@@ -2964,22 +3071,28 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
2964
3071
  sessions,
2965
3072
  currentId
2966
3073
  ]);
3074
+ /** Assistant-row inline-code file mentions. The official provider resolves
3075
+ * only a turn's produced / presented files, and dsh 0.2.x reads them from
3076
+ * the real Turn face (`owner.turn.data.get('deliverables')`) — the same
3077
+ * owner the official ui-chat turn tail passes. Handing it a plain
3078
+ * `{turn: number, step}` makes `forClosing` throw, and the plugin's catch
3079
+ * silently disabled the chips on every 0.2 build (pre-existing bug, not a
3080
+ * 0.2.2 regression). `fileMentionOwnerOf` (model.ts) resolves the Turn face
3081
+ * from the Chat snapshot's `timeline.turns`, and keeps the legacy owner
3082
+ * shape for generations without a timeline. */
2967
3083
  const fileMentionsFor = (0, react.useCallback)((node) => {
2968
3084
  if (!chatFileMentions) return void 0;
2969
3085
  try {
2970
- return chatFileMentions.forClosing({
2971
- turn: {
2972
- turn: node.turn,
2973
- step: node.step
2974
- },
2975
- seq: node.seq,
2976
- openFile: (path, options) => openMentionedFile(path, options)
2977
- }, currentId);
3086
+ const openFile = (path, options) => openMentionedFile(path, options);
3087
+ const resolved = fileMentionOwnerOf(chatView, node, openFile);
3088
+ if (resolved === null) return void 0;
3089
+ return chatFileMentions.forClosing(resolved.owner, currentId);
2978
3090
  } catch {
2979
3091
  return;
2980
3092
  }
2981
3093
  }, [
2982
3094
  chatFileMentions,
3095
+ chatView,
2983
3096
  currentId,
2984
3097
  openMentionedFile
2985
3098
  ]);
@@ -3030,7 +3143,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3030
3143
  setDockFocused(f);
3031
3144
  if (!f) setEngaged(false);
3032
3145
  };
3033
- const queueCount = snap && snap.queue ? snap.queue.length : 0;
3146
+ const queueCount = inputState && Array.isArray(inputState.queue) ? inputState.queue.length : snap && snap.queue ? snap.queue.length : 0;
3034
3147
  const occCount = inputState && inputState.occurrences ? inputState.occurrences.length : 0;
3035
3148
  const promptErr = snap && snap.promptError && snap.promptError.op === "send" ? snap.promptError.error : null;
3036
3149
  const errorLine = promptErr ? promptErr.message || promptErr.code || "send failed" : null;
@@ -3348,6 +3461,11 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
3348
3461
  ref: overlayRef,
3349
3462
  tabIndex: -1,
3350
3463
  children: [
3464
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3465
+ className: "fm-frame-strip",
3466
+ "data-window-drag": true,
3467
+ "aria-hidden": "true"
3468
+ }),
3351
3469
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3352
3470
  className: "fm-topbar",
3353
3471
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-focus-overlay",
3
- "version": "0.12.4",
3
+ "version": "0.13.0",
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",
@@ -32,7 +32,8 @@
32
32
  "0.1.2-rc.1": "compatible",
33
33
  "0.1.3-alpha.1": "compatible",
34
34
  "0.1.5-rc.1": "compatible",
35
- "0.2.0-rc.2": "compatible"
35
+ "0.2.0-rc.2": "compatible",
36
+ "0.2.2-rc.1": "compatible"
36
37
  }
37
38
  }
38
39
  },