dsh-later 1.1.0 → 1.3.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/lib/client.js CHANGED
@@ -4,9 +4,11 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  "use strict";
7
+ var __create = Object.create;
7
8
  var __defProp = Object.defineProperty;
8
9
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
10
  var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __getProtoOf = Object.getPrototypeOf;
10
12
  var __hasOwnProp = Object.prototype.hasOwnProperty;
11
13
  var __export = (target, all) => {
12
14
  for (var name2 in all)
@@ -20,14 +22,29 @@ var __copyProps = (to, from, except, desc) => {
20
22
  }
21
23
  return to;
22
24
  };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
23
33
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
34
 
25
35
  // src/client/index.tsx
26
36
  var index_exports = {};
27
37
  __export(index_exports, {
28
38
  apply: () => apply,
39
+ buildScheduleSummary: () => buildScheduleSummary,
40
+ createScheduleSummaryStore: () => createScheduleSummaryStore,
41
+ getScheduleSummaryStore: () => getScheduleSummaryStore,
29
42
  inject: () => inject,
30
- name: () => name
43
+ name: () => name,
44
+ schedulesOfProjection: () => schedulesOfProjection,
45
+ subscribeScheduleSummaryStore: () => subscribeScheduleSummaryStore,
46
+ summarizeScheduleGroup: () => summarizeScheduleGroup,
47
+ useScheduleSummary: () => useScheduleSummary
31
48
  });
32
49
  module.exports = __toCommonJS(index_exports);
33
50
 
@@ -71,25 +88,27 @@ var COMPONENTS = [
71
88
  " margin: 0 auto calc(0px - var(--dsh-composer-stack-gap) - 3px);",
72
89
  " padding: 0 var(--dsh-composer-dock-inset);",
73
90
  "}",
74
- "/* \u9762\u677F\u4E0E QueueDock .panel \u4E00\u81F4\uFF1Abg tip\u3001\u9876\u90E8\u5706\u89D2\u3001::after \u753B\u63CF\u8FB9\uFF08\u7559\u5E95\u8FB9\u5F00\u53E3\uFF09\u3002 */",
91
+ "/* \u9762\u677F\u4E0E QueueDock .panel \u4E00\u81F4\uFF1Abg menu + backdrop-filter\u3001\u9876\u90E8\u5706\u89D2\u3001::before \u753B\u63CF\u8FB9\uFF08\u7559\u5E95\u8FB9\u5F00\u53E3\uFF09\u3002 */",
75
92
  ".ss-dock-body {",
76
93
  " position: relative; overflow: hidden; width: 100%; padding: 2px 0;",
77
- " background: var(--dsw-specific-tip);",
78
- " border-radius: 12px 12px 0 0;",
94
+ " isolation: isolate;",
95
+ " border-radius: var(--dsw-radius-lg) var(--dsw-radius-lg) 0 0;",
79
96
  " --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);",
80
97
  " --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);",
81
98
  "}",
82
- ".ss-dock-body::after {",
83
- " content: ''; position: absolute; inset: 0; pointer-events: none;",
84
- " border: 1px solid var(--dsw-alias-border-l1); border-bottom: none; border-radius: inherit;",
99
+ ".ss-dock-body::before {",
100
+ " z-index: -1; border-radius: inherit; background: var(--dsw-specific-menu);",
101
+ " backdrop-filter: var(--dsw-menu-backdrop-filter); content: '';",
102
+ " position: absolute; inset: 0; pointer-events: none;",
103
+ " border: 1px solid var(--dsw-alias-border-l1); border-bottom: none;",
85
104
  "}",
86
105
  ".ss-dock-header {",
87
106
  " box-sizing: border-box; display: flex; align-items: center; gap: 10px;",
88
107
  " width: 100%; height: 36px; padding: 4px 12px;",
89
- " background: none; border: none; border-radius: 8px; cursor: pointer; text-align: left;",
108
+ " background: none; border: none; border-radius: var(--dsw-radius-md); cursor: pointer; text-align: left;",
90
109
  " color: var(--dsw-alias-label-primary);",
91
110
  "}",
92
- ".ss-dock-header:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }",
111
+ ".ss-dock-header:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: -2px; }",
93
112
  ".ss-dock-lead { display: grid; place-items: center; color: var(--dsw-alias-label-tertiary); flex: none; }",
94
113
  ".ss-dock-count {",
95
114
  " flex: auto; min-width: 0;",
@@ -182,13 +201,30 @@ var COMPONENTS = [
182
201
  " background-repeat: no-repeat;",
183
202
  "}",
184
203
  ".ss-dock-actions { display: flex; align-items: center; gap: 10px; flex: none; }",
204
+ "/* DockTip\uFF1A\u9010\u5B57\u5BF9\u9F50\u5B98\u65B9 Tooltip.module.css .bubble\uFF08portal: true \u5C42\uFF09\u3002 */",
205
+ ".ss-dock-tip-anchor { display: inline-grid; flex: none; }",
206
+ ".ss-dock-tip {",
207
+ " position: fixed; z-index: 1100;",
208
+ " display: inline-flex; align-items: center; gap: 8px;",
209
+ " width: max-content; max-width: 50vw;",
210
+ " padding: 3px 7px; border-radius: var(--dsw-radius-sm);",
211
+ " background: var(--dsw-alias-tooltip-bg); color: var(--dsw-static-neutral-bluish-00);",
212
+ " font-size: 13px; line-height: 20px;",
213
+ " white-space: pre-line; overflow-wrap: break-word;",
214
+ " pointer-events: none;",
215
+ " transform: translateX(-50%);",
216
+ " animation: ss-dock-tip-in 150ms var(--ds-ease-in-out);",
217
+ "}",
218
+ '.ss-dock-tip[data-side="top"] { transform: translate(-50%, -100%); }',
219
+ "@keyframes ss-dock-tip-in { from { opacity: 0; } }",
220
+ "@media (prefers-reduced-motion: reduce) { .ss-dock-tip { animation: none; } }",
185
221
  ".ss-dock-action {",
186
222
  " display: grid; place-items: center; width: 28px; height: 28px; padding: 0; flex: none;",
187
223
  " border: none; border-radius: 999px; background: none; cursor: pointer;",
188
224
  " color: var(--dsw-alias-label-tertiary);",
189
225
  "}",
190
226
  ".ss-dock-action:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }",
191
- ".ss-dock-action:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }",
227
+ ".ss-dock-action:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: -2px; }",
192
228
  ".ss-dock-action:disabled { cursor: default; opacity: 0.45; }",
193
229
  "/* \u63D2\u8BDD\u5B8C\u6210\u6001\uFF1AQueueDock \u65E0\u6B64\u6001\uFF0C\u7528\u6210\u529F\u8272\u505A\u8F7B\u63D0\u793A\uFF08\u53E0\u52A0 disabled \u534A\u900F\u660E\uFF09\u3002 */",
194
230
  ".ss-dock-action-steer.ss-done { color: var(--dsw-alias-state-success-primary); }",
@@ -238,19 +274,40 @@ var COMPONENTS = [
238
274
  ".ss-dock-editing { flex-wrap: wrap; }",
239
275
  "",
240
276
  "/* ==================== \u9762\u677F ==================== */",
241
- "/* \u5BF9\u9F50\u5B98\u65B9 popover panel\uFF1Az-index 100\u3001border-inverted\u3001radius 12\u3001\u5B57\u53F7 12\u3001secondary \u6587\u5B57 */",
277
+ "/*",
278
+ " * \u62AC\u5347\u9762\u9010\u9879\u5BF9\u9F50\u5B98\u65B9 PickerPopover\uFF08@deepseek-ai/dsh-client-ui-schedule \u7684",
279
+ " * PickerPopover.module.css + primitives MenuSurface.module.css\uFF09\uFF1A",
280
+ " * \u80CC\u666F --dsw-menu-surface-fill\uFF08\u5E26\u900F\u660E\u5EA6\u7684\u83DC\u5355\u9762\uFF09+ blur(40px) \u6BDB\u73BB\u7483",
281
+ " * \u63CF\u8FB9\u8D70\u4E3B\u9898\u7684 --dsw-elevation-stroke\uFF080 0 0 .5px\uFF09\uFF0C\u800C\u4E0D\u662F\u624B\u5199 1px \u2014\u2014",
282
+ " * 0.5px \u63CF\u8FB9\u662F\u9AD8\u5206\u5C4F\u4E0B\u7684\u6B63\u786E\u7EC6\u7EBF\uFF0C1px \u5728 1x \u4E0A\u8FC7\u91CD\u3001\u5728 2x \u4E0A\u53C8\u53D1\u865A\u3002",
283
+ " * \u9634\u5F71 --dsw-elevation-prominent\uFF08\u542B\u63CF\u8FB9\u5C42\uFF0C\u4E0D\u662F\u7B2C\u4E8C\u5957\u9634\u5F71\uFF09\u3002",
284
+ " * \u5706\u89D2 16px = --dsw-radius-lg\uFF08\u5B98\u65B9\u9762\u677F\u540C\u4E00\u6863\uFF1B\u65E7\u7684 12px \u662F --dsw-radius-md\uFF09\u3002",
285
+ " * z-index 1100 = \u5B98\u65B9\u6D6E\u5C42\u6863\uFF08\u65E7\u7684 101 \u662F\u81EA\u9020\u503C\uFF0C\u6392\u5728 dock \u4E4B\u4E0B\uFF09\u3002",
286
+ " *",
287
+ " * \u26A0\uFE0F \u52FF\u518D\u5F15\u5165 --dsw-shadow-lv3 / --dsw-alias-border-inverted\uFF1A\u90A3\u662F\u66F4\u65E9\u8BBE\u8BA1\u4EE3\u6B21\u7684",
288
+ " * token\uFF0C\u6DF7\u7528\u4F1A\u8BA9\u9762\u677F\u5728\u6D45\u8272/\u6DF1\u8272\u4E0B\u90FD\u504F\u79BB\u5B98\u65B9\u89C2\u611F\uFF08issue #24\uFF09\u3002",
289
+ " */",
242
290
  ".ss-backdrop {",
243
- " position: fixed; inset: 0; z-index: 100; background: transparent;",
291
+ " position: fixed; inset: 0; z-index: 1099; background: transparent;",
244
292
  "}",
245
293
  ".ss-panel {",
246
- " position: fixed; z-index: 101;",
294
+ " position: fixed; top: auto; left: auto; z-index: 1100;",
295
+ " box-sizing: border-box; display: flex; flex-direction: column;",
247
296
  " width: min(340px, calc(100vw - 24px));",
248
- " background: var(--dsw-specific-menu);",
297
+ " padding: 12px;",
298
+ " background: var(--dsw-menu-surface-fill);",
299
+ " backdrop-filter: var(--dsw-menu-backdrop-filter);",
300
+ " -webkit-backdrop-filter: var(--dsw-menu-backdrop-filter);",
301
+ " border: 0; border-radius: var(--dsw-radius-lg);",
302
+ " box-shadow: var(--dsw-elevation-prominent);",
303
+ " /* \u63CF\u8FB9\u4E0E\u9634\u5F71\u540C\u53D8\u91CF\uFF1A--dsw-elevation-prominent \u81EA\u8EAB\u542B --dsw-elevation-stroke\uFF0C",
304
+ " \u8FD9\u91CC\u53EA\u9700\u7ED9 stroke \u989C\u8272\u3002\u5B98\u65B9 schedule/input-trigger/subagent \u4E09\u4E2A\u5305\u4E00\u81F4\u53D6 l1\u3002 */",
305
+ " --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);",
249
306
  " color: var(--dsw-alias-label-secondary);",
250
- " border: 1px solid var(--dsw-alias-border-inverted); border-radius: 12px;",
251
- " box-shadow: var(--dsw-shadow-lv3); padding: 12px; box-sizing: border-box;",
252
307
  " font-size: 12px; line-height: 20px; font-family: var(--dsw-font-family);",
253
308
  "}",
309
+ /* 兜底定位分支(无官方 useAnchoredPosition 的旧代次宿主)才用 bottom,
310
+ 有官方锚定时 left/top 由 hook 注入,这里只兜住首帧测量前的 top:auto。 */
254
311
  ".ss-panel-header {",
255
312
  " display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;",
256
313
  "}",
@@ -419,15 +476,22 @@ var COMPONENTS = [
419
476
  '.ss-presence[data-state="overdue"] .ss-presence-ring { animation: none; }',
420
477
  "",
421
478
  "/* \u65B0\u7248\u63CF\u8FB9\u65F6\u949F\uFF08modern\uFF09\u2014 \u5BF9\u9F50 DSH \u5B98\u65B9 spinner \u6837\u5F0F \u2500\u2500 */",
422
- ".ss-presence .ss-clock-face {",
479
+ "/* \u53CC\u73AF\uFF1Atrack \u6052\u5B9A\u4E3A\u5E95\uFF08\u7ED9\u51FA\u300C\u603B\u7A97\u53E3\u300D\u7684\u89C6\u89C9\u53C2\u7167\uFF09\uFF0Cprog \u662F\u4ECE 12 \u70B9\u8D77\u7684\u8FDB\u5EA6\u5F27\u3002",
480
+ " \u65E0\u4ECE\u7B97\u8FDB\u5EA6\u65F6 JS \u628A prog \u7F6E\u4E3A display:none \u2014\u2014 \u6B64\u65F6\u89C6\u89C9\u4E0E\u6539\u9020\u524D\u7684\u5355\u73AF\u5B8C\u5168\u4E00\u81F4\u3002 */",
481
+ ".ss-presence .ss-clock-track {",
423
482
  " fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;",
424
483
  " opacity: 0.25; animation: ss-clock-breathe 2.4s var(--ds-ease-in-out, ease-in-out) infinite;",
425
484
  "}",
485
+ ".ss-presence .ss-clock-prog {",
486
+ " fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round;",
487
+ " opacity: 1; transition: stroke-dashoffset 0.5s var(--ds-ease-in-out, ease-in-out);",
488
+ "}",
426
489
  ".ss-presence .ss-clock-hand { stroke: currentColor; stroke-width: 2; stroke-linecap: round; }",
427
490
  ".ss-presence .ss-clock-hand-h { opacity: 1; }",
428
491
  ".ss-presence .ss-clock-hand-m { opacity: 0.85; }",
429
- '.ss-presence[data-state="urgent"] .ss-clock-face { animation-name: ss-clock-pulse; animation-duration: 0.9s; }',
430
- '.ss-presence[data-state="overdue"] .ss-clock-face { animation: none; opacity: 1; }',
492
+ "/* urgent \u53EA\u8109\u51B2\u5E95\u73AF\uFF1A\u8FDB\u5EA6\u5F27\u627F\u8F7D\u786E\u5B9A\u4FE1\u606F\uFF0C\u8DDF\u7740\u95EA\u4F1A\u8BA9\u6BD4\u4F8B\u4E0D\u53EF\u8BFB\u3002 */",
493
+ '.ss-presence[data-state="urgent"] .ss-clock-track { animation-name: ss-clock-pulse; animation-duration: 0.9s; }',
494
+ '.ss-presence[data-state="overdue"] .ss-clock-track { animation: none; opacity: 1; }',
431
495
  "",
432
496
  "/* \u2500\u2500 \u4E09\u6001\u989C\u8272 \u2500\u2500 */",
433
497
  '.ss-presence[data-state="urgent"] { color: var(--dsw-alias-state-warn-secondary); }',
@@ -440,7 +504,9 @@ var COMPONENTS = [
440
504
  "@keyframes ss-clock-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }",
441
505
  "@media (prefers-reduced-motion: reduce) {",
442
506
  " .ss-presence .ss-presence-ring { animation: none; }",
443
- " .ss-presence .ss-clock-face { animation: none; }",
507
+ " .ss-presence .ss-clock-track { animation: none; }",
508
+ ' /* \u51CF\u5C11\u52A8\u6548\uFF1A\u8FDB\u5EA6\u5F27\u7684\u4F4D\u79FB\u8FC7\u6E21\u4E5F\u4E00\u5E76\u505C\uFF0C\u907F\u514D"\u8FDB\u5EA6\u81EA\u5DF1\u5728\u722C"\u7684\u89C2\u611F\u3002 */',
509
+ " .ss-presence .ss-clock-prog { transition: none; }",
444
510
  "}",
445
511
  "",
446
512
  "/* ==================== \u60AC\u6D6E\u5361\u7247\u72B6\u6001\u884C\u589E\u8865\uFF08issue #4\uFF09 ==================== */",
@@ -619,7 +685,8 @@ function injectStyles() {
619
685
  }
620
686
 
621
687
  // src/client/components/SchedButton.tsx
622
- var import_react6 = require("react");
688
+ var import_react7 = require("react");
689
+ var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
623
690
 
624
691
  // src/time-utils.ts
625
692
  function detectTimeZone() {
@@ -863,7 +930,9 @@ function useSchedT(locale) {
863
930
  }
864
931
 
865
932
  // src/client/components/SchedPanel.tsx
866
- var import_react3 = require("react");
933
+ var import_react4 = require("react");
934
+ var import_react_dom = require("react-dom");
935
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
867
936
 
868
937
  // src/smart-window.ts
869
938
  var DEFAULT_SMART_WINDOW = Object.freeze({
@@ -878,13 +947,13 @@ function isValidHhmm(value) {
878
947
  return typeof value === "string" && HHMM_RE.test(value);
879
948
  }
880
949
  function sanitizeSmartWindowConfig(raw) {
881
- const pick = (key, fallback) => isValidHhmm(raw?.[key]) ? raw[key] : fallback;
950
+ const pick2 = (key, fallback) => isValidHhmm(raw?.[key]) ? raw[key] : fallback;
882
951
  return {
883
- workStart: pick("workStart", DEFAULT_SMART_WINDOW.workStart),
884
- workEnd: pick("workEnd", DEFAULT_SMART_WINDOW.workEnd),
885
- lunchStart: pick("lunchStart", DEFAULT_SMART_WINDOW.lunchStart),
886
- lunchEnd: pick("lunchEnd", DEFAULT_SMART_WINDOW.lunchEnd),
887
- eveningEnd: pick("eveningEnd", DEFAULT_SMART_WINDOW.eveningEnd)
952
+ workStart: pick2("workStart", DEFAULT_SMART_WINDOW.workStart),
953
+ workEnd: pick2("workEnd", DEFAULT_SMART_WINDOW.workEnd),
954
+ lunchStart: pick2("lunchStart", DEFAULT_SMART_WINDOW.lunchStart),
955
+ lunchEnd: pick2("lunchEnd", DEFAULT_SMART_WINDOW.lunchEnd),
956
+ eveningEnd: pick2("eveningEnd", DEFAULT_SMART_WINDOW.eveningEnd)
888
957
  };
889
958
  }
890
959
  var SMART_MIN_DELAY_MS = 2 * 60 * 1e3;
@@ -1019,8 +1088,21 @@ function epochFromLocal(date, time, timeZone) {
1019
1088
  if (hour > 23 || minute > 59 || second > 59 || month < 1 || month > 12 || day < 1 || day > 31) {
1020
1089
  return void 0;
1021
1090
  }
1022
- const approx = Date.UTC(year, month - 1, day, 0, 0, 0, 0);
1023
- return resolveWallClock(approx, timeZone, hour, minute, second);
1091
+ const localEpoch = Date.UTC(year, month - 1, day, hour, minute, second, 0);
1092
+ const offsets = /* @__PURE__ */ new Set();
1093
+ for (let delta = -2; delta <= 2; delta += 1) {
1094
+ offsets.add(offsetOf(localEpoch + delta * DAY_MS2, timeZone));
1095
+ }
1096
+ const candidates = [];
1097
+ for (const offset of offsets) {
1098
+ const candidate = localEpoch - offset;
1099
+ const f = localFieldsOf(candidate, timeZone);
1100
+ if (f.year === year && f.month === month && f.day === day && f.hour === hour && f.minute === minute && f.second === second) {
1101
+ candidates.push(candidate);
1102
+ }
1103
+ }
1104
+ candidates.sort((a, b) => a - b);
1105
+ return candidates[0];
1024
1106
  }
1025
1107
  function nextSmartAt(now, timeZone, window2 = DEFAULT_SMART_WINDOW) {
1026
1108
  let target;
@@ -1046,6 +1128,7 @@ function nextSmartAt(now, timeZone, window2 = DEFAULT_SMART_WINDOW) {
1046
1128
  }
1047
1129
 
1048
1130
  // src/client/components/TaskList.tsx
1131
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1049
1132
  var import_jsx_runtime = require("react/jsx-runtime");
1050
1133
  function scheduleStateOf(item, now) {
1051
1134
  return Date.parse(item.scheduled_at) <= now ? "overdue" : "scheduled";
@@ -1079,7 +1162,7 @@ function TaskList({ items, now, deliveredIds, cancellingIds, onDelete, timeZone,
1079
1162
  event.stopPropagation();
1080
1163
  onDelete(item.id);
1081
1164
  },
1082
- children: "\u2715"
1165
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 })
1083
1166
  }
1084
1167
  )
1085
1168
  ] }, item.id);
@@ -1097,6 +1180,96 @@ function useNow(intervalMs = 1e3) {
1097
1180
  return now;
1098
1181
  }
1099
1182
 
1183
+ // src/client/primitives-compat.ts
1184
+ var import_react3 = require("react");
1185
+ var primitives = __toESM(require("@deepseek-ai/dsh-client-ui-primitives"), 1);
1186
+ function pick(name2) {
1187
+ return primitives[name2];
1188
+ }
1189
+ var hostUseAnchoredPosition = pick("useAnchoredPosition");
1190
+ var hostUseDismissOnOutsidePointer = pick("useDismissOnOutsidePointer");
1191
+ function useLegacyAnchoredPosition({
1192
+ open,
1193
+ anchorRef,
1194
+ panelRef,
1195
+ side = "bottom",
1196
+ align = "start",
1197
+ gap,
1198
+ margin
1199
+ }) {
1200
+ const [position, setPosition] = (0, import_react3.useState)(null);
1201
+ (0, import_react3.useLayoutEffect)(() => {
1202
+ if (!open) {
1203
+ setPosition(null);
1204
+ return void 0;
1205
+ }
1206
+ const measure = () => {
1207
+ const anchor = anchorRef.current;
1208
+ const panel2 = panelRef.current;
1209
+ if (anchor === null || panel2 === null) return;
1210
+ const rect = anchor.getBoundingClientRect();
1211
+ const panelWidth = panel2.offsetWidth;
1212
+ const panelHeight = panel2.offsetHeight;
1213
+ const viewportWidth = window.innerWidth;
1214
+ const viewportHeight = window.innerHeight;
1215
+ const left = align === "end" ? rect.right - panelWidth : rect.left;
1216
+ const rawTop = side === "top" ? rect.top - panelHeight - gap : rect.bottom + gap;
1217
+ const clampedLeft = Math.min(
1218
+ Math.max(margin, left),
1219
+ Math.max(margin, viewportWidth - panelWidth - margin)
1220
+ );
1221
+ const overflowsTop = rawTop < margin;
1222
+ const overflowsBottom = rawTop + panelHeight > viewportHeight - margin;
1223
+ const top = overflowsTop && !overflowsBottom ? rect.bottom + gap : overflowsBottom && !overflowsTop ? rect.top - panelHeight - gap : rawTop;
1224
+ setPosition({
1225
+ left: `${Math.round(clampedLeft)}px`,
1226
+ top: `${Math.round(Math.max(margin, Math.min(top, viewportHeight - panelHeight - margin)))}px`
1227
+ });
1228
+ };
1229
+ measure();
1230
+ window.addEventListener("scroll", measure, true);
1231
+ window.addEventListener("resize", measure);
1232
+ const panel = panelRef.current;
1233
+ const observer = typeof ResizeObserver === "undefined" || panel === null ? void 0 : new ResizeObserver(measure);
1234
+ if (observer !== void 0 && panel !== null) observer.observe(panel);
1235
+ return () => {
1236
+ window.removeEventListener("scroll", measure, true);
1237
+ window.removeEventListener("resize", measure);
1238
+ observer?.disconnect();
1239
+ };
1240
+ }, [open, anchorRef, panelRef, side, align, gap, margin]);
1241
+ return position;
1242
+ }
1243
+ function noopDismiss() {
1244
+ }
1245
+ var anchorImpl = hostUseAnchoredPosition ?? useLegacyAnchoredPosition;
1246
+ var dismissImpl = hostUseDismissOnOutsidePointer ?? noopDismiss;
1247
+ var hasOfficialOutsideDismiss = hostUseDismissOnOutsidePointer !== void 0;
1248
+ var hasOfficialAnchoredPosition = hostUseAnchoredPosition !== void 0;
1249
+ function usePanelAnchorPosition(options) {
1250
+ return anchorImpl(options);
1251
+ }
1252
+ function usePanelOutsideDismiss(root, open, setOpen, portal) {
1253
+ return dismissImpl(root, open, setOpen, portal);
1254
+ }
1255
+ var PANEL_MEASURE_STYLE = {
1256
+ visibility: "hidden",
1257
+ left: 0,
1258
+ top: 0
1259
+ };
1260
+ function useEscapeToClose(open, setOpen) {
1261
+ const setOpenRef = (0, import_react3.useRef)(setOpen);
1262
+ setOpenRef.current = setOpen;
1263
+ (0, import_react3.useEffect)(() => {
1264
+ if (!open) return void 0;
1265
+ const onKey = (event) => {
1266
+ if (event.key === "Escape") setOpenRef.current(false);
1267
+ };
1268
+ window.addEventListener("keydown", onKey, true);
1269
+ return () => window.removeEventListener("keydown", onKey, true);
1270
+ }, [open]);
1271
+ }
1272
+
1100
1273
  // src/client/components/SchedPanel.tsx
1101
1274
  var import_jsx_runtime2 = require("react/jsx-runtime");
1102
1275
  function atOnDay(dayOffset, hhmm, timeZone, now) {
@@ -1119,22 +1292,36 @@ function SchedPanel({
1119
1292
  onCreate,
1120
1293
  onDelete,
1121
1294
  bottom,
1295
+ anchorRef,
1296
+ panelRef: externalPanelRef,
1122
1297
  smartWindow = DEFAULT_SMART_WINDOW,
1123
1298
  promptLimits
1124
1299
  }) {
1125
1300
  const now = useNow(1e3);
1126
- const [selected, setSelected] = (0, import_react3.useState)("after-600");
1127
- const [customDate, setCustomDate] = (0, import_react3.useState)(() => {
1301
+ const localPanelRef = (0, import_react4.useRef)(null);
1302
+ const panelRef = externalPanelRef ?? localPanelRef;
1303
+ const anchored = usePanelAnchorPosition({
1304
+ open: true,
1305
+ anchorRef: anchorRef ?? { current: null },
1306
+ panelRef,
1307
+ side: "top",
1308
+ align: "start",
1309
+ gap: 4,
1310
+ margin: 12
1311
+ });
1312
+ const panelStyle = hasOfficialAnchoredPosition ? anchored ?? PANEL_MEASURE_STYLE : { bottom };
1313
+ const [selected, setSelected] = (0, import_react4.useState)("after-600");
1314
+ const [customDate, setCustomDate] = (0, import_react4.useState)(() => {
1128
1315
  const f = localFieldsOf(Date.now(), timeZone);
1129
1316
  return `${String(f.year).padStart(4, "0")}-${String(f.month).padStart(2, "0")}-${String(f.day).padStart(2, "0")}`;
1130
1317
  });
1131
- const [customTime, setCustomTime] = (0, import_react3.useState)("18:00");
1132
- const [error, setError] = (0, import_react3.useState)(null);
1133
- const [cancelling, setCancelling] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
1134
- const [delivered, setDelivered] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
1135
- const deliveredTimers = (0, import_react3.useRef)([]);
1136
- const prevIds = (0, import_react3.useRef)(null);
1137
- (0, import_react3.useEffect)(() => {
1318
+ const [customTime, setCustomTime] = (0, import_react4.useState)("18:00");
1319
+ const [error, setError] = (0, import_react4.useState)(null);
1320
+ const [cancelling, setCancelling] = (0, import_react4.useState)(/* @__PURE__ */ new Set());
1321
+ const [delivered, setDelivered] = (0, import_react4.useState)(/* @__PURE__ */ new Set());
1322
+ const deliveredTimers = (0, import_react4.useRef)([]);
1323
+ const prevIds = (0, import_react4.useRef)(null);
1324
+ (0, import_react4.useEffect)(() => {
1138
1325
  const currentIds = new Set(schedules.map((item) => item.id));
1139
1326
  const previous = prevIds.current;
1140
1327
  prevIds.current = currentIds;
@@ -1153,13 +1340,13 @@ function SchedPanel({
1153
1340
  deliveredTimers.current.push(timer);
1154
1341
  }
1155
1342
  }, [schedules, cancelling]);
1156
- (0, import_react3.useEffect)(
1343
+ (0, import_react4.useEffect)(
1157
1344
  () => () => {
1158
1345
  for (const timer of deliveredTimers.current) window.clearTimeout(timer);
1159
1346
  },
1160
1347
  []
1161
1348
  );
1162
- const quickOptions = (0, import_react3.useMemo)(() => {
1349
+ const quickOptions = (0, import_react4.useMemo)(() => {
1163
1350
  const options = [
1164
1351
  { kind: "after", seconds: 600, label: t.quick10m },
1165
1352
  { kind: "after", seconds: 3600, label: t.quick1h }
@@ -1184,7 +1371,7 @@ function SchedPanel({
1184
1371
  options.push({ kind: "custom", label: t.quickCustom });
1185
1372
  return options;
1186
1373
  }, [timeZone, now, t, smartWindow]);
1187
- const target = (0, import_react3.useMemo)(() => {
1374
+ const target = (0, import_react4.useMemo)(() => {
1188
1375
  if (selected === "custom") {
1189
1376
  const match = /^(\d{2}):(\d{2})$/.exec(customTime);
1190
1377
  if (match === null) return null;
@@ -1197,7 +1384,7 @@ function SchedPanel({
1197
1384
  if (option.kind === "after") return { after_seconds: option.seconds, epoch: now + option.seconds * 1e3 };
1198
1385
  return { at: option.at, epoch: option.epoch };
1199
1386
  }, [selected, customDate, customTime, timeZone, now, quickOptions]);
1200
- const targetLabel = (0, import_react3.useMemo)(() => {
1387
+ const targetLabel = (0, import_react4.useMemo)(() => {
1201
1388
  if (target === void 0 || target === null) return null;
1202
1389
  return `${formatAbsolute(target.epoch, timeZone, lang)} \xB7 ${formatRelative(target.epoch, now, lang)}`;
1203
1390
  }, [target, timeZone, now]);
@@ -1245,87 +1432,101 @@ function SchedPanel({
1245
1432
  };
1246
1433
  const promptEmpty = defaultPrompt.trim().length === 0;
1247
1434
  const canConfirm = !busy && !promptEmpty && target !== null;
1248
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-panel", "data-ss-panel": "", role: "dialog", "aria-label": t.panelTitle, style: { bottom }, children: [
1249
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-panel-header", children: [
1250
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ss-panel-title", children: t.panelTitle }),
1251
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "ss-panel-close", "aria-label": t.close, onClick: onClose, children: "\u2715" })
1252
- ] }),
1253
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-quick-grid", role: "radiogroup", "aria-label": t.panelTitle, children: quickOptions.map((option) => {
1254
- const key = keyOf(option);
1255
- const active = selected === key || option.kind === "custom" && selected === "custom";
1256
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1257
- "button",
1258
- {
1259
- type: "button",
1260
- role: "radio",
1261
- "aria-checked": active,
1262
- className: active ? "ss-quick-chip ss-active" : "ss-quick-chip",
1263
- onClick: () => {
1264
- setSelected(key);
1265
- setError(null);
1266
- },
1267
- children: option.label
1268
- },
1269
- key
1270
- );
1271
- }) }),
1272
- selected === "custom" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-custom-row", children: [
1273
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1274
- "input",
1275
- {
1276
- type: "date",
1277
- className: "ss-input",
1278
- "aria-label": t.dateLabel,
1279
- value: customDate,
1280
- onChange: (event) => {
1281
- if (event.target.value) setCustomDate(event.target.value);
1435
+ const panel = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1436
+ "div",
1437
+ {
1438
+ ref: panelRef,
1439
+ className: "ss-panel",
1440
+ "data-ss-panel": "",
1441
+ role: "dialog",
1442
+ "aria-label": t.panelTitle,
1443
+ style: panelStyle,
1444
+ onClick: (event) => event.stopPropagation(),
1445
+ children: [
1446
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-panel-header", children: [
1447
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ss-panel-title", children: t.panelTitle }),
1448
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "ss-panel-close", "aria-label": t.close, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.IconCloseOutlineRegular, { size: 14 }) })
1449
+ ] }),
1450
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-quick-grid", role: "radiogroup", "aria-label": t.panelTitle, children: quickOptions.map((option) => {
1451
+ const key = keyOf(option);
1452
+ const active = selected === key || option.kind === "custom" && selected === "custom";
1453
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1454
+ "button",
1455
+ {
1456
+ type: "button",
1457
+ role: "radio",
1458
+ "aria-checked": active,
1459
+ className: active ? "ss-quick-chip ss-active" : "ss-quick-chip",
1460
+ onClick: () => {
1461
+ setSelected(key);
1462
+ setError(null);
1463
+ },
1464
+ children: option.label
1465
+ },
1466
+ key
1467
+ );
1468
+ }) }),
1469
+ selected === "custom" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-custom-row", children: [
1470
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1471
+ "input",
1472
+ {
1473
+ type: "date",
1474
+ className: "ss-input",
1475
+ "aria-label": t.dateLabel,
1476
+ value: customDate,
1477
+ onChange: (event) => {
1478
+ if (event.target.value) setCustomDate(event.target.value);
1479
+ }
1480
+ }
1481
+ ),
1482
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1483
+ "input",
1484
+ {
1485
+ type: "time",
1486
+ className: "ss-input",
1487
+ "aria-label": t.timeLabel,
1488
+ value: customTime,
1489
+ onChange: (event) => {
1490
+ if (event.target.value) setCustomTime(event.target.value);
1491
+ }
1492
+ }
1493
+ )
1494
+ ] }),
1495
+ target !== null && targetLabel !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-hint", style: { margin: "6px 0 0" }, children: targetLabel }),
1496
+ error !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-error", role: "alert", style: { marginTop: 6 }, children: error }),
1497
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-tasklist-head", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ss-hint", children: [
1498
+ t.taskListTitle,
1499
+ schedules.length > 0 ? ` (${schedules.length})` : ""
1500
+ ] }) }),
1501
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1502
+ TaskList,
1503
+ {
1504
+ items: schedules,
1505
+ now,
1506
+ deliveredIds: delivered,
1507
+ cancellingIds: cancelling,
1508
+ onDelete: handleDelete,
1509
+ timeZone,
1510
+ t,
1511
+ lang
1282
1512
  }
1283
- }
1284
- ),
1285
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1286
- "input",
1287
- {
1288
- type: "time",
1289
- className: "ss-input",
1290
- "aria-label": t.timeLabel,
1291
- value: customTime,
1292
- onChange: (event) => {
1293
- if (event.target.value) setCustomTime(event.target.value);
1513
+ ),
1514
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-footer", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1515
+ "button",
1516
+ {
1517
+ type: "button",
1518
+ className: "ss-confirm",
1519
+ disabled: !canConfirm,
1520
+ onClick: () => void handleConfirm(),
1521
+ children: busy ? t.sending : target !== null ? format(t.confirmAdd, { time: formatAbsolute(target.epoch, timeZone, lang) }) : t.confirmAddNoTime
1294
1522
  }
1295
- }
1296
- )
1297
- ] }),
1298
- target !== null && targetLabel !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-hint", style: { margin: "6px 0 0" }, children: targetLabel }),
1299
- error !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-error", role: "alert", style: { marginTop: 6 }, children: error }),
1300
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-tasklist-head", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ss-hint", children: [
1301
- t.taskListTitle,
1302
- schedules.length > 0 ? ` (${schedules.length})` : ""
1303
- ] }) }),
1304
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1305
- TaskList,
1306
- {
1307
- items: schedules,
1308
- now,
1309
- deliveredIds: delivered,
1310
- cancellingIds: cancelling,
1311
- onDelete: handleDelete,
1312
- timeZone,
1313
- t,
1314
- lang
1315
- }
1316
- ),
1317
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-footer", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1318
- "button",
1319
- {
1320
- type: "button",
1321
- className: "ss-confirm",
1322
- disabled: !canConfirm,
1323
- onClick: () => void handleConfirm(),
1324
- children: busy ? t.sending : target !== null ? format(t.confirmAdd, { time: formatAbsolute(target.epoch, timeZone, lang) }) : t.confirmAddNoTime
1325
- }
1326
- ) }),
1327
- promptEmpty && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-hint", style: { marginTop: 6 }, children: t.promptFromDraft })
1328
- ] });
1523
+ ) }),
1524
+ promptEmpty && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-hint", style: { marginTop: 6 }, children: t.promptFromDraft })
1525
+ ]
1526
+ }
1527
+ );
1528
+ if (typeof document === "undefined") return panel;
1529
+ return (0, import_react_dom.createPortal)(panel, document.body);
1329
1530
  }
1330
1531
  function keyOf(option) {
1331
1532
  switch (option.kind) {
@@ -1341,7 +1542,7 @@ function keyOf(option) {
1341
1542
  }
1342
1543
 
1343
1544
  // src/client/hooks/useSchedules.ts
1344
- var import_react4 = require("react");
1545
+ var import_react5 = require("react");
1345
1546
 
1346
1547
  // src/client/local-store.ts
1347
1548
  var STORAGE_KEY = "dsh-later:pending";
@@ -1394,28 +1595,28 @@ var createLine = (input) => {
1394
1595
  var deleteLine = (id) => `/user-schedule-delete ${JSON.stringify({ id })}`;
1395
1596
  function useSchedules(inject2) {
1396
1597
  const { callCommand, sessionId, inputActions, input, useProjection, useInput } = inject2;
1397
- const [busy, setBusy] = (0, import_react4.useState)(false);
1398
- const [anchorBottom, setAnchorBottom] = (0, import_react4.useState)(80);
1399
- const [pendingSync, setPendingSync] = (0, import_react4.useState)(() => readPending());
1400
- const rowRef = (0, import_react4.useRef)(null);
1598
+ const [busy, setBusy] = (0, import_react5.useState)(false);
1599
+ const [anchorBottom, setAnchorBottom] = (0, import_react5.useState)(80);
1600
+ const [pendingSync, setPendingSync] = (0, import_react5.useState)(() => readPending());
1601
+ const rowRef = (0, import_react5.useRef)(null);
1401
1602
  const now = useNow(1e3);
1402
1603
  const draft = input?.draft ?? (typeof useInput === "function" ? useInput((s) => s.draft) : "") ?? "";
1403
1604
  const projection = typeof useProjection === "function" ? useProjection("userSchedules") : void 0;
1404
- const schedules = (0, import_react4.useMemo)(() => schedulesOf(projection), [projection]);
1405
- const nextEpoch = (0, import_react4.useMemo)(() => {
1605
+ const schedules = (0, import_react5.useMemo)(() => schedulesOf(projection), [projection]);
1606
+ const nextEpoch = (0, import_react5.useMemo)(() => {
1406
1607
  return schedules.map((item) => Date.parse(item.scheduled_at)).filter((value) => !Number.isNaN(value)).sort((a, b) => a - b)[0];
1407
1608
  }, [schedules]);
1408
1609
  const enabled = draft.trim().length > 0 || schedules.length > 0;
1409
- const reposition = (0, import_react4.useCallback)(() => {
1610
+ const reposition = (0, import_react5.useCallback)(() => {
1410
1611
  const el = rowRef.current;
1411
1612
  if (el === null) return;
1412
1613
  const rect = el.getBoundingClientRect();
1413
1614
  setAnchorBottom(Math.max(12, window.innerHeight - rect.top + 10));
1414
1615
  }, []);
1415
- const syncedPrompts = (0, import_react4.useMemo)(() => {
1616
+ const syncedPrompts = (0, import_react5.useMemo)(() => {
1416
1617
  return new Set(schedules.map((item) => item.prompt));
1417
1618
  }, [schedules]);
1418
- (0, import_react4.useEffect)(() => {
1619
+ (0, import_react5.useEffect)(() => {
1419
1620
  if (pendingSync.length === 0) return;
1420
1621
  const remaining = pendingSync.filter((item) => !syncedPrompts.has(item.payload.prompt));
1421
1622
  if (remaining.length !== pendingSync.length) {
@@ -1423,7 +1624,7 @@ function useSchedules(inject2) {
1423
1624
  writePendingLocal(remaining);
1424
1625
  }
1425
1626
  }, [syncedPrompts, pendingSync]);
1426
- (0, import_react4.useEffect)(() => {
1627
+ (0, import_react5.useEffect)(() => {
1427
1628
  if (pendingSync.length === 0 || !sessionId) return;
1428
1629
  let cancelled = false;
1429
1630
  const syncAll = async () => {
@@ -1451,7 +1652,7 @@ function useSchedules(inject2) {
1451
1652
  window.removeEventListener("online", onOnline);
1452
1653
  };
1453
1654
  }, [pendingSync, sessionId, callCommand]);
1454
- const handleCreate = (0, import_react4.useCallback)(async (inputPayload) => {
1655
+ const handleCreate = (0, import_react5.useCallback)(async (inputPayload) => {
1455
1656
  if (!sessionId) {
1456
1657
  throw new Error(CLIENT_ERR.NO_SESSION);
1457
1658
  }
@@ -1467,15 +1668,15 @@ function useSchedules(inject2) {
1467
1668
  setBusy(false);
1468
1669
  }
1469
1670
  }, [sessionId, callCommand]);
1470
- const handleDelete = (0, import_react4.useCallback)(async (id) => {
1671
+ const handleDelete = (0, import_react5.useCallback)(async (id) => {
1471
1672
  if (!sessionId) return;
1472
1673
  await callCommand(sessionId, deleteLine(id));
1473
1674
  }, [sessionId, callCommand]);
1474
- const handleCancelAll = (0, import_react4.useCallback)(async () => {
1675
+ const handleCancelAll = (0, import_react5.useCallback)(async () => {
1475
1676
  if (!sessionId) return;
1476
1677
  await Promise.all(schedules.map((item) => callCommand(sessionId, deleteLine(item.id))));
1477
1678
  }, [sessionId, schedules, callCommand]);
1478
- const handleClearDraft = (0, import_react4.useCallback)(() => {
1679
+ const handleClearDraft = (0, import_react5.useCallback)(() => {
1479
1680
  inputActions?.setDraft("");
1480
1681
  }, [inputActions]);
1481
1682
  return {
@@ -1503,7 +1704,7 @@ function writePendingLocal(pending) {
1503
1704
  }
1504
1705
 
1505
1706
  // src/client/hooks/useSchedulerSettings.ts
1506
- var import_react5 = require("react");
1707
+ var import_react6 = require("react");
1507
1708
  var CLIENT_DEFAULT_MAX_PROMPT_CHARS = 1e3;
1508
1709
  var FALLBACK_PROMPT = {
1509
1710
  allowLong: false,
@@ -1547,16 +1748,16 @@ function resolveFromSnapshot(snap) {
1547
1748
  var NOOP_SUBSCRIBE = () => () => void 0;
1548
1749
  var EMPTY_SNAPSHOT = { status: "unavailable" };
1549
1750
  function useSchedulerSettings(scope) {
1550
- const getSnapshot = (0, import_react5.useCallback)(
1751
+ const getSnapshot = (0, import_react6.useCallback)(
1551
1752
  () => scope === void 0 ? EMPTY_SNAPSHOT : scope.getSnapshot(),
1552
1753
  [scope]
1553
1754
  );
1554
- const subscribe = (0, import_react5.useCallback)(
1755
+ const subscribe = (0, import_react6.useCallback)(
1555
1756
  (onStoreChange) => scope === void 0 ? NOOP_SUBSCRIBE() : scope.subscribe(onStoreChange),
1556
1757
  [scope]
1557
1758
  );
1558
- const raw = (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1559
- return (0, import_react5.useMemo)(() => resolveFromSnapshot(raw), [raw]);
1759
+ const raw = (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1760
+ return (0, import_react6.useMemo)(() => resolveFromSnapshot(raw), [raw]);
1560
1761
  }
1561
1762
 
1562
1763
  // src/client/components/SchedButton.tsx
@@ -1565,7 +1766,7 @@ function SchedButton(props) {
1565
1766
  const { callCommand, sessionId, inputActions, input, useProjection, useInput, locale, schedulerScope } = props;
1566
1767
  const { lang, t } = useSchedT(locale);
1567
1768
  const { smartWindow, promptLimits, showButton } = useSchedulerSettings(schedulerScope);
1568
- const [open, setOpen] = (0, import_react6.useState)(false);
1769
+ const [open, setOpen] = (0, import_react7.useState)(false);
1569
1770
  const {
1570
1771
  draft,
1571
1772
  schedules,
@@ -1584,19 +1785,15 @@ function SchedButton(props) {
1584
1785
  useProjection,
1585
1786
  useInput
1586
1787
  });
1587
- (0, import_react6.useEffect)(() => {
1588
- if (!open) return;
1788
+ useEscapeToClose(open, setOpen);
1789
+ (0, import_react7.useEffect)(() => {
1790
+ if (!open) return void 0;
1589
1791
  reposition();
1590
1792
  window.addEventListener("resize", reposition);
1591
- const onKey = (event) => {
1592
- if (event.key === "Escape") setOpen(false);
1593
- };
1594
- window.addEventListener("keydown", onKey, true);
1595
- return () => {
1596
- window.removeEventListener("resize", reposition);
1597
- window.removeEventListener("keydown", onKey, true);
1598
- };
1793
+ return () => window.removeEventListener("resize", reposition);
1599
1794
  }, [open, reposition]);
1795
+ const panelRef = (0, import_react7.useRef)(null);
1796
+ usePanelOutsideDismiss(rowRef, open, setOpen, panelRef);
1600
1797
  if (!showButton) return null;
1601
1798
  const handleCreateWithErrorBoundary = async (inputPayload) => {
1602
1799
  try {
@@ -1617,14 +1814,11 @@ function SchedButton(props) {
1617
1814
  "aria-expanded": open,
1618
1815
  disabled: !enabled,
1619
1816
  onClick: () => setOpen((value) => !value),
1620
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1621
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
1622
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "12 6 12 12 16 14" })
1623
- ] })
1817
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dsh_client_ui_primitives3.IconClockOutlineRegular, { size: 16 })
1624
1818
  }
1625
1819
  ),
1626
1820
  open && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1627
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ss-backdrop", onClick: () => setOpen(false), "aria-hidden": "true" }),
1821
+ !hasOfficialOutsideDismiss && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ss-backdrop", onClick: () => setOpen(false), "aria-hidden": "true" }),
1628
1822
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1629
1823
  SchedPanel,
1630
1824
  {
@@ -1638,6 +1832,8 @@ function SchedButton(props) {
1638
1832
  onCreate: handleCreateWithErrorBoundary,
1639
1833
  onDelete: (id) => void handleDelete(id),
1640
1834
  bottom: anchorBottom,
1835
+ anchorRef: rowRef,
1836
+ panelRef,
1641
1837
  smartWindow,
1642
1838
  promptLimits
1643
1839
  }
@@ -1647,7 +1843,9 @@ function SchedButton(props) {
1647
1843
  }
1648
1844
 
1649
1845
  // src/client/components/ScheduleDock.tsx
1650
- var import_react7 = require("react");
1846
+ var import_react8 = require("react");
1847
+ var import_react_dom2 = require("react-dom");
1848
+ var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
1651
1849
 
1652
1850
  // src/bar-geometry.ts
1653
1851
  var BAR_UNIT_MS = 36e5;
@@ -1758,8 +1956,8 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1758
1956
  const now = useNow(1e3);
1759
1957
  const { t } = useSchedT(locale);
1760
1958
  const projection = typeof useProjection === "function" ? useProjection("userSchedules") : void 0;
1761
- const [dismissedPaused, setDismissedPaused] = (0, import_react7.useState)(/* @__PURE__ */ new Set());
1762
- (0, import_react7.useEffect)(() => {
1959
+ const [dismissedPaused, setDismissedPaused] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
1960
+ (0, import_react8.useEffect)(() => {
1763
1961
  const live = projection?.schedules;
1764
1962
  if (live === void 0) return;
1765
1963
  setDismissedPaused((current) => {
@@ -1772,8 +1970,8 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1772
1970
  return changed ? nextSet : current;
1773
1971
  });
1774
1972
  }, [projection]);
1775
- const [promptOverrides, setPromptOverrides] = (0, import_react7.useState)(/* @__PURE__ */ new Map());
1776
- (0, import_react7.useEffect)(() => {
1973
+ const [promptOverrides, setPromptOverrides] = (0, import_react8.useState)(/* @__PURE__ */ new Map());
1974
+ (0, import_react8.useEffect)(() => {
1777
1975
  const live = projection?.schedules;
1778
1976
  if (live === void 0 || promptOverrides.size === 0) return;
1779
1977
  setPromptOverrides((current) => {
@@ -1790,7 +1988,7 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1790
1988
  return changed ? nextMap : current;
1791
1989
  });
1792
1990
  }, [projection, promptOverrides]);
1793
- const schedules = (0, import_react7.useMemo)(
1991
+ const schedules = (0, import_react8.useMemo)(
1794
1992
  () => (projection?.schedules ?? []).filter((item) => !(item.status === "paused" && dismissedPaused.has(item.id))).map((item) => {
1795
1993
  const override = promptOverrides.get(item.id);
1796
1994
  return override === void 0 ? item : { ...item, prompt: override };
@@ -1799,17 +1997,17 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1799
1997
  ),
1800
1998
  [projection, dismissedPaused, promptOverrides]
1801
1999
  );
1802
- const [collapsed, setCollapsed] = (0, import_react7.useState)(true);
1803
- const [cancelling, setCancelling] = (0, import_react7.useState)(/* @__PURE__ */ new Set());
1804
- const [editingId, setEditingId] = (0, import_react7.useState)(null);
1805
- const [editingText, setEditingText] = (0, import_react7.useState)("");
1806
- const [editErr, setEditErr] = (0, import_react7.useState)(null);
1807
- const [saving, setSaving] = (0, import_react7.useState)(false);
1808
- const [steering, setSteering] = (0, import_react7.useState)(/* @__PURE__ */ new Set());
1809
- const [steerDone, setSteerDone] = (0, import_react7.useState)(/* @__PURE__ */ new Set());
1810
- const [steerErr, setSteerErr] = (0, import_react7.useState)(/* @__PURE__ */ new Map());
1811
- const [steerUnconfirmed, setSteerUnconfirmed] = (0, import_react7.useState)(/* @__PURE__ */ new Set());
1812
- (0, import_react7.useEffect)(() => {
2000
+ const [collapsed, setCollapsed] = (0, import_react8.useState)(true);
2001
+ const [cancelling, setCancelling] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
2002
+ const [editingId, setEditingId] = (0, import_react8.useState)(null);
2003
+ const [editingText, setEditingText] = (0, import_react8.useState)("");
2004
+ const [editErr, setEditErr] = (0, import_react8.useState)(null);
2005
+ const [saving, setSaving] = (0, import_react8.useState)(false);
2006
+ const [steering, setSteering] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
2007
+ const [steerDone, setSteerDone] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
2008
+ const [steerErr, setSteerErr] = (0, import_react8.useState)(/* @__PURE__ */ new Map());
2009
+ const [steerUnconfirmed, setSteerUnconfirmed] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
2010
+ (0, import_react8.useEffect)(() => {
1813
2011
  const live = projection?.schedules;
1814
2012
  if (live === void 0) return;
1815
2013
  const present = new Set(live.filter((item) => item.status !== "paused").map((item) => item.id));
@@ -1826,7 +2024,7 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1826
2024
  return changed ? next2 : current;
1827
2025
  });
1828
2026
  }, [projection]);
1829
- (0, import_react7.useEffect)(() => {
2027
+ (0, import_react8.useEffect)(() => {
1830
2028
  if (steerDone.size === 0) return void 0;
1831
2029
  const timer = window.setTimeout(() => {
1832
2030
  const live = projection?.schedules;
@@ -1847,11 +2045,11 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1847
2045
  }, STEER_CONFIRM_WINDOW_MS);
1848
2046
  return () => window.clearTimeout(timer);
1849
2047
  }, [steerDone, projection]);
1850
- const [pausing, setPausing] = (0, import_react7.useState)(/* @__PURE__ */ new Set());
1851
- const [resuming, setResuming] = (0, import_react7.useState)(/* @__PURE__ */ new Set());
1852
- const [pauseErr, setPauseErr] = (0, import_react7.useState)(/* @__PURE__ */ new Map());
1853
- const firstSeenRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
1854
- const lastActiveAtRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
2048
+ const [pausing, setPausing] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
2049
+ const [resuming, setResuming] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
2050
+ const [pauseErr, setPauseErr] = (0, import_react8.useState)(/* @__PURE__ */ new Map());
2051
+ const firstSeenRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
2052
+ const lastActiveAtRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
1855
2053
  const wallClock = Date.now();
1856
2054
  {
1857
2055
  const next2 = new Map(lastActiveAtRef.current);
@@ -1872,8 +2070,8 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1872
2070
  }
1873
2071
  if (changed) lastActiveAtRef.current = next2;
1874
2072
  }
1875
- const byId = (0, import_react7.useMemo)(() => new Map(schedules.map((item) => [item.id, item])), [schedules]);
1876
- (0, import_react7.useEffect)(() => {
2073
+ const byId = (0, import_react8.useMemo)(() => new Map(schedules.map((item) => [item.id, item])), [schedules]);
2074
+ (0, import_react8.useEffect)(() => {
1877
2075
  let changed = false;
1878
2076
  const next2 = new Map(firstSeenRef.current);
1879
2077
  for (const [id] of byId) {
@@ -1890,7 +2088,7 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1890
2088
  }
1891
2089
  if (changed) firstSeenRef.current = next2;
1892
2090
  }, [byId]);
1893
- (0, import_react7.useEffect)(() => {
2091
+ (0, import_react8.useEffect)(() => {
1894
2092
  if (schedules.length <= 1) setCollapsed(true);
1895
2093
  }, [schedules.length]);
1896
2094
  if (schedules.length === 0 || sessionId === void 0) return null;
@@ -2072,7 +2270,7 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
2072
2270
  children: saving ? "\u2026" : t.editSave
2073
2271
  }
2074
2272
  ),
2075
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "ss-dock-action", onClick: cancelEdit, "aria-label": t.editCancel, children: "\u2715" }),
2273
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", className: "ss-dock-action", onClick: cancelEdit, "aria-label": t.editCancel, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconCloseOutlineRegular, { size: 14 }) }),
2076
2274
  editErr !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ss-dock-edit-err", children: editErr })
2077
2275
  ] }, item.id);
2078
2276
  }
@@ -2099,63 +2297,58 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
2099
2297
  ] })
2100
2298
  ] }),
2101
2299
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ss-dock-actions", children: [
2102
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2300
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockTip, { label: t.editTask, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2103
2301
  "button",
2104
2302
  {
2105
2303
  type: "button",
2106
2304
  className: "ss-dock-action",
2107
- title: t.editTask,
2108
2305
  "aria-label": t.editTask,
2109
2306
  onClick: () => startEdit(item),
2110
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QueueEditIcon, {})
2307
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconEditOutlineRegular, { size: 14 })
2111
2308
  }
2112
- ),
2113
- isPaused ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2309
+ ) }),
2310
+ isPaused ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockTip, { label: t.resumeTask, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2114
2311
  "button",
2115
2312
  {
2116
2313
  type: "button",
2117
2314
  className: `ss-dock-action ss-paused-active${resuming.has(item.id) ? " ss-pending" : ""}`,
2118
- title: t.resumeTask,
2119
2315
  "aria-label": t.resumeTask,
2120
2316
  disabled: resuming.has(item.id),
2121
2317
  onClick: () => handleResume(item),
2122
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QueueResumeIcon, {})
2318
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconPlayOutlineRegular, { size: 16 })
2123
2319
  }
2124
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2320
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockTip, { label: canPause(item, now) ? t.pauseTask : t.pauseUnsupportedKind, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2125
2321
  "button",
2126
2322
  {
2127
2323
  type: "button",
2128
2324
  className: `ss-dock-action${pausing.has(item.id) ? " ss-pending" : ""}`,
2129
- title: canPause(item, now) ? t.pauseTask : t.pauseUnsupportedKind,
2130
2325
  "aria-label": canPause(item, now) ? t.pauseTask : t.pauseUnsupportedKind,
2131
2326
  disabled: !canPause(item, now) || pausing.has(item.id),
2132
2327
  onClick: () => handlePause(item),
2133
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QueuePauseIcon, {})
2328
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconPauseOutlineRegular, { size: 16 })
2134
2329
  }
2135
- ),
2136
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2330
+ ) }),
2331
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockTip, { label: t.deleteTask, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2137
2332
  "button",
2138
2333
  {
2139
2334
  type: "button",
2140
2335
  className: "ss-dock-action",
2141
- title: t.deleteTask,
2142
2336
  "aria-label": t.deleteTask,
2143
2337
  onClick: () => handleCancel(item.id, isPaused ? "paused" : "active"),
2144
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QueueDeleteIcon, {})
2338
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconTrashOutlineRegular, { size: 14 })
2145
2339
  }
2146
- ),
2147
- item.kind !== "every" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2340
+ ) }),
2341
+ item.kind !== "every" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DockTip, { label: steerDone.has(item.id) ? t.steerAlreadySent : t.steerTask, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2148
2342
  "button",
2149
2343
  {
2150
2344
  type: "button",
2151
2345
  className: `ss-dock-action ss-dock-action-steer${steering.has(item.id) ? " ss-pending" : ""}${steerDone.has(item.id) ? " ss-done" : ""}`,
2152
- title: steerDone.has(item.id) ? t.steerAlreadySent : t.steerTask,
2153
2346
  "aria-label": steerDone.has(item.id) ? t.steerAlreadySent : t.steerTask,
2154
2347
  disabled: steering.has(item.id) || steerDone.has(item.id),
2155
2348
  onClick: () => handleSteer(item),
2156
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QueueSendIcon, {})
2349
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconSendOutlineRegular, { size: 16 })
2157
2350
  }
2158
- )
2351
+ ) })
2159
2352
  ] })
2160
2353
  ]
2161
2354
  },
@@ -2171,9 +2364,9 @@ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
2171
2364
  "aria-expanded": !collapsed,
2172
2365
  onClick: () => setCollapsed((value) => !value),
2173
2366
  children: [
2174
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ss-dock-lead", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QueueClockIcon, {}) }),
2367
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ss-dock-lead", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconClockOutlineRegular, { size: 16 }) }),
2175
2368
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ss-dock-count", children: format(t.panelCount, { n: schedules.length }) }),
2176
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: expanded ? "ss-dock-chevron ss-open" : "ss-dock-chevron", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(QueueChevronIcon, {}) })
2369
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: expanded ? "ss-dock-chevron ss-open" : "ss-dock-chevron", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dsh_client_ui_primitives4.IconChevronDownOutlineRegular, { size: 14 }) })
2177
2370
  ]
2178
2371
  }
2179
2372
  ),
@@ -2192,7 +2385,7 @@ function MultiBar({
2192
2385
  const frozenSeg = frozen ? frozenBarSegments(item, frozenLeftMs ?? 0, firstSeen, renderedAt) : void 0;
2193
2386
  const activeSeg = frozen ? void 0 : barSegments(item, now, firstSeen);
2194
2387
  const { totalRatio, count } = frozenSeg ?? activeSeg ?? { totalRatio: 0, count: 1 };
2195
- const phaseAtRef = (0, import_react7.useRef)(null);
2388
+ const phaseAtRef = (0, import_react8.useRef)(null);
2196
2389
  if (frozen || prefersReducedMotion()) {
2197
2390
  phaseAtRef.current = null;
2198
2391
  } else if (phaseAtRef.current === null) {
@@ -2236,33 +2429,76 @@ function MultiBar({
2236
2429
  }
2237
2430
  );
2238
2431
  }
2239
- function QueueClockIcon() {
2240
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M7.00049 0.199829C3.24488 0.199829 0.199952 3.24408 0.199707 6.99963C0.199707 8.0414 0.434087 9.03061 0.854004 9.91467L1.11279 10.4576L2.19775 9.94202L1.94092 9.39905L1.81787 9.12268C1.5498 8.46885 1.40186 7.75171 1.40186 6.99963C1.4021 3.90808 3.90888 1.40198 7.00049 1.40198C10.0919 1.40219 12.5979 3.90821 12.5981 6.99963C12.5981 10.0913 10.0921 12.5981 7.00049 12.5983C6.36734 12.5983 5.90348 12.5535 5.49268 12.4401C5.08803 12.3283 4.7041 12.1414 4.24463 11.8209C3.57111 11.3511 2.60588 11.1855 1.81006 11.6881L1.79736 11.6959L1.78467 11.7047L1.25537 12.0778L1.65381 13.2672L2.46045 12.6989C2.75029 12.5214 3.18004 12.5442 3.55615 12.8063C4.10063 13.1861 4.60863 13.4423 5.17334 13.5983C5.73194 13.7525 6.31665 13.8004 7.00049 13.8004C10.7561 13.8002 13.8003 10.7553 13.8003 6.99963C13.8 3.24421 10.7559 0.200041 7.00049 0.199829ZM3.81201 7.47327V8.67542H7.11572V7.47327H3.81201ZM3.81201 6.34924H10.2173V5.14709H3.81201V6.34924Z", fill: "currentColor" }) });
2241
- }
2242
- function QueueChevronIcon() {
2243
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) });
2244
- }
2245
- function QueueEditIcon() {
2246
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M9.94076 1.34942C10.7047 0.90231 11.6503 0.902415 12.4143 1.34942C12.7061 1.52015 12.9688 1.79118 13.3104 2.13284C13.6521 2.47448 13.9231 2.73721 14.0939 3.02894C14.5408 3.79294 14.5409 4.73856 14.0939 5.50251C13.9231 5.79415 13.652 6.05704 13.3104 6.39861L6.65932 13.0497C6.28068 13.4284 6.00695 13.7108 5.66543 13.9097C5.32391 14.1085 4.94315 14.2074 4.42705 14.3498L3.24394 14.6761C2.77527 14.8054 2.34538 14.9262 2.00131 14.9684C1.65196 15.0112 1.17964 15.0013 0.810764 14.6325C0.441921 14.2637 0.432107 13.7913 0.47486 13.442C0.517035 13.0979 0.6379 12.668 0.767181 12.1993L1.09352 11.0162C1.23588 10.5001 1.33481 10.1193 1.5336 9.77784C1.7325 9.43632 2.0149 9.1626 2.39355 8.78395L9.04466 2.13284C9.38625 1.79126 9.64911 1.52016 9.94076 1.34942ZM15.5427 14.8398H7.55223L8.96707 13.425H15.5427V14.8398ZM3.39382 9.78422C2.965 10.213 2.84244 10.3436 2.75709 10.49C2.67183 10.6366 2.61862 10.8079 2.45733 11.3925L2.13099 12.5756C2.00183 13.0439 1.92194 13.3419 1.88863 13.5536C2.10041 13.5204 2.39872 13.4416 2.86764 13.3123L4.05075 12.9859C4.63544 12.8246 4.80669 12.7715 4.95323 12.6862C5.09968 12.6008 5.23022 12.4783 5.65905 12.0494L10.721 6.98644L8.45577 4.72121L3.39382 9.78422ZM11.7 2.57079C11.3774 2.38198 10.9777 2.38198 10.6551 2.57079C10.5602 2.62647 10.4487 2.72931 10.0449 3.13311L9.45604 3.72094L11.7213 5.98617L12.3102 5.39833C12.7139 4.99457 12.8168 4.88307 12.8725 4.78818C13.0613 4.46561 13.0612 4.06585 12.8725 3.74326C12.8169 3.64827 12.7146 3.53752 12.3102 3.13311C11.9057 2.72863 11.795 2.6264 11.7 2.57079Z", fill: "currentColor" }) });
2247
- }
2248
- function QueueDeleteIcon() {
2249
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M14.4782 4.84067L14.2138 10.1152C14.1102 12.1872 14.067 13.0115 13.3866 13.9607C13.1044 14.3546 12.7498 14.6912 12.3424 14.9535C11.8239 15.2872 11.2415 15.4316 10.5585 15.4998C9.88727 15.5668 9.04946 15.5656 7.99998 15.5656C6.95051 15.5656 6.1127 15.5668 5.44142 15.4998C4.75851 15.4316 4.17602 15.2872 3.65753 14.9535C3.25012 14.6912 2.89559 14.3546 2.61332 13.9607C1.93296 13.0115 1.88979 12.1872 1.78619 10.1152L1.52179 4.84067L2.89006 4.77277L3.15343 10.0463C3.26221 12.2218 3.32452 12.6015 3.72646 13.1624C3.90825 13.4161 4.13686 13.6334 4.39927 13.8023C4.66204 13.9714 5.00263 14.0792 5.57825 14.1367C6.16562 14.1953 6.92298 14.1963 7.99998 14.1963C9.07699 14.1963 9.83434 14.1953 10.4217 14.1367C10.9973 14.0792 11.3379 13.9714 11.6007 13.8023C11.8631 13.6334 12.0917 13.4161 12.2735 13.1624C12.6755 12.6015 12.7378 12.2218 12.8465 10.0463L13.1099 4.77277L14.4782 4.84067ZM5.43011 6.22849H6.7994V11.3909H5.43011V6.22849ZM9.20056 6.22849H10.5699V11.3909H9.20056V6.22849ZM8.53597 0.434431C9.17976 0.434431 9.6522 0.426926 10.0966 0.571258C10.2357 0.616451 10.3717 0.672554 10.502 0.738948C10.9182 0.951107 11.2464 1.29099 11.7015 1.74612L12.4978 2.54136H15.3742V3.91169H0.625732V2.54136H3.50218L4.29845 1.74612C4.75358 1.29099 5.08174 0.951107 5.49801 0.738948C5.62831 0.672554 5.76425 0.616451 5.90334 0.571258C6.34776 0.426926 6.82021 0.434431 7.46399 0.434431H8.53597ZM7.46399 1.80476C6.73208 1.80476 6.51641 1.81187 6.32617 1.87369C6.25545 1.89667 6.18668 1.92533 6.12041 1.95907C5.96398 2.03878 5.82348 2.16253 5.44142 2.54136H10.5585C10.1765 2.16253 10.036 2.03878 9.87955 1.95907C9.81329 1.92533 9.74452 1.89667 9.6738 1.87369C9.48356 1.81187 9.26789 1.80476 8.53597 1.80476H7.46399Z", fill: "currentColor" }) });
2250
- }
2251
- function QueueSendIcon() {
2252
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M7.24707 1.01771C7.52897 1.07653 7.77619 1.19694 8.00391 1.38001C8.19202 1.53136 8.39884 1.73784 8.61914 1.95814L12.6396 5.9806L11.6299 6.99134L7.71484 3.0763V13.0001H6.28516V3.0763L2.36914 6.99134L1.35938 5.9806L5.38086 1.95814C5.60116 1.73784 5.80798 1.53136 5.99609 1.38001C6.19476 1.22027 6.4385 1.06739 6.75195 1.01771C6.91296 0.992304 7.07471 0.997504 7.24707 1.01771Z", fill: "currentColor" }) });
2253
- }
2254
- function QueuePauseIcon() {
2255
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: [
2256
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "3.6", y: "2.6", width: "3.2", height: "10.8", rx: "1.1", fill: "currentColor" }),
2257
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "9.2", y: "2.6", width: "3.2", height: "10.8", rx: "1.1", fill: "currentColor" })
2432
+ function DockTip({ label, children }) {
2433
+ const anchorRef = (0, import_react8.useRef)(null);
2434
+ const bubbleRef = (0, import_react8.useRef)(null);
2435
+ const [pos, setPos] = (0, import_react8.useState)(null);
2436
+ const timerRef = (0, import_react8.useRef)(null);
2437
+ const cancel = () => {
2438
+ if (timerRef.current !== null) {
2439
+ window.clearTimeout(timerRef.current);
2440
+ timerRef.current = null;
2441
+ }
2442
+ };
2443
+ const show = () => {
2444
+ const el = anchorRef.current;
2445
+ if (!el) return;
2446
+ const r = el.getBoundingClientRect();
2447
+ setPos({ left: r.left + r.width / 2, top: r.bottom + 8, side: "bottom" });
2448
+ };
2449
+ const enter = () => {
2450
+ cancel();
2451
+ timerRef.current = window.setTimeout(show, 500);
2452
+ };
2453
+ const leave = () => {
2454
+ cancel();
2455
+ setPos(null);
2456
+ };
2457
+ (0, import_react8.useEffect)(() => cancel, []);
2458
+ (0, import_react8.useLayoutEffect)(() => {
2459
+ if (!pos || !bubbleRef.current) return;
2460
+ const b = bubbleRef.current;
2461
+ const w = b.offsetWidth;
2462
+ const h = b.offsetHeight;
2463
+ const edge = 12;
2464
+ let left = pos.left;
2465
+ let top = pos.top;
2466
+ let side = pos.side;
2467
+ left = Math.max(edge + w / 2, Math.min(left, window.innerWidth - edge - w / 2));
2468
+ if (side === "bottom" && top + h > window.innerHeight - edge) {
2469
+ const anchorEl = anchorRef.current;
2470
+ const r = anchorEl.getBoundingClientRect();
2471
+ top = r.top - 8;
2472
+ side = "top";
2473
+ }
2474
+ b.style.left = `${left}px`;
2475
+ b.style.top = `${top}px`;
2476
+ b.dataset.side = side;
2477
+ b.style.visibility = "visible";
2478
+ }, [pos]);
2479
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2480
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2481
+ "span",
2482
+ {
2483
+ ref: anchorRef,
2484
+ className: "ss-dock-tip-anchor",
2485
+ onPointerEnter: enter,
2486
+ onPointerLeave: leave,
2487
+ onFocus: enter,
2488
+ onBlur: leave,
2489
+ children
2490
+ }
2491
+ ),
2492
+ pos && typeof document !== "undefined" && (0, import_react_dom2.createPortal)(
2493
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: bubbleRef, className: "ss-dock-tip", style: { left: pos.left, top: pos.top, visibility: "hidden" }, children: label }),
2494
+ document.body
2495
+ )
2258
2496
  ] });
2259
2497
  }
2260
- function QueueResumeIcon() {
2261
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4.4 2.9c0-.6.66-1 1.18-.68l7.1 4.1c.52.3.52 1.06 0 1.36l-7.1 4.1A.8.8 0 0 1 4.4 11.1V2.9Z", fill: "currentColor" }) });
2262
- }
2263
2498
 
2264
2499
  // src/client/components/SchedulerSettingsCard.tsx
2265
- var import_react8 = require("react");
2500
+ var import_react9 = require("react");
2501
+ var import_dsh_client_ui_primitives5 = require("@deepseek-ai/dsh-client-ui-primitives");
2266
2502
  var import_jsx_runtime5 = require("react/jsx-runtime");
2267
2503
  var SECTIONS = [
2268
2504
  {
@@ -2329,22 +2565,50 @@ var SECTIONS = [
2329
2565
  defaultValue: "keep"
2330
2566
  }
2331
2567
  ]
2568
+ },
2569
+ {
2570
+ id: "advanced",
2571
+ titleKey: "sectionAdvanced",
2572
+ layout: "stack",
2573
+ beta: true,
2574
+ fields: [
2575
+ {
2576
+ kind: "boolean",
2577
+ key: "advancedSyntax",
2578
+ labelKey: "advancedSyntaxLabel",
2579
+ hintKey: "advancedSyntaxHint",
2580
+ defaultValue: true
2581
+ }
2582
+ ]
2332
2583
  }
2333
2584
  ];
2334
2585
  var FIELDS = SECTIONS.flatMap((s) => s.fields);
2335
- function SchedulerSettingsCard({ scope, t }) {
2336
- const [snapshot, setSnapshot] = (0, import_react8.useState)(() => scope.getSnapshot());
2337
- const [pending, setPending] = (0, import_react8.useState)({});
2338
- const [saving, setSaving] = (0, import_react8.useState)(false);
2339
- const [failed, setFailed] = (0, import_react8.useState)(false);
2340
- const [helpOpen, setHelpOpen] = (0, import_react8.useState)({});
2341
- (0, import_react8.useEffect)(() => {
2586
+ function SchedulerSettingsCard({ scope, t, surface }) {
2587
+ const [snapshot, setSnapshot] = (0, import_react9.useState)(() => scope.getSnapshot());
2588
+ const [pending, setPending] = (0, import_react9.useState)({});
2589
+ const [saving, setSaving] = (0, import_react9.useState)(false);
2590
+ const [failed, setFailed] = (0, import_react9.useState)(false);
2591
+ const [helpOpen, setHelpOpen] = (0, import_react9.useState)({});
2592
+ const SHOW_SETTINGS_KEY = "dsh-later:show-settings-section";
2593
+ const [showSettings, setShowSettings] = (0, import_react9.useState)(() => {
2594
+ if (typeof localStorage === "undefined") return false;
2595
+ return localStorage.getItem(SHOW_SETTINGS_KEY) === "true";
2596
+ });
2597
+ const toggleShowSettings = () => {
2598
+ const next = !showSettings;
2599
+ setShowSettings(next);
2600
+ if (typeof localStorage !== "undefined") {
2601
+ if (next) localStorage.setItem(SHOW_SETTINGS_KEY, "true");
2602
+ else localStorage.removeItem(SHOW_SETTINGS_KEY);
2603
+ }
2604
+ };
2605
+ (0, import_react9.useEffect)(() => {
2342
2606
  const initial = scope.getSnapshot();
2343
2607
  setSnapshot(initial);
2344
2608
  setPending({});
2345
2609
  if (initial.status === "ready") setFailed(false);
2346
2610
  }, [scope]);
2347
- (0, import_react8.useEffect)(() => {
2611
+ (0, import_react9.useEffect)(() => {
2348
2612
  return scope.subscribe(() => {
2349
2613
  const snap = scope.getSnapshot();
2350
2614
  setSnapshot(snap);
@@ -2422,11 +2686,11 @@ function SchedulerSettingsCard({ scope, t }) {
2422
2686
  setFieldPending(field.key, baselineValue(field), true);
2423
2687
  setFailed(false);
2424
2688
  };
2425
- const handleDiscard = (0, import_react8.useCallback)(() => {
2689
+ const handleDiscard = (0, import_react9.useCallback)(() => {
2426
2690
  setPending({});
2427
2691
  setFailed(false);
2428
2692
  }, []);
2429
- const handleSave = (0, import_react8.useCallback)(async () => {
2693
+ const handleSave = (0, import_react9.useCallback)(async () => {
2430
2694
  if (!dirty || saving || !writable) return;
2431
2695
  setSaving(true);
2432
2696
  setFailed(false);
@@ -2462,8 +2726,32 @@ function SchedulerSettingsCard({ scope, t }) {
2462
2726
  const confirmEnabled = (pending.confirmDelivery?.value ?? (value.confirmDelivery === false ? "false" : "true")) !== "false";
2463
2727
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-settings-form", children: [
2464
2728
  !writable && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "ss-hint", role: "status", children: t("readOnly") }),
2729
+ surface === "detail" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "ss-section", "aria-labelledby": "ss-section-display-toggle", children: [
2730
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "ss-section-heading", id: "ss-section-display-toggle", children: t("sectionDisplayToggle") }),
2731
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ss-section-fields", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-field ss-fieldToggle", children: [
2732
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-toggle-row", children: [
2733
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ss-label-group", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { className: "ss-toggle-label", htmlFor: "ss-show-settings", children: t("showInSettings") }) }),
2734
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2735
+ "button",
2736
+ {
2737
+ type: "button",
2738
+ id: "ss-show-settings",
2739
+ role: "switch",
2740
+ "aria-checked": showSettings,
2741
+ "aria-label": t("showInSettings"),
2742
+ className: "ss-toggle-switch",
2743
+ onClick: toggleShowSettings
2744
+ }
2745
+ )
2746
+ ] }),
2747
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "ss-toggle-description", children: t("showInSettingsHint") })
2748
+ ] }) })
2749
+ ] }),
2465
2750
  SECTIONS.map((section) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "ss-section", "aria-labelledby": `ss-section-${section.id}`, children: [
2466
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "ss-section-heading", id: `ss-section-${section.id}`, children: t(section.titleKey) }),
2751
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h3", { className: "ss-section-heading", id: `ss-section-${section.id}`, children: [
2752
+ t(section.titleKey),
2753
+ section.beta === true ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-section-beta", "aria-label": t("betaBadgeAria"), children: t("betaBadge") }) : null
2754
+ ] }),
2467
2755
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: section.layout === "grid" ? "ss-section-fields ss-section-fieldsGrid" : "ss-section-fields", children: section.fields.map((field) => {
2468
2756
  const invalid = invalidField(field);
2469
2757
  const isTime = field.kind === "time";
@@ -2522,11 +2810,7 @@ function SchedulerSettingsCard({ scope, t }) {
2522
2810
  "aria-expanded": helpOpen[field.key] === true,
2523
2811
  "aria-controls": `ss-help-${field.key}`,
2524
2812
  onClick: () => setHelpOpen((prev) => ({ ...prev, [field.key]: !prev[field.key] })),
2525
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "12", height: "12", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", strokeWidth: "1", children: [
2526
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12.5757 7.00012C12.5757 3.92085 10.0794 1.42463 7.00012 1.42456C3.9208 1.42456 1.42456 3.9208 1.42456 7.00012C1.42463 10.0794 3.92085 12.5757 7.00012 12.5757C10.0793 12.5756 12.5756 10.0793 12.5757 7.00012ZM13.8002 7.00012C13.8001 10.7559 10.7559 13.8001 7.00012 13.8002C3.2443 13.8002 0.199291 10.7559 0.199219 7.00012C0.199219 3.24426 3.24426 0.199219 7.00012 0.199219C10.7559 0.199291 13.8002 3.2443 13.8002 7.00012Z", fill: "currentColor" }),
2527
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M7.6127 3.18921V4.55986H6.38735V3.18921H7.6127Z", fill: "currentColor" }),
2528
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M7.6127 5.68921V10.8109H6.38735V5.68921H7.6127Z", fill: "currentColor" })
2529
- ] })
2813
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_dsh_client_ui_primitives5.IconInfoOutlineRegular, { size: 12 })
2530
2814
  }
2531
2815
  )
2532
2816
  ] }),
@@ -2594,6 +2878,7 @@ function SchedulerSettingsCard({ scope, t }) {
2594
2878
  var PRESENCE_URGENT_WINDOW_MS = 5 * 6e4;
2595
2879
  function summarizeSchedules(schedules) {
2596
2880
  let nextAt;
2881
+ let progressFrom;
2597
2882
  let count = 0;
2598
2883
  let pausedCount = 0;
2599
2884
  for (const item of schedules ?? []) {
@@ -2606,10 +2891,44 @@ function summarizeSchedules(schedules) {
2606
2891
  pausedCount += 1;
2607
2892
  continue;
2608
2893
  }
2609
- if (nextAt === void 0 || epoch < nextAt) nextAt = epoch;
2894
+ if (nextAt === void 0 || epoch < nextAt) {
2895
+ nextAt = epoch;
2896
+ progressFrom = windowStartOf(item, epoch);
2897
+ }
2610
2898
  }
2611
2899
  if (count === 0) return { count: 0, pausedCount: 0 };
2612
- return nextAt === void 0 ? { count, pausedCount } : { count, nextAt, pausedCount };
2900
+ return nextAt === void 0 ? { count, pausedCount } : { count, nextAt, pausedCount, ...progressFrom !== void 0 ? { progressFrom } : {} };
2901
+ }
2902
+ function windowStartOf(item, scheduledEpoch) {
2903
+ const explicit = toFiniteSeconds(item.window_seconds ?? item.windowSeconds);
2904
+ if (explicit !== void 0) return scheduledEpoch - explicit * 1e3;
2905
+ const afterSec = toFiniteSeconds(item.after_seconds ?? item.afterSeconds);
2906
+ const everySec = toFiniteSeconds(item.every_seconds ?? item.everySeconds);
2907
+ const span = item.kind === "every" ? everySec : afterSec ?? everySec;
2908
+ if (span !== void 0) return scheduledEpoch - span * 1e3;
2909
+ const created = toEpochMs(item.created_at) ?? toEpochMs(item.createdAt);
2910
+ if (created !== null) return created;
2911
+ return void 0;
2912
+ }
2913
+ function toFiniteSeconds(value) {
2914
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
2915
+ }
2916
+ function toEpochMs(value) {
2917
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
2918
+ if (typeof value === "string" && value.length > 0) {
2919
+ const parsed = Date.parse(value);
2920
+ return Number.isFinite(parsed) ? parsed : null;
2921
+ }
2922
+ return null;
2923
+ }
2924
+ function progressOf(entry, now) {
2925
+ const { nextAt, progressFrom } = entry;
2926
+ if (nextAt === void 0 || progressFrom === void 0) return void 0;
2927
+ const span = nextAt - progressFrom;
2928
+ if (!(span > 0)) return void 0;
2929
+ const ratio = (now - progressFrom) / span;
2930
+ if (!Number.isFinite(ratio)) return void 0;
2931
+ return ratio <= 0 ? 0 : ratio >= 1 ? 1 : ratio;
2613
2932
  }
2614
2933
  function badgeStateFor(nextAt, now) {
2615
2934
  if (nextAt === void 0) return "scheduled";
@@ -2659,9 +2978,10 @@ var HOVER_ROW_CLASS = "ss-hover-status";
2659
2978
  var FIBER_MAX_DEPTH = 8;
2660
2979
  var SCAN_DEBOUNCE_MS = 60;
2661
2980
  var STATE_TICK_MS = 3e4;
2662
- var REFRESH_INTERVAL_MS = 6e4;
2663
2981
  var CLOCK_SVG_CLASSIC = '<svg width="10" height="10" viewBox="0 0 10 10" shape-rendering="crispEdges" aria-hidden="true"><g class="ss-presence-ring"><rect x="0" y="0" width="2" height="2"/><rect x="4" y="0" width="2" height="2"/><rect x="8" y="0" width="2" height="2"/><rect x="8" y="4" width="2" height="2"/><rect x="8" y="8" width="2" height="2"/><rect x="4" y="8" width="2" height="2"/><rect x="0" y="8" width="2" height="2"/><rect x="0" y="4" width="2" height="2"/></g><g class="ss-presence-hand"><rect x="4" y="2" width="2" height="2"/><rect x="6" y="4" width="2" height="2"/></g></svg>';
2664
- var CLOCK_SVG_MODERN = '<svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><circle class="ss-clock-face" cx="12" cy="12" r="9.5"/><g class="ss-clock-hand"><line class="ss-clock-hand-h" x1="12" y1="12" x2="12" y2="6.5"/><line class="ss-clock-hand-m" x1="12" y1="12" x2="17" y2="12"/></g></svg>';
2982
+ var CLOCK_FACE_R = 9.5;
2983
+ var CLOCK_FACE_CIRCUMFERENCE = 2 * Math.PI * CLOCK_FACE_R;
2984
+ var CLOCK_SVG_MODERN = `<svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true"><circle class="ss-clock-track" cx="12" cy="12" r="${CLOCK_FACE_R}"/><circle class="ss-clock-prog" cx="12" cy="12" r="${CLOCK_FACE_R}" stroke-dasharray="${CLOCK_FACE_CIRCUMFERENCE.toFixed(3)}" stroke-dashoffset="${CLOCK_FACE_CIRCUMFERENCE.toFixed(3)}" transform="rotate(-90 12 12)"/><g class="ss-clock-hand"><line class="ss-clock-hand-h" x1="12" y1="12" x2="12" y2="6.5"/><line class="ss-clock-hand-m" x1="12" y1="12" x2="17" y2="12"/></g></svg>`;
2665
2985
  var hhmmFormatter = new Intl.DateTimeFormat(void 0, { hour: "2-digit", minute: "2-digit", hour12: false });
2666
2986
  var dateFormatter = new Intl.DateTimeFormat(void 0, { month: "short", day: "numeric" });
2667
2987
  var formatHhmm2 = (epoch) => hhmmFormatter.format(epoch);
@@ -2684,14 +3004,9 @@ function sessionIdOfNode(element) {
2684
3004
  }
2685
3005
  return void 0;
2686
3006
  }
2687
- function buildIndex(state) {
2688
- const index = /* @__PURE__ */ new Map();
2689
- for (const [sessionId, summary] of Object.entries(state.byId)) {
2690
- const schedules = summary.projectionValues?.["userSchedules"]?.schedules;
2691
- const entry = summarizeSchedules(schedules);
2692
- if (entry.count > 0) index.set(sessionId, entry);
2693
- }
2694
- return index;
3007
+ function currentIndex(summary) {
3008
+ summary.reload();
3009
+ return summary.getSnapshot();
2695
3010
  }
2696
3011
  function findBadge(row) {
2697
3012
  for (const child of Array.from(row.children)) {
@@ -2712,7 +3027,7 @@ function labelsFor(dict) {
2712
3027
  };
2713
3028
  }
2714
3029
  function mountSessionPresence(options) {
2715
- const { sessions, locale, getClockIconStyle } = options;
3030
+ const { summary, locale, getClockIconStyle } = options;
2716
3031
  if (typeof document === "undefined" || typeof MutationObserver === "undefined") {
2717
3032
  return () => void 0;
2718
3033
  }
@@ -2726,7 +3041,7 @@ function mountSessionPresence(options) {
2726
3041
  const dictForNow = () => dictFor(locale?.getSnapshot().active);
2727
3042
  const scan = () => {
2728
3043
  if (disposed) return;
2729
- index = buildIndex(sessions.list.getSnapshot());
3044
+ index = currentIndex(summary);
2730
3045
  const dict = dictForNow();
2731
3046
  const now = Date.now();
2732
3047
  for (const row of Array.from(document.querySelectorAll('[role="treeitem"][draggable="true"]'))) {
@@ -2756,10 +3071,12 @@ function mountSessionPresence(options) {
2756
3071
  const allPaused = entry.nextAt === void 0 && entry.pausedCount > 0;
2757
3072
  const aria = allPaused ? format(dict.presencePausedAria, { n: entry.count }) : state === "overdue" ? format(dict.presenceOverdueAria, { n: entry.count }) : format(dict.presenceBadgeAria, { n: entry.count, time });
2758
3073
  const tooltip = allPaused ? format(dict.presencePausedTooltip, { n: entry.count }) : state === "overdue" ? format(dict.presenceOverdueTooltip, { n: entry.count }) : format(dict.presenceTooltip, { n: entry.count, time });
3074
+ const progress = progressOf(entry, now);
2759
3075
  if (existing !== null) {
2760
3076
  if (existing.dataset.state !== state) existing.dataset.state = state;
2761
3077
  if (existing.getAttribute("aria-label") !== aria) existing.setAttribute("aria-label", aria);
2762
3078
  if (existing.getAttribute("title") !== tooltip) existing.setAttribute("title", tooltip);
3079
+ applyProgress(existing, progress);
2763
3080
  return;
2764
3081
  }
2765
3082
  const slot = row.firstElementChild;
@@ -2772,9 +3089,20 @@ function mountSessionPresence(options) {
2772
3089
  badge.setAttribute("aria-label", aria);
2773
3090
  badge.setAttribute("title", tooltip);
2774
3091
  badge.innerHTML = getClockSvg();
3092
+ applyProgress(badge, progress);
2775
3093
  if (beside) slot.after(badge);
2776
3094
  else slot.append(badge);
2777
3095
  }
3096
+ function applyProgress(badge, ratio) {
3097
+ const arc = badge.querySelector(".ss-clock-prog");
3098
+ if (arc === null) return;
3099
+ if (ratio === void 0) {
3100
+ arc.style.display = "none";
3101
+ return;
3102
+ }
3103
+ if (arc.style.display === "none") arc.style.display = "";
3104
+ arc.style.strokeDashoffset = String(CLOCK_FACE_CIRCUMFERENCE * (1 - ratio));
3105
+ }
2778
3106
  function findHoverCard() {
2779
3107
  for (const card of Array.from(document.body.children)) {
2780
3108
  if (!(card instanceof HTMLElement)) continue;
@@ -2820,46 +3148,201 @@ function mountSessionPresence(options) {
2820
3148
  row.append(dot, text);
2821
3149
  statusHost.append(row);
2822
3150
  }
2823
- const unsubscribeList = sessions.list.subscribe(scheduleScan);
3151
+ const unsubscribeSummary = summary.subscribe(scheduleScan);
2824
3152
  const unsubscribeLocale = locale?.subscribe(scheduleScan);
2825
3153
  const observer = new MutationObserver(scheduleScan);
2826
3154
  observer.observe(document.body, { childList: true, subtree: true });
2827
- const refreshNow = () => {
2828
- if (disposed) return;
2829
- try {
2830
- void sessions.refresh().catch(() => void 0);
2831
- } catch {
2832
- }
2833
- };
2834
- const onVisible = () => {
2835
- if (document.visibilityState === "visible") {
2836
- refreshNow();
2837
- scheduleScan();
2838
- }
2839
- };
2840
- document.addEventListener("visibilitychange", onVisible);
2841
- window.addEventListener("online", onVisible);
2842
3155
  const stateTick = setInterval(scheduleScan, STATE_TICK_MS);
2843
- const refreshTick = setInterval(() => {
2844
- if (document.visibilityState === "visible") refreshNow();
2845
- }, REFRESH_INTERVAL_MS);
2846
- refreshNow();
2847
3156
  scheduleScan();
2848
3157
  return () => {
2849
3158
  disposed = true;
2850
3159
  if (scanTimer !== void 0) clearTimeout(scanTimer);
2851
3160
  clearInterval(stateTick);
2852
- clearInterval(refreshTick);
2853
3161
  observer.disconnect();
2854
- unsubscribeList();
3162
+ unsubscribeSummary();
2855
3163
  unsubscribeLocale?.();
2856
- document.removeEventListener("visibilitychange", onVisible);
2857
- window.removeEventListener("online", onVisible);
2858
3164
  for (const badge of Array.from(document.querySelectorAll(`.${BADGE_CLASS}`))) badge.remove();
2859
3165
  for (const row of Array.from(document.querySelectorAll(`.${HOVER_ROW_CLASS}`))) row.remove();
2860
3166
  };
2861
3167
  }
2862
3168
 
3169
+ // src/schedule-summary.ts
3170
+ var SCHEDULE_PROJECTION_KEY = "userSchedules";
3171
+ function schedulesOfProjection(projectionValues) {
3172
+ const value = projectionValues?.[SCHEDULE_PROJECTION_KEY];
3173
+ if (typeof value !== "object" || value === null) return void 0;
3174
+ const schedules = value.schedules;
3175
+ return Array.isArray(schedules) ? schedules : void 0;
3176
+ }
3177
+ function buildScheduleSummary(source, now) {
3178
+ const summary = /* @__PURE__ */ new Map();
3179
+ for (const [sessionId, row] of Object.entries(source.byId)) {
3180
+ if (row === void 0) continue;
3181
+ const entry = summarizeSchedules(schedulesOfProjection(row.projectionValues));
3182
+ if (entry.count === 0) continue;
3183
+ summary.set(sessionId, { ...entry, state: badgeStateFor(entry.nextAt, now) });
3184
+ }
3185
+ return summary;
3186
+ }
3187
+ function sameScheduleSummary(a, b) {
3188
+ if (a === b) return true;
3189
+ if (a.size !== b.size) return false;
3190
+ for (const [sessionId, left] of a) {
3191
+ const right = b.get(sessionId);
3192
+ if (right === void 0) return false;
3193
+ if (left.count !== right.count || left.pausedCount !== right.pausedCount || left.nextAt !== right.nextAt || left.progressFrom !== right.progressFrom || left.state !== right.state) {
3194
+ return false;
3195
+ }
3196
+ }
3197
+ return true;
3198
+ }
3199
+ var STATE_RANK = { scheduled: 0, urgent: 1, overdue: 2 };
3200
+ function summarizeScheduleGroup(summary, sessionIds) {
3201
+ let sessions = 0;
3202
+ let count = 0;
3203
+ let pausedCount = 0;
3204
+ let nextAt;
3205
+ let state;
3206
+ for (const sessionId of sessionIds) {
3207
+ const entry = summary.get(sessionId);
3208
+ if (entry === void 0) continue;
3209
+ sessions += 1;
3210
+ count += entry.count;
3211
+ pausedCount += entry.pausedCount;
3212
+ if (entry.nextAt !== void 0 && (nextAt === void 0 || entry.nextAt < nextAt)) nextAt = entry.nextAt;
3213
+ if (state === void 0 || STATE_RANK[entry.state] > STATE_RANK[state]) state = entry.state;
3214
+ }
3215
+ return nextAt === void 0 ? { sessions, count, pausedCount, state } : { sessions, count, pausedCount, nextAt, state };
3216
+ }
3217
+
3218
+ // src/schedule-summary-store.ts
3219
+ var SUMMARY_STATE_TICK_MS = 3e4;
3220
+ var SUMMARY_REFRESH_INTERVAL_MS = 6e4;
3221
+ function documentOf() {
3222
+ const doc = globalThis.document;
3223
+ return typeof doc === "object" && doc !== null ? doc : void 0;
3224
+ }
3225
+ function createScheduleSummaryStore(sessions, options = {}) {
3226
+ const now = options.now ?? Date.now;
3227
+ const stateTickMs = options.stateTickMs ?? SUMMARY_STATE_TICK_MS;
3228
+ const refreshIntervalMs = options.refreshIntervalMs ?? SUMMARY_REFRESH_INTERVAL_MS;
3229
+ const autoRefresh = options.autoRefresh ?? true;
3230
+ const canRefresh = options.canRefresh ?? (() => {
3231
+ const doc2 = documentOf();
3232
+ return doc2?.visibilityState === void 0 || doc2.visibilityState === "visible";
3233
+ });
3234
+ const listeners = /* @__PURE__ */ new Set();
3235
+ let snapshot = /* @__PURE__ */ new Map();
3236
+ let disposed = false;
3237
+ const notify = () => {
3238
+ for (const listener of [...listeners]) {
3239
+ try {
3240
+ listener();
3241
+ } catch {
3242
+ }
3243
+ }
3244
+ };
3245
+ const reload = () => {
3246
+ if (disposed) return;
3247
+ const next = buildScheduleSummary(sessions.list.getSnapshot(), now());
3248
+ if (sameScheduleSummary(snapshot, next)) return;
3249
+ snapshot = next;
3250
+ notify();
3251
+ };
3252
+ const unsubscribeList = sessions.list.subscribe(reload);
3253
+ const stateTick = setInterval(reload, stateTickMs);
3254
+ let refreshTick;
3255
+ let onVisible;
3256
+ const doc = documentOf();
3257
+ const target = globalThis;
3258
+ const refreshNow = () => {
3259
+ if (disposed || !canRefresh()) return;
3260
+ try {
3261
+ void sessions.refresh().catch(() => void 0);
3262
+ } catch {
3263
+ }
3264
+ };
3265
+ if (autoRefresh) {
3266
+ onVisible = () => {
3267
+ if (!disposed && canRefresh()) {
3268
+ refreshNow();
3269
+ reload();
3270
+ }
3271
+ };
3272
+ refreshTick = setInterval(() => {
3273
+ if (canRefresh()) refreshNow();
3274
+ }, refreshIntervalMs);
3275
+ doc?.addEventListener?.("visibilitychange", onVisible);
3276
+ target.addEventListener?.("online", onVisible);
3277
+ refreshNow();
3278
+ }
3279
+ reload();
3280
+ const dispose = () => {
3281
+ if (disposed) return;
3282
+ disposed = true;
3283
+ clearInterval(stateTick);
3284
+ if (refreshTick !== void 0) clearInterval(refreshTick);
3285
+ if (onVisible !== void 0) {
3286
+ doc?.removeEventListener?.("visibilitychange", onVisible);
3287
+ target.removeEventListener?.("online", onVisible);
3288
+ }
3289
+ unsubscribeList();
3290
+ listeners.clear();
3291
+ };
3292
+ return {
3293
+ getSnapshot: () => snapshot,
3294
+ subscribe: (listener) => {
3295
+ listeners.add(listener);
3296
+ return () => {
3297
+ listeners.delete(listener);
3298
+ };
3299
+ },
3300
+ reload,
3301
+ dispose
3302
+ };
3303
+ }
3304
+ var activeStore;
3305
+ var registryListeners = /* @__PURE__ */ new Set();
3306
+ function setActiveScheduleSummaryStore(store) {
3307
+ if (activeStore === store) return;
3308
+ activeStore = store;
3309
+ for (const listener of [...registryListeners]) {
3310
+ try {
3311
+ listener();
3312
+ } catch {
3313
+ }
3314
+ }
3315
+ }
3316
+ function getScheduleSummaryStore() {
3317
+ return activeStore;
3318
+ }
3319
+ function subscribeScheduleSummaryStore(listener) {
3320
+ registryListeners.add(listener);
3321
+ return () => {
3322
+ registryListeners.delete(listener);
3323
+ };
3324
+ }
3325
+
3326
+ // src/client/useScheduleSummary.ts
3327
+ var import_react10 = require("react");
3328
+ var EMPTY_SUMMARY = /* @__PURE__ */ new Map();
3329
+ var noopSubscribe2 = () => () => void 0;
3330
+ var emptySnapshot = () => EMPTY_SUMMARY;
3331
+ var missingSnapshot = () => void 0;
3332
+ function useScheduleSummary(store) {
3333
+ const registered = (0, import_react10.useSyncExternalStore)(subscribeScheduleSummaryStore, getScheduleSummaryStore, missingSnapshot);
3334
+ const target = store ?? registered;
3335
+ const subscribe = (0, import_react10.useMemo)(
3336
+ () => target === void 0 ? noopSubscribe2 : (listener) => target.subscribe(listener),
3337
+ [target]
3338
+ );
3339
+ const getSnapshot = (0, import_react10.useMemo)(
3340
+ () => target === void 0 ? emptySnapshot : () => target.getSnapshot(),
3341
+ [target]
3342
+ );
3343
+ return (0, import_react10.useSyncExternalStore)(subscribe, getSnapshot, emptySnapshot);
3344
+ }
3345
+
2863
3346
  // src/client/index.tsx
2864
3347
  var INPUT_RIGHT_SLOT = "conversation.input.right";
2865
3348
  var inject = ["slots", "sessions", "conversation", "locale"];
@@ -2874,9 +3357,18 @@ function apply(ctx) {
2874
3357
  ctx.effect(() => injectStyles(), "dsh-later: styles");
2875
3358
  let detectedVersion = "classic";
2876
3359
  let getClockIconStyle = () => "auto";
3360
+ const summary = createScheduleSummaryStore(sessions);
3361
+ setActiveScheduleSummaryStore(summary);
3362
+ ctx.effect(
3363
+ () => () => {
3364
+ if (getScheduleSummaryStore() === summary) setActiveScheduleSummaryStore(void 0);
3365
+ summary.dispose();
3366
+ },
3367
+ "dsh-later: schedule summary"
3368
+ );
2877
3369
  ctx.effect(
2878
3370
  () => mountSessionPresence({
2879
- sessions,
3371
+ summary,
2880
3372
  ...locale === void 0 ? {} : { locale },
2881
3373
  getClockIconStyle: () => {
2882
3374
  const style = getClockIconStyle();
@@ -2898,6 +3390,11 @@ function apply(ctx) {
2898
3390
  sectionDisplay: "\u663E\u793A\u8BBE\u7F6E",
2899
3391
  sectionTime: "\u65F6\u95F4\u8BBE\u7F6E",
2900
3392
  sectionDelivery: "\u6295\u9012\u8BBE\u7F6E",
3393
+ sectionAdvanced: "\u9AD8\u7EA7\u8BED\u6CD5",
3394
+ betaBadge: "Beta",
3395
+ betaBadgeAria: "Beta \u529F\u80FD\uFF0C\u884C\u4E3A\u53EF\u80FD\u968F\u7248\u672C\u8C03\u6574",
3396
+ advancedSyntaxLabel: "\u542F\u7528\u591A\u6B21\u5199\u6CD5\uFF08+1h*3 / 15:32+16:32\uFF09",
3397
+ advancedSyntaxHint: "\u8BA9 /later \u4E0E /schedule \u652F\u6301\u4E00\u6B21\u5199\u591A\u6761\uFF1A+1h*3\uFF08\u7B49\u95F4\u9694 3 \u6B21\uFF09\u3001+1h+1h+0.5h\uFF08\u76F8\u5BF9\u5E8F\u5217\uFF09\u300115:32+16:32 \u4E0E 9:00+1d\uFF08\u951A\u70B9\u5E8F\u5217\uFF0C\u6309\u5899\u949F\u63A8\u8FDB\uFF0C\u8DE8\u590F\u4EE4\u65F6\u4E0D\u6F02\uFF09\u3002\u6BCF\u6761\u90FD\u662F\u72EC\u7ACB\u4EFB\u52A1\uFF0C\u53EF\u5355\u72EC\u53D6\u6D88\u6216\u7F16\u8F91\uFF1B\u5355\u6B21\u6700\u591A 10 \u6761\u3002\u6807\u8BB0\u4E3A Beta\uFF1A\u5C55\u5F00\u540E\u7684\u89C2\u611F\u4E0E\u6761\u6570\u4E0A\u9650\u4ECD\u9700\u89C2\u5BDF\u3002",
2901
3398
  showButtonLabel: "\u663E\u793A\u5B9A\u65F6\u6309\u94AE",
2902
3399
  showButtonHint: "\u5173\u95ED\u540E\u8F93\u5165\u6846\u53F3\u4FA7\u4E0D\u518D\u663E\u793A\u65F6\u949F\u6309\u94AE\uFF1B\u5DF2\u521B\u5EFA\u7684\u63D0\u9192\u4ECD\u4F1A\u7167\u5E38\u89E6\u53D1\uFF0C\u5E76\u5728\u8F93\u5165\u6846\u4E0B\u65B9\u7684\u5F85\u53D1\u9001\u5217\u8868\u4E2D\u663E\u793A\u3002",
2903
3400
  maxSchedulesLabel: "\u5355\u4F1A\u8BDD\u4EFB\u52A1\u4E0A\u9650",
@@ -2935,7 +3432,10 @@ function apply(ctx) {
2935
3432
  discard: "\u653E\u5F03\u4FEE\u6539",
2936
3433
  saveFailed: "\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002",
2937
3434
  loading: "\u52A0\u8F7D\u4E2D\u2026",
2938
- unavailable: "\u8BBE\u7F6E\u6682\u4E0D\u53EF\u7528\u3002"
3435
+ unavailable: "\u8BBE\u7F6E\u6682\u4E0D\u53EF\u7528\u3002",
3436
+ sectionDisplayToggle: "\u663E\u793A\u8BBE\u7F6E",
3437
+ showInSettings: "\u5728\u8BBE\u7F6E\u4E2D\u663E\u793A",
3438
+ showInSettingsHint: "\u5728\u8BBE\u7F6E\u5BF9\u8BDD\u6846\u4E2D\u663E\u793A\u672C\u63D2\u4EF6\u7684\u8BBE\u7F6E\u9875\uFF08\u5237\u65B0\u9875\u9762\u540E\u751F\u6548\uFF09"
2939
3439
  },
2940
3440
  en: {
2941
3441
  title: "Session Scheduler",
@@ -2947,6 +3447,11 @@ function apply(ctx) {
2947
3447
  sectionDisplay: "Display",
2948
3448
  sectionTime: "Time windows",
2949
3449
  sectionDelivery: "Delivery",
3450
+ sectionAdvanced: "Advanced syntax",
3451
+ betaBadge: "Beta",
3452
+ betaBadgeAria: "Beta feature; behaviour may change between versions",
3453
+ advancedSyntaxLabel: "Multi-shot syntax (+1h*3 / 15:32+16:32)",
3454
+ advancedSyntaxHint: "Let /later and /schedule create several tasks at once: +1h*3 (same interval N times), +1h+1h+0.5h (relative chain), 15:32+16:32 and 9:00+1d (anchored on a wall clock \u2014 it keeps the clock time across DST). Each occurrence is an independent task you can cancel or edit on its own; up to 10 per command. Marked Beta: the expanded result and the 10-task cap still need real-world observation.",
2950
3455
  showButtonLabel: "Show schedule button",
2951
3456
  showButtonHint: "Hide the clock button on the input bar. Existing reminders keep firing and stay visible in the dock below the composer.",
2952
3457
  maxSchedulesLabel: "Max schedules per session",
@@ -2984,7 +3489,10 @@ function apply(ctx) {
2984
3489
  discard: "Discard",
2985
3490
  saveFailed: "Save failed, please try again.",
2986
3491
  loading: "Loading\u2026",
2987
- unavailable: "Settings unavailable."
3492
+ unavailable: "Settings unavailable.",
3493
+ sectionDisplayToggle: "Display",
3494
+ showInSettings: "Show in settings",
3495
+ showInSettingsHint: "Show this plugin's settings page in the settings dialog (refresh to take effect)"
2988
3496
  }
2989
3497
  }), "dsh-later: locale");
2990
3498
  }
@@ -3029,11 +3537,17 @@ function apply(ctx) {
3029
3537
  );
3030
3538
  };
3031
3539
  const cardT = locale === void 0 ? void 0 : locale.bind(NS);
3032
- const cardDeclaration = (slot, scope) => ({
3540
+ const cardDeclaration = (slot, scope, surface = "detail") => ({
3033
3541
  name: slot,
3542
+ id: SETTINGS_NS,
3543
+ order: 30,
3034
3544
  key: SETTINGS_NS,
3035
3545
  locale: NS,
3036
- inject: () => ({ scope, t: cardT })
3546
+ label: () => {
3547
+ const active = locale === void 0 ? "zh" : locale.getSnapshot().active;
3548
+ return active === "en" ? "Session Scheduler" : "\u5B9A\u65F6\u63D0\u9192";
3549
+ },
3550
+ inject: () => ({ scope, t: cardT, surface })
3037
3551
  });
3038
3552
  ctx.inject(["settingsScope"], () => {
3039
3553
  const binder = ctx.get("settingsScope");
@@ -3049,10 +3563,18 @@ function apply(ctx) {
3049
3563
  };
3050
3564
  mountComposer(scope);
3051
3565
  if (cardT === void 0) return;
3052
- ctx.slots.inject(
3053
- "settings.plugin.item",
3054
- () => ctx.slots.register(cardDeclaration("settings.plugin.item", scope), SchedulerSettingsCard)
3566
+ const SHOW_SETTINGS_KEY = "dsh-later:show-settings-section";
3567
+ const showSettingsSection = typeof localStorage !== "undefined" && localStorage.getItem(SHOW_SETTINGS_KEY) === "true";
3568
+ if (!showSettingsSection) return;
3569
+ const disposeSection = ctx.slots.inject(
3570
+ "settings.section",
3571
+ () => ctx.slots.register(cardDeclaration("settings.section", scope, "settings"), SchedulerSettingsCard)
3055
3572
  );
3573
+ if (typeof disposeSection === "function") {
3574
+ ctx.effect(() => () => {
3575
+ disposeSection();
3576
+ });
3577
+ }
3056
3578
  });
3057
3579
  ctx.inject(["configForms"], () => {
3058
3580
  const forms = ctx.get("configForms");
@@ -3073,11 +3595,23 @@ function apply(ctx) {
3073
3595
  [ENTRY_ID],
3074
3596
  () => ctx.slots.inject(
3075
3597
  "plugins.bundle.config",
3076
- () => ctx.slots.register(cardDeclaration("plugins.bundle.config", scope), SchedulerSettingsCard)
3598
+ () => ctx.slots.register(cardDeclaration("plugins.bundle.config", scope, "detail"), SchedulerSettingsCard)
3077
3599
  )
3078
3600
  ),
3079
3601
  "dsh-later: plugins-page card"
3080
3602
  );
3603
+ const SHOW_SETTINGS_KEY = "dsh-later:show-settings-section";
3604
+ const showSettingsSection = typeof localStorage !== "undefined" && localStorage.getItem(SHOW_SETTINGS_KEY) === "true";
3605
+ if (!showSettingsSection) return;
3606
+ const disposeSection = ctx.slots.inject(
3607
+ "settings.section",
3608
+ () => ctx.slots.register(cardDeclaration("settings.section", scope, "settings"), SchedulerSettingsCard)
3609
+ );
3610
+ if (typeof disposeSection === "function") {
3611
+ ctx.effect(() => () => {
3612
+ disposeSection();
3613
+ });
3614
+ }
3081
3615
  });
3082
3616
  }
3083
3617