dsh-focus-overlay 0.5.0 → 0.7.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
@@ -60,6 +60,7 @@ Once in focus mode, an assistant turn no longer shows every step — it folds in
60
60
  | Plugin configuration card | A collapsible card under "Settings → Plugins → Plugin configuration": navbar toggle, open-position strategy, text-area width — persisted to `localStorage` |
61
61
  | Auto-enter focus | After a reply **completes normally**, auto-open focus at your question; abnormal endings (stop / error / max-tokens / interrupt) never fire |
62
62
  | Reply / waiting reminders | In focus, a "New reply ready + View" toast on completion; an "AI is waiting for your reply + Reply" toast on a question/approval, auto-cleared once answered |
63
+ | DSH-better-sidebar compatibility | While in focus, auto-hide its top-right panel toggle buttons and any open right/bottom panels (and release the squeezed layout); restored on exit |
63
64
 
64
65
  ## Install
65
66
 
@@ -98,6 +99,8 @@ dsh web
98
99
 
99
100
  Restart `dsh web` after install.
100
101
 
102
+ > **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".
103
+
101
104
  ## Usage
102
105
 
103
106
  1. Open a session and click the **"Focus"** button in the session header action row.
@@ -105,16 +108,18 @@ Restart `dsh web` after install.
105
108
  3. Hover the right-side dots to preview, click to jump; a centered "↓ back to latest" button appears when you're not at the bottom.
106
109
  4. Press `Esc` or click "Exit focus" to return — the original UI is untouched.
107
110
 
111
+ > **Compatibility note**: if [DSH-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) is also installed, entering focus mode auto-hides its two top-right panel toggle buttons and any open right/bottom panels (releasing the squeezed layout); they're restored as they were on exit, without touching that plugin's layout state.
112
+
108
113
  ## Settings
109
114
 
110
115
  In the sidebar "Settings → Plugins → Plugin configuration", expand the "Focus Mode" card:
111
116
 
112
117
  | Option | Description |
113
118
  | --- | --- |
114
- | Show right-side navbar | Toggle the nav dots (default on) |
115
- | Preserve chat position on open | Open at your current reading position; off opens at the latest (default on) |
119
+ | Auto-enter focus mode when the AI finishes a reply | On a normal completion: auto-open at your question when closed, or a "New reply ready" toast when open (default off) |
120
+ | Sync the current reading position when entering focus mode | Open at the message you're reading; off opens at the latest (default on) |
121
+ | Show right-side turn navbar | One navigation dot per user message (turn) — hover to preview, click to jump (default on) |
116
122
  | Text width | 480–1200px slider for the reading column (default 760px) |
117
- | Auto-enter focus mode when a reply completes | On a normal completion: auto-open at your question when closed, or a "New reply ready" toast when open; an "AI is waiting" toast on a question/approval (default off) |
118
123
 
119
124
  ## License
120
125
 
package/README.md CHANGED
@@ -58,6 +58,7 @@
58
58
  | 插件配置卡片 | 在「设置 → 插件 → 插件配置」中的可折叠卡片,含导航条开关、打开定位策略、文字区宽度,持久化到 `localStorage` |
59
59
  | 自动进入专注 | 回复**正常完成**后自动打开专注并定位到本轮你的提问;异常结束(停止 / 报错 / 超 token / 打断)不触发 |
60
60
  | 回复 / 等待提醒 | 专注中回复完成弹「新回复已生成 + 查看」;AI 提问 / 审批等待时弹「AI 正在等待你的回复 + 去回复」,回答完自动消失 |
61
+ | 兼容 DSH-better-sidebar | 进入专注时自动隐藏其右上角的收放面板按钮与已开启的右侧/底部面板,并释放被挤压的布局;退出专注后按原状态还原 |
61
62
 
62
63
  ## 安装
63
64
 
@@ -96,6 +97,8 @@ dsh web
96
97
 
97
98
  安装后重启 `dsh web` 生效。
98
99
 
100
+ > **首次安装**:重启后欢迎页会弹出一次「专注模式」引导,说明功能并让你就地完成配置;其中所有设置随时可在「设置 → 插件 → 插件配置」中更改。
101
+
99
102
  ## 使用
100
103
 
101
104
  1. 打开任意会话,点击标题栏操作区的 **「专注」** 按钮。
@@ -103,16 +106,18 @@ dsh web
103
106
  3. 右侧导航圆点可悬停预览、点击跳转;离开底部时右下出现居中的「↓ 回到最新」。
104
107
  4. 按 `Esc` 或点「退出专注」返回原界面,原位置/状态保持不变。
105
108
 
109
+ > **兼容提示**:若同时安装了 [DSH-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar),进入专注模式会自动隐藏其右上角的两个收放面板按钮以及已开启的右侧/底部面板(并释放被挤压的布局);退出专注后按原状态恢复,不会改动该插件的面板布局。
110
+
106
111
  ## 设置
107
112
 
108
113
  侧栏「设置 → 插件 → 插件配置」中,展开「专注模式」卡片:
109
114
 
110
115
  | 选项 | 说明 |
111
116
  | --- | --- |
112
- | 显示右侧导航条 | 开关导航圆点(默认开) |
113
- | 打开时保留聊天位置 | 进入时定位到当前阅读位置;关闭则定位到最新(默认开) |
114
- | 文字区宽度 | 480–1200px 滑块,调整阅读列宽(默认 760px) |
115
- | 回复结束后自动进入专注模式 | 回复正常完成后:关闭时自动进入并定位到提问,已打开时弹「新回复已生成」;AI 提问 / 审批等待时弹「AI 正在等待你的回复」(默认关) |
117
+ | AI完成回复后,自动进入专注模式 | AI 正常完成回复后自动进入并定位到你的提问;已打开时弹「新回复已生成」(默认关) |
118
+ | 进入专注模式时,同步当前阅读位置 | 进入时定位到你正在阅读的消息;关闭则定位到最新(默认开) |
119
+ | 显示右侧轮次导航条 | 在右侧显示导航圆点,每个用户消息(一轮)对应一个,悬停预览、点击跳转(默认开) |
120
+ | 文字区宽度 | 480–1200px 滑块,调整正文阅读列宽(默认 760px) |
116
121
 
117
122
  ## 许可
118
123
 
package/lib/client.js CHANGED
@@ -6,53 +6,6 @@ window.__ModuleLoader__.load({
6
6
  let react = require("react");
7
7
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
- //#region src/client/settings.ts
10
- const KEY = "dsh-focus-overlay:prefs";
11
- const DEFAULTS = {
12
- navbar: true,
13
- scroll: "preserve",
14
- width: 760,
15
- autoFocus: false
16
- };
17
- function load() {
18
- try {
19
- const raw = localStorage.getItem(KEY);
20
- if (raw) return {
21
- ...DEFAULTS,
22
- ...JSON.parse(raw)
23
- };
24
- } catch {}
25
- return { ...DEFAULTS };
26
- }
27
- let prefs = load();
28
- const listeners = [];
29
- const prefsStore = {
30
- get: () => prefs,
31
- set: (next) => {
32
- prefs = next;
33
- try {
34
- localStorage.setItem(KEY, JSON.stringify(next));
35
- } catch {}
36
- for (const l of listeners) l();
37
- },
38
- update: (patch) => prefsStore.set({
39
- ...prefs,
40
- ...patch
41
- }),
42
- subscribe: (l) => {
43
- listeners.push(l);
44
- return () => {
45
- const i = listeners.indexOf(l);
46
- if (i >= 0) listeners.splice(i, 1);
47
- };
48
- }
49
- };
50
- function usePrefs() {
51
- const [p, setP] = (0, react.useState)(prefsStore.get);
52
- (0, react.useEffect)(() => prefsStore.subscribe(() => setP(prefsStore.get)), []);
53
- return p;
54
- }
55
- //#endregion
56
9
  //#region src/client/model.ts
57
10
  function flattenText(content) {
58
11
  if (!content || !content.length) return "";
@@ -313,6 +266,74 @@ window.__ModuleLoader__.load({
313
266
  const pending = snap && snap.pending;
314
267
  return Array.isArray(pending) && pending.length > 0;
315
268
  }
269
+ /** True once the persisted onboarding flag matches the current intro version.
270
+ * Kept as a pure comparison so the "already seen?" decision is testable and
271
+ * can be versioned: bump the version to re-show the intro after a major change. */
272
+ function onboardingSeen(stored, version) {
273
+ return stored === version;
274
+ }
275
+ //#endregion
276
+ //#region src/client/settings.ts
277
+ const KEY = "dsh-focus-overlay:prefs";
278
+ const DEFAULTS = {
279
+ navbar: true,
280
+ scroll: "preserve",
281
+ width: 760,
282
+ autoFocus: false
283
+ };
284
+ function load() {
285
+ try {
286
+ const raw = localStorage.getItem(KEY);
287
+ if (raw) return {
288
+ ...DEFAULTS,
289
+ ...JSON.parse(raw)
290
+ };
291
+ } catch {}
292
+ return { ...DEFAULTS };
293
+ }
294
+ let prefs = load();
295
+ const listeners = [];
296
+ const prefsStore = {
297
+ get: () => prefs,
298
+ set: (next) => {
299
+ prefs = next;
300
+ try {
301
+ localStorage.setItem(KEY, JSON.stringify(next));
302
+ } catch {}
303
+ for (const l of listeners) l();
304
+ },
305
+ update: (patch) => prefsStore.set({
306
+ ...prefs,
307
+ ...patch
308
+ }),
309
+ subscribe: (l) => {
310
+ listeners.push(l);
311
+ return () => {
312
+ const i = listeners.indexOf(l);
313
+ if (i >= 0) listeners.splice(i, 1);
314
+ };
315
+ }
316
+ };
317
+ function usePrefs() {
318
+ const [p, setP] = (0, react.useState)(prefsStore.get);
319
+ (0, react.useEffect)(() => prefsStore.subscribe(() => setP(prefsStore.get)), []);
320
+ return p;
321
+ }
322
+ const ONBOARD_KEY = "dsh-focus-overlay:onboarded";
323
+ const onboardingStore = {
324
+ isDone: () => {
325
+ try {
326
+ return onboardingSeen(localStorage.getItem(ONBOARD_KEY), "1");
327
+ } catch {
328
+ return false;
329
+ }
330
+ },
331
+ markDone: () => {
332
+ try {
333
+ localStorage.setItem(ONBOARD_KEY, "1");
334
+ } catch {}
335
+ }
336
+ };
316
337
  //#endregion
317
338
  //#region src/client/FocusView.tsx
318
339
  let focusOn = false;
@@ -752,8 +773,91 @@ window.__ModuleLoader__.load({
752
773
  children: t("toggle")
753
774
  });
754
775
  }
755
- function FocusSettingsCard({ t }) {
776
+ function FocusPrefsFields({ t }) {
756
777
  const prefs = usePrefs();
778
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
780
+ className: "fm-plugin-field",
781
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
782
+ className: "fm-plugin-check",
783
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
784
+ type: "checkbox",
785
+ checked: prefs.autoFocus,
786
+ onChange: (e) => prefsStore.update({ autoFocus: e.target.checked })
787
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
788
+ className: "fm-plugin-check-label",
789
+ children: t("settings.autoFocus")
790
+ })]
791
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
792
+ className: "fm-plugin-field-hint",
793
+ children: t("settings.autoFocus.hint")
794
+ })]
795
+ }),
796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
797
+ className: "fm-plugin-field",
798
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
799
+ className: "fm-plugin-check",
800
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
801
+ type: "checkbox",
802
+ checked: prefs.scroll === "preserve",
803
+ onChange: (e) => prefsStore.update({ scroll: e.target.checked ? "preserve" : "bottom" })
804
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
805
+ className: "fm-plugin-check-label",
806
+ children: t("settings.scrollPreserve")
807
+ })]
808
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
809
+ className: "fm-plugin-field-hint",
810
+ children: t("settings.scrollPreserve.hint")
811
+ })]
812
+ }),
813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
814
+ className: "fm-plugin-field",
815
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
816
+ className: "fm-plugin-check",
817
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
818
+ type: "checkbox",
819
+ checked: prefs.navbar,
820
+ onChange: (e) => prefsStore.update({ navbar: e.target.checked })
821
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
822
+ className: "fm-plugin-check-label",
823
+ children: t("settings.navbar")
824
+ })]
825
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
826
+ className: "fm-plugin-field-hint",
827
+ children: t("settings.navbar.hint")
828
+ })]
829
+ }),
830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
831
+ className: "fm-plugin-field",
832
+ children: [
833
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
834
+ className: "fm-plugin-field-head",
835
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
836
+ className: "fm-plugin-field-label",
837
+ children: t("settings.width")
838
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
839
+ className: "fm-plugin-field-value",
840
+ children: [prefs.width, "px"]
841
+ })]
842
+ }),
843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
844
+ type: "range",
845
+ className: "fm-plugin-range",
846
+ min: 480,
847
+ max: 1200,
848
+ step: 40,
849
+ value: prefs.width,
850
+ onChange: (e) => prefsStore.update({ width: Number(e.target.value) })
851
+ }),
852
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
853
+ className: "fm-plugin-field-hint",
854
+ children: t("settings.width.hint")
855
+ })
856
+ ]
857
+ })
858
+ ] });
859
+ }
860
+ function FocusSettingsCard({ t }) {
757
861
  const [open, setOpen] = (0, react.useState)(false);
758
862
  const title = t("settings.label");
759
863
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
@@ -774,90 +878,100 @@ window.__ModuleLoader__.load({
774
878
  children: t("settings.description")
775
879
  })]
776
880
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "fm-plugin-card-chevron" + (open ? " fm-plugin-card-chevron-open" : "") })]
777
- }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
881
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
778
882
  className: "fm-plugin-card-body",
883
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusPrefsFields, { t })
884
+ }) : null]
885
+ });
886
+ }
887
+ function FocusOnboarding(props) {
888
+ const { complete, openSection, t } = props;
889
+ const [done, setDone] = (0, react.useState)(() => onboardingStore.isDone());
890
+ const finished = (0, react.useRef)(false);
891
+ const finish = (0, react.useCallback)(() => {
892
+ if (finished.current) return;
893
+ finished.current = true;
894
+ complete();
895
+ }, [complete]);
896
+ (0, react.useEffect)(() => {
897
+ if (done) finish();
898
+ }, [done, finish]);
899
+ (0, react.useEffect)(() => {
900
+ const appRoot = document.getElementById("root");
901
+ if (!appRoot) return;
902
+ const previous = appRoot.inert;
903
+ appRoot.inert = true;
904
+ return () => {
905
+ appRoot.inert = previous;
906
+ };
907
+ }, []);
908
+ if (done) return null;
909
+ const dismiss = () => {
910
+ onboardingStore.markDone();
911
+ setDone(true);
912
+ finish();
913
+ };
914
+ const goSettings = () => {
915
+ onboardingStore.markDone();
916
+ setDone(true);
917
+ finish();
918
+ openSection("plugins");
919
+ };
920
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
921
+ open: true,
922
+ title: t("onboarding.title"),
923
+ onClose: dismiss,
924
+ headless: true,
925
+ className: "fm-onboard",
926
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
927
+ className: "fm-onboard-content",
779
928
  children: [
780
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
781
- className: "fm-plugin-field",
782
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
783
- className: "fm-plugin-check",
784
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
785
- type: "checkbox",
786
- checked: prefs.navbar,
787
- onChange: (e) => prefsStore.update({ navbar: e.target.checked })
788
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
789
- className: "fm-plugin-check-label",
790
- children: t("settings.navbar")
791
- })]
792
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
793
- className: "fm-plugin-field-hint",
794
- children: t("settings.navbar.hint")
795
- })]
929
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
930
+ className: "fm-onboard-title",
931
+ children: t("onboarding.title")
796
932
  }),
797
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
798
- className: "fm-plugin-field",
799
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
800
- className: "fm-plugin-check",
801
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
802
- type: "checkbox",
803
- checked: prefs.scroll === "preserve",
804
- onChange: (e) => prefsStore.update({ scroll: e.target.checked ? "preserve" : "bottom" })
805
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
806
- className: "fm-plugin-check-label",
807
- children: t("settings.scrollPreserve")
808
- })]
809
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
810
- className: "fm-plugin-field-hint",
811
- children: t("settings.scrollPreserve.hint")
812
- })]
933
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
934
+ className: "fm-onboard-intro",
935
+ children: t("onboarding.intro")
813
936
  }),
814
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
815
- className: "fm-plugin-field",
816
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
817
- className: "fm-plugin-check",
818
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
819
- type: "checkbox",
820
- checked: prefs.autoFocus,
821
- onChange: (e) => prefsStore.update({ autoFocus: e.target.checked })
822
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
823
- className: "fm-plugin-check-label",
824
- children: t("settings.autoFocus")
825
- })]
826
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
827
- className: "fm-plugin-field-hint",
828
- children: t("settings.autoFocus.hint")
829
- })]
937
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
938
+ className: "fm-onboard-subtitle",
939
+ children: t("onboarding.features.title")
830
940
  }),
831
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
832
- className: "fm-plugin-field",
941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
942
+ className: "fm-onboard-features",
833
943
  children: [
834
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
835
- className: "fm-plugin-field-head",
836
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
837
- className: "fm-plugin-field-label",
838
- children: t("settings.width")
839
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
840
- className: "fm-plugin-field-value",
841
- children: [prefs.width, "px"]
842
- })]
843
- }),
844
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
845
- type: "range",
846
- className: "fm-plugin-range",
847
- min: 480,
848
- max: 1200,
849
- step: 40,
850
- value: prefs.width,
851
- onChange: (e) => prefsStore.update({ width: Number(e.target.value) })
852
- }),
853
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
854
- className: "fm-plugin-field-hint",
855
- children: t("settings.width.hint")
856
- })
857
- ]
944
+ "fullscreen",
945
+ "fold",
946
+ "navbar",
947
+ "autoFocus"
948
+ ].map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("onboarding.feature." + f) }, f))
949
+ }),
950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
951
+ className: "fm-onboard-subtitle",
952
+ children: t("onboarding.configure.title")
953
+ }),
954
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusPrefsFields, { t }),
955
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
956
+ className: "fm-onboard-note",
957
+ children: t("onboarding.laterNote")
958
+ }),
959
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
960
+ className: "fm-onboard-actions",
961
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
962
+ variant: "outline",
963
+ size: "sm",
964
+ onClick: goSettings,
965
+ children: t("onboarding.openSettings")
966
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
967
+ variant: "primary",
968
+ size: "sm",
969
+ onClick: dismiss,
970
+ children: t("onboarding.done")
971
+ })]
858
972
  })
859
973
  ]
860
- }) : null]
974
+ })
861
975
  });
862
976
  }
863
977
  //#endregion
@@ -915,6 +1029,26 @@ window.__ModuleLoader__.load({
915
1029
  .fm-plugin-check input[type="checkbox"]{width:16px;height:16px;margin:0;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#4176e6)}
916
1030
  .fm-plugin-check-label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}
917
1031
  .fm-plugin-range{width:100%;max-width:280px;cursor:pointer;accent-color:var(--dsw-alias-brand-primary,#4176e6)}
1032
+ /* DSH-better-sidebar compatibility: its panel host (top-right toggle cluster +
1033
+ right/bottom panels) is appended to document.body at z-index 40/45, above
1034
+ shell.overlay's z-20 layer, so this overlay cannot cover it. Hide the host
1035
+ while focus is on, and neutralize the #root layout push it applies when its
1036
+ panels are open. Selectors mirror better-sidebar's own layout.css exactly. */
1037
+ body[data-fm-focus] [data-dsh-panel-host]{display:none!important}
1038
+ body[data-fm-focus] #root{margin-right:0!important;width:100%!important}
1039
+ body[data-fm-focus] #root [data-dsh-frame] > [data-pane="conversation"],
1040
+ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!important}
1041
+ /* First-run onboarding (settings.onboarding step) — content inside the official
1042
+ Modal primitive; the primitive owns the mask/dialog chrome, these style our body. */
1043
+ .fm-onboard{width:min(600px,100%);padding:0}
1044
+ .fm-onboard-content{box-sizing:border-box;flex-direction:column;max-height:calc(100vh - 48px);padding:28px;display:flex;overflow-y:auto}
1045
+ .fm-onboard-title{color:var(--dsw-alias-label-primary);outline:none;margin:0;font-size:20px;font-weight:500;line-height:28px}
1046
+ .fm-onboard-intro{color:var(--dsw-alias-label-secondary);margin:8px 0 0;font-size:14px;line-height:22px}
1047
+ .fm-onboard-subtitle{color:var(--dsw-alias-label-primary);margin:20px 0 8px;font-size:14px;font-weight:600;line-height:22px}
1048
+ .fm-onboard-features{margin:0;padding:0 0 0 18px;list-style:disc;display:flex;flex-direction:column;gap:6px;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
1049
+ .fm-onboard-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:18px}
1050
+ .fm-onboard-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}
1051
+ @media (width<=560px){.fm-onboard-content{padding:24px}}
918
1052
  `;
919
1053
  //#endregion
920
1054
  //#region src/client/locales.ts
@@ -928,14 +1062,14 @@ window.__ModuleLoader__.load({
928
1062
  "settings.description": "全屏阅读遮罩的偏好设置",
929
1063
  "settings.expand": "展开",
930
1064
  "settings.collapse": "收起",
931
- "settings.navbar": "显示右侧导航条",
932
- "settings.navbar.hint": "在右侧显示用户消息导航圆点",
933
- "settings.scrollPreserve": "打开时保留聊天位置",
934
- "settings.scrollPreserve.hint": "打开时定位到当前阅读位置;关闭则定位到最新",
935
- "settings.autoFocus": "回复结束后自动进入专注模式",
936
- "settings.autoFocus.hint": "回复正常完成后自动打开;AI 提问 / 审批等待时弹出提醒",
1065
+ "settings.navbar": "显示右侧轮次导航条",
1066
+ "settings.navbar.hint": "在右侧显示导航圆点,每个用户消息(一轮)对应一个,悬停预览、点击跳转",
1067
+ "settings.scrollPreserve": "进入专注模式时,同步当前阅读位置",
1068
+ "settings.scrollPreserve.hint": "进入时定位到你正在阅读的消息;关闭则定位到最新",
1069
+ "settings.autoFocus": "AI完成回复后,自动进入专注模式",
1070
+ "settings.autoFocus.hint": "AI 正常完成回复后自动进入并定位到你的提问",
937
1071
  "settings.width": "文字区宽度",
938
- "settings.width.hint": "专注模式阅读列宽度(480–1200px)",
1072
+ "settings.width.hint": "专注模式正文的阅读列宽(480–1200px)",
939
1073
  "empty": "暂无打开的会话",
940
1074
  "loading": "正在读取会话…",
941
1075
  "running": "· 正在工作 …",
@@ -957,7 +1091,18 @@ window.__ModuleLoader__.load({
957
1091
  "sum.questions": "问了 {n} 个问题",
958
1092
  "sum.plans": "计划了 {n} 次",
959
1093
  "sum.jobs": "后台任务 {n} 个",
960
- "sum.others": "调用了 {n} 个工具"
1094
+ "sum.others": "调用了 {n} 个工具",
1095
+ "onboarding.title": "欢迎使用专注模式",
1096
+ "onboarding.intro": "dsh-focus-overlay 为 DeepSeek Harness Web 提供全屏专注阅读。",
1097
+ "onboarding.features.title": "功能",
1098
+ "onboarding.feature.fullscreen": "全屏阅读 —— 隐藏标题栏、输入区与侧栏,纵向空间尽给对话",
1099
+ "onboarding.feature.fold": "折叠工具调用 —— 把 AI 的命令、编辑、搜索等步骤折叠成一行摘要",
1100
+ "onboarding.feature.navbar": "右侧导航条 —— 按用户消息快速跳转,悬停预览",
1101
+ "onboarding.feature.autoFocus": "自动专注与提醒 —— 回复完成自动进入专注,AI 提问 / 等待时提醒",
1102
+ "onboarding.configure.title": "快速配置",
1103
+ "onboarding.laterNote": "这些设置随时可在「设置 → 插件 → 插件配置」中更改。",
1104
+ "onboarding.openSettings": "打开设置",
1105
+ "onboarding.done": "完成"
961
1106
  };
962
1107
  const en = {
963
1108
  "toggle": "Focus",
@@ -968,12 +1113,12 @@ window.__ModuleLoader__.load({
968
1113
  "settings.description": "Preferences for the full-screen reading overlay",
969
1114
  "settings.expand": "Expand",
970
1115
  "settings.collapse": "Collapse",
971
- "settings.navbar": "Show right-side navbar",
972
- "settings.navbar.hint": "Show navigation dots for user messages on the right",
973
- "settings.scrollPreserve": "Preserve chat position on open",
974
- "settings.scrollPreserve.hint": "Open at your current reading position; off opens at the latest",
975
- "settings.autoFocus": "Auto-enter focus mode when a reply completes",
976
- "settings.autoFocus.hint": "Auto-open after a normal completion; toast while AI waits for you",
1116
+ "settings.navbar": "Show right-side turn navbar",
1117
+ "settings.navbar.hint": "Show one navigation dot per user message (turn) hover to preview, click to jump",
1118
+ "settings.scrollPreserve": "Sync the current reading position when entering focus mode",
1119
+ "settings.scrollPreserve.hint": "Open at the message you're reading; off opens at the latest",
1120
+ "settings.autoFocus": "Auto-enter focus mode when the AI finishes a reply",
1121
+ "settings.autoFocus.hint": "Auto-open at your question after a normal reply",
977
1122
  "settings.width": "Text width",
978
1123
  "settings.width.hint": "Reading column width (480–1200px)",
979
1124
  "empty": "No open session",
@@ -997,7 +1142,18 @@ window.__ModuleLoader__.load({
997
1142
  "sum.questions": "asked {n} questions",
998
1143
  "sum.plans": "planned {n} times",
999
1144
  "sum.jobs": "{n} background jobs",
1000
- "sum.others": "called {n} tools"
1145
+ "sum.others": "called {n} tools",
1146
+ "onboarding.title": "Welcome to Focus Mode",
1147
+ "onboarding.intro": "dsh-focus-overlay adds full-screen reading to the DeepSeek Harness web GUI.",
1148
+ "onboarding.features.title": "Features",
1149
+ "onboarding.feature.fullscreen": "Full-screen reading — hides the header, composer and sidebars",
1150
+ "onboarding.feature.fold": "Folded tool calls — collapses commands, edits and searches into one summary line",
1151
+ "onboarding.feature.navbar": "Right-side navbar — jump between user messages with hover previews",
1152
+ "onboarding.feature.autoFocus": "Auto-focus & reminders — auto-enter after a reply, notify while the AI waits",
1153
+ "onboarding.configure.title": "Quick setup",
1154
+ "onboarding.laterNote": "You can change these anytime in Settings → Plugins → Plugin configuration.",
1155
+ "onboarding.openSettings": "Open settings",
1156
+ "onboarding.done": "Done"
1001
1157
  };
1002
1158
  //#endregion
1003
1159
  //#region src/client/index.ts
@@ -1029,6 +1185,18 @@ window.__ModuleLoader__.load({
1029
1185
  style.remove();
1030
1186
  };
1031
1187
  });
1188
+ ctx.effect(() => {
1189
+ const sync = () => {
1190
+ if (focusStore.get()) document.body.setAttribute("data-fm-focus", "");
1191
+ else document.body.removeAttribute("data-fm-focus");
1192
+ };
1193
+ sync();
1194
+ const unsub = focusStore.subscribe(sync);
1195
+ return () => {
1196
+ unsub();
1197
+ document.body.removeAttribute("data-fm-focus");
1198
+ };
1199
+ });
1032
1200
  ctx.effect(() => ctx.locale.register(NS, {
1033
1201
  zh,
1034
1202
  en
@@ -1115,6 +1283,14 @@ window.__ModuleLoader__.load({
1115
1283
  id: "dsh-focus-overlay",
1116
1284
  order: 1e3
1117
1285
  }, () => (0, react.createElement)(FocusSettingsCard, { t })));
1286
+ ctx.slots.inject("settings.onboarding", () => ctx.slots.register({
1287
+ name: "settings.onboarding",
1288
+ id: "dsh-focus-overlay",
1289
+ order: 10
1290
+ }, (props) => (0, react.createElement)(FocusOnboarding, {
1291
+ ...props,
1292
+ t
1293
+ })));
1118
1294
  }
1119
1295
  };
1120
1296
  return module.exports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-focus-overlay",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",