dsh-notify-me 1.10.4 → 1.10.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to **dsh-notify-me** are documented here.
4
4
 
5
+ ## [1.10.6] — 2026-10-21
6
+
7
+ ### 修复
8
+
9
+ - **提醒方式选「页面浮层」时,窗口在后台就等于没有提醒**:浮层卡片画在页面里,窗口被别的应用挡住(或最小化)时它既看不见、又会在 12 秒后自己收起,于是只剩下提示音,屏幕上什么都没有。现在浮层通道在页面离开前台时会额外发一条系统通知——那是唯一能追到「人在别处」的手段;窗口回到前台则不加,屏幕上还是一张卡片,不重复。系统通知仍受通知权限约束,未授权时保持原样(只有卡片)。
10
+
11
+ ### 新增
12
+
13
+ - 设置页「通知类型」下方新增「窗口在后台时补发系统通知」开关(默认开),关掉后浮层严格留在页面内。
14
+
15
+ ### 测试
16
+
17
+ - 冒烟新增断言:浮层通道在页面隐藏时发出系统通知且仍画卡片;开关关闭后不再发;页面可见时行为不变。
18
+
19
+ ## [1.10.5] — 2026-10-21
20
+
21
+ ### 修复
22
+
23
+ - **桌面端窗口被遮住时「回复完成」不提醒**:本插件判断「你离开了」只看 `document.hidden` / `visibilityState`,而桌面壳里把 DSH 窗口切到别的应用后面、或被别的窗口盖住时,页面依然是 `visible` —— 只有最小化或整窗被遮挡才会变成 `hidden`。于是「只在后台提醒」的回复完成提醒在窗口只是被盖住时被静默丢弃(1.10.5 之前只在真正最小化时才响)。现在把窗口焦点也算进「离开」:页面失焦即视为后台,失焦瞬间同样补发此前因为「就在你眼前」而被压下的审批提醒;窗口回到前台后照常安静。设置页三个相关开关的文案同步改为「窗口在前台时也提醒」,说明里点明「切走 / 被遮挡 / 最小化」都算后台。
24
+
25
+ ### 测试
26
+
27
+ - 冒烟新增 `awayWithoutHiding` 用例:焦点在时回复完成静默、失焦后同一页发出提醒、回焦再次静默、`visibilityState` 全程保持 `visible`、失焦时补发被压下的审批提醒。
28
+
5
29
  ## [1.10.4] — 2026-10-21
6
30
 
7
31
  ### 新增
package/README.en.md CHANGED
@@ -75,7 +75,7 @@ The Electron application manages the `desktop` profile. Do not change the Web co
75
75
 
76
76
  1. Click once in the DSH page and allow notifications when prompted. If you previously declined, allow them in the browser's site settings. The operating system must also allow notifications from the browser / DSH Desktop.
77
77
  2. Open **Settings → Notify me**. Confirm that the section and plugin version at its foot appear, and leave the master switch, the alert type on system notifications, and sound enabled.
78
- 3. Click **Test "reply finished"** on the settings page. A test notification should appear and sound according to your cue settings. This local test does not forward to external endpoints and ignores the "while the page is open" toggle.
78
+ 3. Click **Test "reply finished"** on the settings page. A test notification should appear and sound according to your cue settings. This local test does not forward to external endpoints and ignores the "while the window is in front" toggle.
79
79
 
80
80
  Web users can also test approval buttons. On Desktop the shell cannot register a Service Worker, so this test draws an in-page card with Approve / Reject in the window corner (since 1.10.4; earlier versions sent a plain notification).
81
81
 
@@ -110,7 +110,7 @@ dsh-notify-me decouples **supervision** from **sitting in front of the screen**:
110
110
 
111
111
  **Input needed** covers approval requests, plan review, and questions (`ask_user_question`). Approvals carry Approve / Reject: on Web as a system notification with buttons, on Desktop (which cannot register a Service Worker) as a card drawn in the window corner.
112
112
 
113
- **Reply finished** also covers background sessions. A finished but unread session keeps the `✅ Reply finished ·` title marker until opened. Title markers are independent of the "while the page is open" toggles.
113
+ **Reply finished** also covers background sessions. A finished but unread session keeps the `✅ Reply finished ·` title marker until opened. Title markers are independent of the "while the window is in front" toggles.
114
114
 
115
115
  > **Background** subagent child sessions are **not** alerted about by default: DSH lists each of them as its own session, but they are steps inside the parent conversation's turn, so an alert for them is only noise. The subagent session you have open is not part of this and alerts as usual. Settings → "Ignore subagent sessions" turns the mute off.
116
116
 
@@ -138,14 +138,15 @@ The **in-page overlay** skips the notification center and draws the card inside
138
138
  - **Approval cards carry Approve / Reject** — the same decision as Allow once / Reject on the approval card, and they work on the Desktop too (no Service Worker involved). Clicking the card body opens its conversation;
139
139
  - **Lifetime** — "needs you" and "reply finished" cards retire after 5s each, an approval card with buttons after 12s (system toasts use the same two numbers); a card or its close button retires one earlier. At most 4 cards stack at once;
140
140
  - **Relation to system notifications** — it is one or the other. Both used to be switchable at once, which meant every alert arrived twice and the pair covered each other; 1.10.0 made it a single choice, and 1.10.2 turns that into one segmented row. An old config with the overlay on migrates to it.
141
+ - **System notification while the window is in the background** (on by default, since 1.10.6) — the card is drawn inside the page, so a window behind another app or minimized shows nothing and leaves only the sound. With this on, an alert arriving while the page is away is also raised as a system notification; back in front, the card is the only copy again. Turn it off to keep the overlay strictly in-page.
141
142
 
142
143
  The overlay only knows which screen the window is on, not which screen you are looking at. If the window stays on the primary display while you watch another one, it stays invisible — same as a toast.
143
144
 
144
145
  ### When to alert
145
146
 
146
- - **Also alert "needs you" while the page is open** (on): turn it off to hear only from the background;
147
- - **Stay quiet for the conversation on screen** (on): a wait belonging to the conversation you are looking at keeps only the tab marker so the approval card stays readable; backgrounding restores alerts;
148
- - **Also alert "reply finished" while the page is open** (off): by default only background finishes alert;
147
+ - **Also alert "needs you" while the window is in front** (on): turn it off to hear only once the window goes to the background — another app focused, the window covered, or minimized all count;
148
+ - **Stay quiet for the conversation on screen** (on): a wait belonging to the conversation you are looking at keeps only the tab marker so the approval card stays readable; moving the window to the background restores alerts;
149
+ - **Also alert "reply finished" while the window is in front** (off): by default only background finishes alert (a covered Desktop window counts as background since 1.10.5, not only a minimized one);
149
150
  - **Ignore subagent sessions** (on): skip background sessions spawned by a subagent — the parent conversation covers them; the one you have open still alerts;
150
151
  - **Click a toast to open that conversation** (on): the click switches to the conversation that raised it; in a browser it also brings the window forward, while on the Desktop shell it is flaky — sometimes the window comes forward, sometimes only the conversation switches (see [How the Desktop window gets raised](#how-the-desktop-window-gets-raised)); with several tabs open only one is raised, and off means a click does nothing.
151
152
 
@@ -261,6 +262,7 @@ window.__dshNotifyMe.setConfig({
261
262
  quickActions: true, // Approve / Reject buttons on approval toasts
262
263
  overlay: false, // legacy mirror kept in sync with channel (old configs migrate through it)
263
264
  overlayCorner: "bottom-right", // overlay anchor: 'top-right' | 'bottom-right' | 'top-left' | 'bottom-left'
265
+ overlayAwayToast: true, // overlay channel: also raise a system notification while the page is away (the only cross-channel exception)
264
266
  externalEnabled: false, // external-notification forwarding master switch (default off: no endpoint, nothing is sent)
265
267
  externalAttention: true, // forward "needs you" (exactly the same trigger rules as local alerts)
266
268
  externalDone: true, // forward "reply finished"
package/README.md CHANGED
@@ -75,7 +75,7 @@ dsh plugin --profile web add dsh-notify-me
75
75
 
76
76
  1. 在 DSH 页面上点击一次,按提示允许通知;若此前拒绝过,在浏览器站点设置中允许通知。操作系统也需要允许对应浏览器 / DSH Desktop 显示通知。
77
77
  2. 打开 **设置 → 通知提醒**,确认出现该分区及底部插件版本号,保持「启用提醒」「提示音」开启,通知类型先留「系统通知」。
78
- 3. 点击设置页的「测试『回复完成』」按钮:应出现一条测试通知,并按当前音效设置播放声音。这个本地测试不会转发到外部通知端点,也不受「页面打开时也提醒」开关限制。
78
+ 3. 点击设置页的「测试『回复完成』」按钮:应出现一条测试通知,并按当前音效设置播放声音。这个本地测试不会转发到外部通知端点,也不受「窗口在前台时也提醒」开关限制。
79
79
 
80
80
  Web 用户可再测试审批按钮。桌面端拿不到 Service Worker,这条测试会在窗口角落画一张带「同意 / 拒绝」的卡片(1.10.4 起;之前的版本这里只发普通通知)。
81
81
 
@@ -106,7 +106,7 @@ dsh-notify-me 把「监督」和「守在屏幕前」解耦:只在真的出现
106
106
 
107
107
  **需要你操作**包括审批请求、方案待确认(plan review)和提问(`ask_user_question`)。审批带「同意 / 拒绝」:Web 走带按钮的系统通知,桌面端(注册不了 Service Worker)改画在窗口角落的卡片上。
108
108
 
109
- **回复完成**也覆盖后台会话;完成但还没看时,标题带 `✅ 回复完成 ·` 标记,打开对应会话即撤。标题标记不受「页面打开时也提醒」开关限制。
109
+ **回复完成**也覆盖后台会话;完成但还没看时,标题带 `✅ 回复完成 ·` 标记,打开对应会话即撤。标题标记不受「窗口在前台时也提醒」开关限制。
110
110
 
111
111
  > **后台**子代理(subagent)子会话的等待与完成默认**不提醒**:DSH 把每个子代理子会话列成独立会话,但它们是所属主对话那一轮里的步骤,提醒只会是噪音。你当前打开的那个子代理会话不算在内,照常提醒。设置页「子代理会话不提醒」可关掉这个静音。
112
112
 
@@ -134,14 +134,15 @@ Windows 的系统通知横幅固定画在主屏上,系统没有设置项、浏
134
134
  - **审批卡片带「同意 / 拒绝」**:与审批卡片的「允许一次 / 拒绝」等价,桌面端也能用(不依赖 Service Worker);点卡片正文则切回对应对话;
135
135
  - **停留时间**:普通提醒 5 秒自动收起,带「同意 / 拒绝」的审批卡片 12 秒(系统通知用同一套数字);也可以点卡片或右上角叉提前关掉。最多同时叠 4 张;
136
136
  - **与系统通知的关系**:二选一。以前两个开关可以同开,结果同一条提醒弹两遍、还互相挡住;1.10.0 起改成单选,1.10.2 起做成一行分段选择器。老配置里开着浮层的,升级后自动落到浮层。
137
+ - **窗口在后台时补发系统通知**(默认开,1.10.6 起):浮层画在页面里,窗口被别的应用挡住或最小化时看不见,等于只剩声音;开着这一项,页面离开前台时额外发一条系统通知,窗口回到前台还是只有卡片。不需要这份兜底就关掉它。
137
138
 
138
139
  浮层只知道窗口在哪块屏,不知道你人在看哪块屏。窗口留在主屏、人却盯着副屏时,它同样看不见。
139
140
 
140
141
  ### 什么时候提醒
141
142
 
142
- - **页面打开时也提醒「需要你」**(默认开):页面在最前也提醒;关掉后只有切到后台才提醒;
143
- - **当前对话不弹通知**(默认开):等待就发生在你正看着的对话里时只留标签页标记,免得挡住审批卡片;切到后台后照常提醒,若你已处理掉就作废;
144
- - **页面打开时也提醒「回复完成」**(默认关):默认只在切到后台时提醒,避免打扰;
143
+ - **窗口在前台时也提醒「需要你」**(默认开):窗口在前台也提醒;关掉后只有窗口转到后台才提醒——切到别的应用、被别的窗口遮住、最小化都算后台;
144
+ - **当前对话不弹通知**(默认开):等待就发生在你正看着的对话里时只留标签页标记,免得挡住审批卡片;窗口转到后台后照常提醒,若你已处理掉就作废;
145
+ - **窗口在前台时也提醒「回复完成」**(默认关):默认只在窗口转到后台时提醒,避免打扰(桌面端窗口被遮住也算后台,1.10.5 起不再要求最小化);
145
146
  - **子代理会话不提醒**(默认开):跳过后台子代理(subagent)会话——它们是主对话那一轮的步骤,完成由主对话自己的提醒覆盖;你当前打开的那个子代理会话照常提醒;
146
147
  - **点击通知回到对应对话**(默认开):点通知切到提醒所属的对话;浏览器里同时把窗口带到前台;桌面端不稳定——有时窗口会上前,有时只切对话(见[桌面端怎么抬窗的](#桌面端怎么抬窗的));多个标签页同时开着时只调起一个(优先当前可见的那个);关掉后点通知不做任何事。
147
148
 
@@ -257,6 +258,7 @@ window.__dshNotifyMe.setConfig({
257
258
  quickActions: true, // 审批通知上的「同意 / 拒绝」按钮
258
259
  overlay: false, // 兼容字段,跟随 channel 同步(老配置靠它迁移)
259
260
  overlayCorner: "bottom-right", // 浮层位置:'top-right' | 'bottom-right' | 'top-left' | 'bottom-left'
261
+ overlayAwayToast: true, // 浮层通道下,页面在后台时额外发一条系统通知(唯一跨通道例外)
260
262
  externalEnabled: false, // 外部通知转发主开关(默认关:不填端点就什么都不发)
261
263
  externalAttention: true, // 转发「需要你」(触发规则跟本地提醒完全一致)
262
264
  externalDone: true, // 转发「回复完成」
package/lib/client.js CHANGED
@@ -270,6 +270,12 @@
270
270
  // config written by an older build, or a console poke at one of them,
271
271
  // still means what it used to.
272
272
  overlay: false,
273
+ // The overlay draws inside the page, so a window sitting behind another
274
+ // app shows nothing at all. An alert that lands while the page is away
275
+ // therefore also raises a system notification — the only thing that
276
+ // reaches a user who is elsewhere. Off keeps the overlay strictly
277
+ // in-page, at the price of silence while the window is behind.
278
+ overlayAwayToast: true,
273
279
  // Which corner of this window the overlay cards stack in. The screen
274
280
  // follows the window — the page cannot aim it anywhere else either.
275
281
  overlayCorner: "bottom-right", // bottom-right | bottom-left | top-right | top-left
@@ -473,6 +479,17 @@
473
479
  return "Notification" in window && Notification.permission === "granted";
474
480
  } catch (e) { return false; } // a policy-disabled Notification must not break delivery
475
481
  }
482
+ // The overlay is the chosen channel, but a card inside a window the user is
483
+ // not looking at cannot be seen; the system notification is the only thing
484
+ // that reaches them. Permission still decides — without it the in-page card
485
+ // is all there is, waiting for them when they come back.
486
+ function awayToastAllowed() {
487
+ if (config.channel !== "overlay" || !config.overlayAwayToast) return false;
488
+ if (!away()) return false;
489
+ try {
490
+ return "Notification" in window && Notification.permission === "granted";
491
+ } catch (e) { return false; }
492
+ }
476
493
  // True on the packaged desktop shell, where the app page lives under
477
494
  // dsh-app://app inside Electron. The desktop needs its own raise path
478
495
  // (raiseWindow below) and cannot carry quick-decision buttons at all.
@@ -758,8 +775,8 @@
758
775
  return "dsh-notify-me-" + kind + "-" + toastSeq + "-" + Date.now();
759
776
  }
760
777
 
761
- function showToast(kind, title, body, sessionId, quick) {
762
- if (!canToast()) return;
778
+ function showToast(kind, title, body, sessionId, quick, forced) {
779
+ if (!forced && !canToast()) return;
763
780
  try {
764
781
  var tag = quick ? quick.tag : uniqueToastTag(kind);
765
782
  if (quick && bridgeActive()) {
@@ -1392,6 +1409,25 @@
1392
1409
 
1393
1410
  function hidden() { return document.hidden || document.visibilityState === "hidden"; }
1394
1411
 
1412
+ // "Background" is not the same as "hidden": a desktop window sitting behind
1413
+ // another app, or a browser tab on a second monitor, still reports itself as
1414
+ // visible. Only a minimised or fully occluded window flips document.hidden,
1415
+ // so the reminders that are meant to reach a user who walked away stayed
1416
+ // silent whenever the window was merely covered. Window focus is the missing
1417
+ // half of the signal: an unfocused page counts as away.
1418
+ var pageFocused = true;
1419
+ try {
1420
+ if (typeof document.hasFocus === "function") pageFocused = document.hasFocus() !== false;
1421
+ } catch (e) {}
1422
+ function onPageFocus() { pageFocused = true; }
1423
+ function onPageBlur() {
1424
+ pageFocused = false;
1425
+ // Walking away is what re-arms the waits kept quiet because they belonged
1426
+ // to the conversation on screen; a blur alone never fires visibilitychange.
1427
+ try { flushQuieted(); } catch (e) {}
1428
+ }
1429
+ function away() { return hidden() || !pageFocused; }
1430
+
1395
1431
  // Deliver one alert regardless of visibility rules (used by the test
1396
1432
  // buttons); respects the master switch and permission state. The title
1397
1433
  // marker is NOT touched here — its ownership belongs to the watchers
@@ -1405,7 +1441,12 @@
1405
1441
  }
1406
1442
  function deliver(kind, title, body, sessionId, quick) {
1407
1443
  if (!config.enabled) return;
1408
- showToast(kind, title, body, sessionId, quick);
1444
+ // One visible path per alert: the toast channel owns system notifications,
1445
+ // the overlay channel owns the in-page card. The single exception is the
1446
+ // overlay channel while the page is away — that card is unseeable then,
1447
+ // so the alert also goes out as a system notification (see
1448
+ // awayToastAllowed).
1449
+ showToast(kind, title, body, sessionId, quick, awayToastAllowed());
1409
1450
  showOverlay(kind, title, body, sessionId, quick);
1410
1451
  showFallbackApprovalCard(kind, title, body, sessionId, quick);
1411
1452
  if (kind === "attention") {
@@ -1417,13 +1458,13 @@
1417
1458
  }
1418
1459
  function fire(kind, title, body, isCurrent, sessionId, quick) {
1419
1460
  if (!config.enabled) return false;
1420
- if (kind === "attention" && config.attentionHiddenOnly && !hidden()) return false;
1461
+ if (kind === "attention" && config.attentionHiddenOnly && !away()) return false;
1421
1462
  // The wait belongs to the conversation the user is looking at right now:
1422
1463
  // the approval card is already on screen, so a toast would only cover the
1423
1464
  // thing it is asking about. The caller keeps the tab marker, and
1424
1465
  // flushQuieted() re-delivers the alert the moment the page is backgrounded.
1425
1466
  if (kind === "attention" && quietedByVisibleSession(isCurrent)) return false;
1426
- if (kind === "done" && config.doneHiddenOnly && !hidden()) return false;
1467
+ if (kind === "done" && config.doneHiddenOnly && !away()) return false;
1427
1468
  deliver(kind, title, body, sessionId, quick);
1428
1469
  // The alert reached the user locally (or would have, had the tab a
1429
1470
  // voice): mirror the same copy to the outside world. Fire-and-forget —
@@ -1435,7 +1476,7 @@
1435
1476
  // session already on screen. Kept separate from fire() so the caller can
1436
1477
  // queue exactly this case and nothing else.
1437
1478
  function quietedByVisibleSession(isCurrent) {
1438
- return !!(isCurrent && config.currentHiddenOnly && !hidden());
1479
+ return !!(isCurrent && config.currentHiddenOnly && !away());
1439
1480
  }
1440
1481
  // Queued copies of the alerts that rule suppressed. Delivered by key so a
1441
1482
  // wait that got answered while the user was still on the page never fires.
@@ -1443,7 +1484,7 @@
1443
1484
  if (key && copy) quietedAttention[key] = copy;
1444
1485
  }
1445
1486
  function flushQuieted() {
1446
- if (!config.enabled || !hidden()) return;
1487
+ if (!config.enabled || !away()) return;
1447
1488
  var keys = Object.keys(quietedAttention);
1448
1489
  for (var i = 0; i < keys.length; i++) {
1449
1490
  var copy = quietedAttention[keys[i]];
@@ -2047,7 +2088,7 @@
2047
2088
  });
2048
2089
  } catch (e) {}
2049
2090
  // keep in sync with package.json "version" on every release
2050
- api.version = "1.10.4";
2091
+ api.version = "1.10.6";
2051
2092
  api.setConfig = function (patch) { return setConfigLocal(patch); };
2052
2093
  api.resetConfig = function () {
2053
2094
  try {
@@ -2149,6 +2190,8 @@
2149
2190
  actionKeys: Object.keys(pendingActions),
2150
2191
  permission: ("Notification" in window) ? Notification.permission : "n/a",
2151
2192
  visibility: hidden() ? "hidden" : "visible",
2193
+ focused: pageFocused,
2194
+ away: away(),
2152
2195
  uiSession: uiPending ? "bound" : "unbound",
2153
2196
  uiSessionNote: uiBindNote,
2154
2197
  uiSessionSource: uiSource,
@@ -2249,12 +2292,12 @@
2249
2292
  externalCountFailed: "失败 ",
2250
2293
  externalNoTargets: "先添加至少一个推送目标。",
2251
2294
  externalOffHint: "先打开「转发外部通知」。",
2252
- attentionVisible: "页面打开时也提醒「需要你」",
2253
- attentionVisibleHint: "页面在最前也提醒。关掉后只有切到后台才提醒。",
2295
+ attentionVisible: "窗口在前台时也提醒「需要你」",
2296
+ attentionVisibleHint: "窗口在前台也提醒。关掉后只在窗口转到后台(切走、被遮挡或最小化)时提醒。",
2254
2297
  currentQuiet: "当前对话不弹通知",
2255
- currentQuietHint: "等待就发生在你正看着的对话里时只留标签页标记,免得挡住审批卡片;切到后台后照常提醒。",
2256
- doneVisible: "页面打开时也提醒「回复完成」",
2257
- doneVisibleHint: "页面在最前也提醒。默认只在切到后台时提醒,避免打扰。",
2298
+ currentQuietHint: "等待就发生在你正看着的对话里时只留标签页标记,免得挡住审批卡片;窗口转到后台后照常提醒。",
2299
+ doneVisible: "窗口在前台时也提醒「回复完成」",
2300
+ doneVisibleHint: "窗口在前台也提醒。默认只在窗口转到后台时提醒,避免打扰。",
2258
2301
  subagentQuiet: "子代理会话不提醒",
2259
2302
  subagentQuietHint: "跳过后台子代理会话(结果由所属主对话的提醒覆盖);你正打开的那个照常提醒。",
2260
2303
  autoFocus: "点击通知回到对应对话",
@@ -2266,7 +2309,7 @@
2266
2309
  bridgeOffTail: "):系统通知没有按钮,按钮改画在窗口角落的卡片上。",
2267
2310
  bridgeOffDesktop: "系统通知本身没有「同意 / 拒绝」按钮:桌面端页面(dsh-app://)注册不了 Service Worker(宿主少给 allowServiceWorkers 特权)。审批类提醒会同时在窗口角落弹一张卡片,按钮就在卡片上;想只留卡片、不弹系统通知,把「提醒方式」切到「页面浮层」。",
2268
2311
  overlay: "页面浮层提醒",
2269
- overlayHint: "在窗口角落画提醒卡片:窗口在哪块屏,它就在哪块屏;系统通知只会出现在主屏,主屏不看时用它。不需要通知权限,卡片上能直接「同意 / 拒绝」。与系统通知二选一。",
2312
+ overlayHint: "在窗口角落画提醒卡片:窗口在哪块屏,它就在哪块屏;系统通知只会出现在主屏,主屏不看时用它。不需要通知权限,卡片上能直接「同意 / 拒绝」。与系统通知二选一;唯一例外是窗口不在前台时,会补发一条系统通知(下一行可关掉)。",
2270
2313
  channelLabel: "通知类型",
2271
2314
  overlaySeg: "浮层提醒",
2272
2315
  channelHint: "系统通知只会出现在主屏,进通知中心要权限,桌面端的通知本身没有「同意 / 拒绝」按钮(审批会额外在窗口角落弹一张可点击的卡片);页面浮层画在 DSH 窗口所在的那块屏,不需要权限,卡片上能直接裁决。两个只能选一个。",
@@ -2277,6 +2320,8 @@
2277
2320
  overlayPosTL: "左上",
2278
2321
  overlayPosBL: "左下",
2279
2322
  overlayQuickNote: "浮层上的「同意」同样等价于审批卡片上的「允许一次」,与通知按钮一致。",
2323
+ overlayAwayToast: "窗口在后台时补发系统通知",
2324
+ overlayAwayToastHint: "浮层画在页面里,窗口被别的应用挡住时看不见,等于没有提醒。打开后,窗口不在前台时仍会补发一条系统通知(需要通知权限)。",
2280
2325
  testApprovalNoBridge: "已发测试提醒:系统通知没有按钮,快捷裁决按钮画在窗口角落的卡片上。",
2281
2326
  testNeedsQuick: "先打开上面的「审批通知上直接裁决」才能测按钮。",
2282
2327
  volume: "音量",
@@ -2379,12 +2424,12 @@
2379
2424
  externalCountFailed: "failed ",
2380
2425
  externalNoTargets: "Add at least one push target first.",
2381
2426
  externalOffHint: "Turn on \"Forward to external targets\" first.",
2382
- attentionVisible: "Also alert \"needs you\" while the page is open",
2383
- attentionVisibleHint: "Alert even while the page is in front. Turn off to hear only from the background.",
2427
+ attentionVisible: "Also alert \"needs you\" while the window is in front",
2428
+ attentionVisibleHint: "Alert even while the window has focus. Turn off to hear only once the window goes to the background (another app, covered, or minimised).",
2384
2429
  currentQuiet: "Stay quiet for the conversation on screen",
2385
- currentQuietHint: "A wait belonging to the conversation on screen keeps only the tab marker, so the approval card stays readable; backgrounding restores alerts.",
2386
- doneVisible: "Also alert \"reply finished\" while the page is open",
2387
- doneVisibleHint: "Alert even while the page is in front. By default only background finishes alert.",
2430
+ currentQuietHint: "A wait belonging to the conversation on screen keeps only the tab marker, so the approval card stays readable; moving the window to the background restores alerts.",
2431
+ doneVisible: "Also alert \"reply finished\" while the window is in front",
2432
+ doneVisibleHint: "Alert even while the window has focus. By default only finishes in the background alert.",
2388
2433
  subagentQuiet: "Ignore subagent sessions",
2389
2434
  subagentQuietHint: "Skip sessions spawned by a subagent (the parent conversation covers them); the one you have open still alerts.",
2390
2435
  autoFocus: "Click a toast to open that conversation",
@@ -2396,7 +2441,7 @@
2396
2441
  bridgeOffTail: "); system notifications carry no buttons, so they are drawn on a card in the window corner instead.",
2397
2442
  bridgeOffDesktop: "System notifications carry no Approve / Reject buttons: dsh-app:// pages cannot register a Service Worker (the shell does not grant the allowServiceWorkers privilege). An approval therefore also drops a clickable card in the window corner, buttons and all; switch the alert channel to the in-page overlay to keep only the card and skip the system notification.",
2398
2443
  overlay: "In-page overlay",
2399
- overlayHint: "Draw a card in the window corner, on whatever display the window sits on. System notifications only ever show on the primary display. No permission needed, and the card carries Approve / Reject directly. It replaces system notifications rather than adding to them.",
2444
+ overlayHint: "Draw a card in the window corner, on whatever display the window sits on. System notifications only ever show on the primary display. No permission needed, and the card carries Approve / Reject directly. It replaces system notifications rather than adding to them — except while the window is not in front, when one system notification is sent as well (switchable on the next row).",
2400
2445
  channelLabel: "Alert type",
2401
2446
  overlaySeg: "Overlay",
2402
2447
  channelHint: "System notifications land in the Windows notification center: permission needed, primary display only, and the Desktop shell's notifications carry no Approve / Reject buttons (an approval also drops a clickable card in the window corner). The in-page overlay draws on whichever display the DSH window sits on, needs no permission, and lets the Desktop shell decide right on the card. Pick one.",
@@ -2407,6 +2452,8 @@
2407
2452
  overlayPosTL: "Top left",
2408
2453
  overlayPosBL: "Bottom left",
2409
2454
  overlayQuickNote: "\"Approve\" here equals \"Allow once\" on the approval card — the same decision the toast buttons send.",
2455
+ overlayAwayToast: "Also send a system notification while the window is in the background",
2456
+ overlayAwayToastHint: "The card is drawn inside the page, so a window hidden behind another app shows nothing — as good as no alert. With this on, an alert that arrives while the window is not in front is also sent as a system notification (notification permission required).",
2410
2457
  testApprovalNoBridge: "Test alert sent: system notifications carry no buttons, so the quick-decision buttons are drawn on a card in the window corner.",
2411
2458
  testNeedsQuick: "Turn on \"Decide approvals from the toast\" above to test the buttons.",
2412
2459
  volume: "Volume",
@@ -3123,6 +3170,9 @@
3123
3170
  ])
3124
3171
  ])
3125
3172
  }) : null,
3173
+ cfg.channel === "overlay" ? rowSwitch("overlayAwayToast", t("overlayAwayToast"), t("overlayAwayToastHint"), cfg.overlayAwayToast, function (v) {
3174
+ patch({ overlayAwayToast: v });
3175
+ }) : null,
3126
3176
  cfg.sound ? discloseRow({
3127
3177
  key: "volume",
3128
3178
  labelText: t("sound"),
@@ -3381,6 +3431,13 @@
3381
3431
  var onVisibility = function () { try { flushQuieted(); } catch (e) {} };
3382
3432
  try { document.addEventListener("visibilitychange", onVisibility); } catch (e) {}
3383
3433
  unsubs.push(function () { try { document.removeEventListener("visibilitychange", onVisibility); } catch (e) {} });
3434
+ // Same re-arm for the other half of "went away": losing window focus.
3435
+ try { window.addEventListener("focus", onPageFocus); } catch (e) {}
3436
+ try { window.addEventListener("blur", onPageBlur); } catch (e) {}
3437
+ unsubs.push(function () {
3438
+ try { window.removeEventListener("focus", onPageFocus); } catch (e) {}
3439
+ try { window.removeEventListener("blur", onPageBlur); } catch (e) {}
3440
+ });
3384
3441
  // Quick-decision bridge: register the worker half of this bundle (a
3385
3442
  // no-op where quickActions is off or a worker cannot run) and start
3386
3443
  // listening for toast-button relays.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-notify-me",
3
- "version": "1.10.4",
3
+ "version": "1.10.6",
4
4
  "description": "离开 DSH 页面也能收到提醒:需要你操作 / 后台回复完成时发系统通知、标题标记与可选页面浮层(副屏也能看见),支持审批快捷裁决与外部推送,Web 版与桌面版均可用。",
5
5
  "license": "MIT",
6
6
  "type": "module",