dsh-later 1.0.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 ADDED
@@ -0,0 +1,2384 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-later",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ "use strict";
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __export = (target, all) => {
12
+ for (var name2 in all)
13
+ __defProp(target, name2, { get: all[name2], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
+
25
+ // src/client/index.tsx
26
+ var index_exports = {};
27
+ __export(index_exports, {
28
+ apply: () => apply,
29
+ inject: () => inject,
30
+ name: () => name
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/domain.ts
35
+ var name = "dsh-later";
36
+
37
+ // src/client/styles.ts
38
+ var STYLE_ID = "dsh-later-styles";
39
+ var COMPONENTS = [
40
+ "/* ==================== \u8F93\u5165\u884C\u5BB9\u5668 ==================== */",
41
+ ".ss-sched-row { display: inline-flex; align-items: center; gap: 4px; flex: none; }",
42
+ "",
43
+ "/* ==================== \u23F0 \u6309\u94AE\uFF08\u8F93\u5165\u6846\u53F3\u4FA7\u5DE5\u5177\u6309\u94AE\uFF09 ==================== */",
44
+ "/* \u5BF9\u9F50\u5B98\u65B9 ui-conversation trigger\uFF1A28\xD728\u3001secondary \u8272\u3001hover interactive-bg-hover */",
45
+ ".ss-sched-btn {",
46
+ " display: inline-grid; place-items: center; width: 28px; height: 28px;",
47
+ " border: none; border-radius: 999px; cursor: pointer; padding: 0;",
48
+ " color: var(--dsw-alias-label-secondary); background: transparent;",
49
+ " transition: background var(--ds-transition-duration-fast) var(--ds-ease-in-out);",
50
+ "}",
51
+ ".ss-sched-btn:hover:not(:disabled) {",
52
+ " background: var(--dsw-alias-interactive-bg-hover);",
53
+ "}",
54
+ ".ss-sched-btn:focus-visible {",
55
+ " outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px;",
56
+ "}",
57
+ ".ss-sched-btn:disabled {",
58
+ " cursor: default; color: var(--dsw-alias-label-dimmed); opacity: 0.4;",
59
+ "}",
60
+ ".ss-sched-btn.ss-active {",
61
+ " background: var(--dsw-alias-interactive-bg-active);",
62
+ "}",
63
+ "",
64
+ "/* ==================== \u5F85\u53D1\u9001\u63D0\u9192 dock\uFF08\u50CF\u7D20\u7EA7\u5BF9\u9F50 QueueDock\uFF1B\u884C\u5185\u4FDD\u7559 boss \u8FDB\u5EA6\u6761\uFF09 ==================== */",
65
+ "/* \u5916\u58F3\u51E0\u4F55\u4E0E QueueDock .dock \u4E00\u81F4\uFF08\u542B\u8D1F\u4E0B\u8FB9\u8DDD\u8D34\u4F4F composer \u6808\uFF09\u3002 */",
66
+ ".ss-dock-root {",
67
+ " box-sizing: border-box; flex: none;",
68
+ " width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance)",
69
+ " - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));",
70
+ " max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));",
71
+ " margin: 0 auto calc(0px - var(--dsh-composer-stack-gap) - 3px);",
72
+ " padding: 0 var(--dsh-composer-dock-inset);",
73
+ "}",
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 */",
75
+ ".ss-dock-body {",
76
+ " position: relative; overflow: hidden; width: 100%; padding: 2px 0;",
77
+ " background: var(--dsw-specific-tip);",
78
+ " border-radius: 12px 12px 0 0;",
79
+ " --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);",
80
+ " --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);",
81
+ "}",
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;",
85
+ "}",
86
+ ".ss-dock-header {",
87
+ " box-sizing: border-box; display: flex; align-items: center; gap: 10px;",
88
+ " width: 100%; height: 36px; padding: 4px 12px;",
89
+ " background: none; border: none; border-radius: 8px; cursor: pointer; text-align: left;",
90
+ " color: var(--dsw-alias-label-primary);",
91
+ "}",
92
+ ".ss-dock-header:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }",
93
+ ".ss-dock-lead { display: grid; place-items: center; color: var(--dsw-alias-label-tertiary); flex: none; }",
94
+ ".ss-dock-count {",
95
+ " flex: auto; min-width: 0;",
96
+ " font-family: Inter, var(--dsw-font-family);",
97
+ " font-size: 13px; font-weight: 500; line-height: 24px;",
98
+ "}",
99
+ ".ss-dock-chevron { display: grid; place-items: center; width: 14px; height: 14px; color: var(--dsw-alias-label-tertiary); flex: none; }",
100
+ ".ss-dock-chevron.ss-open { transform: rotate(180deg); }",
101
+ ".ss-dock-list {",
102
+ " margin: 0; padding: 0; list-style: none;",
103
+ " max-height: 180px; overflow-y: auto;",
104
+ "}",
105
+ ".ss-dock-row {",
106
+ " box-sizing: border-box; display: flex; align-items: center; gap: 10px;",
107
+ " width: 100%; min-height: 36px; padding: 4px 5px 4px 12px; border-radius: 8px;",
108
+ "}",
109
+ ".ss-dock-row + .ss-dock-row { box-shadow: inset 0 1px 0 var(--dsw-alias-border-l1); }",
110
+ "/* QueueDock \u7684 preview \u662F\u5355\u884C\u6587\u672C\uFF1B\u540C\u4F4D\u653E\u300C\u65F6\u95F4 \xB7 \u5012\u8BA1\u65F6 \xB7 \u5185\u5BB9\u300D+ boss \u8FDB\u5EA6\u6761\uFF08\u4FDD\u7559\u63D2\u4EF6\u7279\u6027\uFF09\u3002 */",
111
+ ".ss-dock-preview {",
112
+ " flex: auto; min-width: 0; display: flex; flex-direction: column; gap: 4px;",
113
+ " font: var(--dsw-font-xs-13); font-family: Inter, var(--dsw-font-family);",
114
+ "}",
115
+ ".ss-dock-row-line { display: flex; align-items: center; gap: 8px; min-width: 0; }",
116
+ ".ss-dock-row-time { flex: none; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }",
117
+ ".ss-dock-row-countdown { flex: none; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; }",
118
+ ".ss-dock-row-countdown.ss-due { color: var(--dsw-alias-state-warn-secondary); }",
119
+ ".ss-dock-row-prompt {",
120
+ " flex: 1; min-width: 0; color: var(--dsw-alias-label-primary-dimmed);",
121
+ " white-space: nowrap; overflow: hidden; text-overflow: ellipsis;",
122
+ "}",
123
+ "/* boss \u8840\u6761\uFF08\u4FDD\u7559\uFF09\uFF1A\u5355\u6839\u957F\u7BA1 + \u7B49\u6BD4\u5206\u9694\u7EBF\uFF08\u6BCF\u6BB5 1h\uFF09\u3002 */",
124
+ ".ss-dock-bars { display: block; }",
125
+ ".ss-dock-bar {",
126
+ " position: relative; height: 6px; border-radius: 999px; overflow: hidden;",
127
+ " background: var(--dsw-alias-line-secondary);",
128
+ " --ss-bar-divider: color-mix(in srgb, var(--dsw-alias-label-primary) 35%, transparent);",
129
+ "}",
130
+ ".ss-dock-bars-compact .ss-dock-bar { height: 4px; }",
131
+ ".ss-dock-bar-fill {",
132
+ " position: relative; z-index: 1;",
133
+ " height: 100%; border-radius: 999px; background: var(--dsw-alias-brand-primary);",
134
+ " transition: width 1s linear;",
135
+ "}",
136
+ "/* \u5206\u9694\u7EBF\uFF1Aabsolute \u8DE8\u6574\u4E2A bar \u753B N-1 \u6761\u7B49\u6BD4\u7AD6\u7EBF\uFF0C\u6E32\u67D3\u5728 fill \u4E4B\u524D\uFF08fill \u7528 z-index:1 \u76D6\u4F4F\u300C\u5DF2\u586B\u5145\u6BB5\u300D\u4E0A\u7684\u523B\u5EA6\uFF09\u3002 */",
137
+ ".ss-dock-bar-divider {",
138
+ " position: absolute; inset: 0; pointer-events: none;",
139
+ " background-repeat: no-repeat;",
140
+ "}",
141
+ ".ss-dock-actions { display: flex; align-items: center; gap: 10px; flex: none; }",
142
+ ".ss-dock-action {",
143
+ " display: grid; place-items: center; width: 28px; height: 28px; padding: 0; flex: none;",
144
+ " border: none; border-radius: 999px; background: none; cursor: pointer;",
145
+ " color: var(--dsw-alias-label-tertiary);",
146
+ "}",
147
+ ".ss-dock-action:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }",
148
+ ".ss-dock-action:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }",
149
+ ".ss-dock-action:disabled { cursor: default; opacity: 0.45; }",
150
+ "/* \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 */",
151
+ ".ss-dock-action-steer.ss-done { color: var(--dsw-alias-state-success-primary); }",
152
+ ".ss-dock-steer-err {",
153
+ " color: var(--dsw-alias-state-warn-secondary); font-size: 12px; line-height: 16px;",
154
+ "}",
155
+ "/* \u884C\u5185\u7F16\u8F91\uFF08QueueDock .editor \u540C\u6B3E\u8F93\u5165\u6846\uFF09\u3002 */",
156
+ ".ss-dock-edit-input {",
157
+ " box-sizing: border-box; flex: auto; min-width: 0; height: 28px; padding: 0 8px;",
158
+ " font: var(--dsw-font-xs-13); font-family: Inter, var(--dsw-font-family);",
159
+ " color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base);",
160
+ " border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; outline: none;",
161
+ "}",
162
+ ".ss-dock-edit-input:focus { border-color: var(--dsw-alias-state-business-primary); }",
163
+ ".ss-dock-row-save {",
164
+ " flex: none; font-size: 12px; line-height: 20px; padding: 0 8px; border-radius: 6px;",
165
+ " border: 1px solid var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary);",
166
+ " background: transparent; cursor: pointer;",
167
+ "}",
168
+ ".ss-dock-row-save:disabled { opacity: 0.45; cursor: default; }",
169
+ ".ss-dock-edit-err {",
170
+ " flex-basis: 100%; color: var(--dsw-alias-state-error-secondary); font-size: 12px; line-height: 16px;",
171
+ "}",
172
+ ".ss-dock-editing { flex-wrap: wrap; }",
173
+ ".ss-toast-host {",
174
+ " position: fixed; right: 16px; bottom: 96px; z-index: 300;",
175
+ " display: flex; flex-direction: column; gap: 8px; pointer-events: none;",
176
+ "}",
177
+ ".ss-toast {",
178
+ " pointer-events: auto; max-width: min(360px, calc(100vw - 32px));",
179
+ " display: flex; align-items: center; gap: 8px;",
180
+ " padding: 9px 12px; border-radius: 10px;",
181
+ " background: var(--dsw-specific-menu); color: var(--dsw-alias-label-primary);",
182
+ " border: 1px solid var(--dsw-alias-line-primary);",
183
+ " box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);",
184
+ " font-size: 13px; line-height: 18px;",
185
+ " animation: ss-toast-in 180ms var(--ds-ease-out);",
186
+ "}",
187
+ ".ss-toast-icon { color: var(--dsw-alias-brand-primary); flex: none; }",
188
+ ".ss-toast-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
189
+ "@keyframes ss-toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }",
190
+ "",
191
+ "/* ==================== \u9762\u677F ==================== */",
192
+ "/* \u5BF9\u9F50\u5B98\u65B9 popover panel\uFF1Az-index 100\u3001border-inverted\u3001radius 12\u3001\u5B57\u53F7 12\u3001secondary \u6587\u5B57 */",
193
+ ".ss-backdrop {",
194
+ " position: fixed; inset: 0; z-index: 100; background: transparent;",
195
+ "}",
196
+ ".ss-panel {",
197
+ " position: fixed; z-index: 101;",
198
+ " width: min(340px, calc(100vw - 24px));",
199
+ " background: var(--dsw-specific-menu);",
200
+ " color: var(--dsw-alias-label-secondary);",
201
+ " border: 1px solid var(--dsw-alias-border-inverted); border-radius: 12px;",
202
+ " box-shadow: var(--dsw-shadow-lv3); padding: 12px; box-sizing: border-box;",
203
+ " font-size: 12px; line-height: 20px; font-family: var(--dsw-font-family);",
204
+ "}",
205
+ ".ss-panel-header {",
206
+ " display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;",
207
+ "}",
208
+ ".ss-panel-title {",
209
+ " font-weight: 500; font-size: 13px;",
210
+ " display: inline-flex; align-items: center; gap: 6px;",
211
+ " color: var(--dsw-alias-label-primary);",
212
+ "}",
213
+ ".ss-panel-close {",
214
+ " width: 24px; height: 24px; border: none; background: transparent;",
215
+ " cursor: pointer; color: var(--dsw-alias-label-tertiary);",
216
+ " border-radius: 999px; display: grid; place-items: center; padding: 0;",
217
+ "}",
218
+ ".ss-panel-close:hover {",
219
+ " background: var(--dsw-alias-interactive-bg-hover);",
220
+ " color: var(--dsw-alias-label-secondary);",
221
+ "}",
222
+ ".ss-panel-close:focus-visible {",
223
+ " outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px;",
224
+ "}",
225
+ "",
226
+ "/* ==================== \u5FEB\u6377\u82AF\u7247\uFF08\u5355\u89C6\u56FE\uFF0C\u65E0 tab\uFF09 ==================== */",
227
+ ".ss-quick-grid {",
228
+ " display: grid; grid-template-columns: 1fr 1fr; gap: 6px;",
229
+ "}",
230
+ ".ss-quick-chip {",
231
+ " height: 30px; padding: 0 10px;",
232
+ " border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px;",
233
+ " background: transparent; color: var(--dsw-alias-label-secondary);",
234
+ " cursor: pointer; font-size: 12px; line-height: 1.5; font-family: var(--dsw-font-family);",
235
+ " transition: color var(--ds-transition-duration-fast) var(--ds-ease-in-out),",
236
+ " border-color var(--ds-transition-duration-fast) var(--ds-ease-in-out),",
237
+ " background var(--ds-transition-duration-fast) var(--ds-ease-in-out);",
238
+ "}",
239
+ ".ss-quick-chip:hover {",
240
+ " color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-dimmed);",
241
+ "}",
242
+ ".ss-quick-chip.ss-active {",
243
+ " background: var(--dsw-alias-interactive-bg-active);",
244
+ " color: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary);",
245
+ "}",
246
+ ".ss-quick-chip:focus-visible {",
247
+ " outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px;",
248
+ "}",
249
+ ".ss-custom-row {",
250
+ " display: flex; gap: 6px; margin-top: 8px;",
251
+ "}",
252
+ ".ss-custom-row .ss-input { flex: 1; min-width: 0; }",
253
+ ".ss-tasklist-head { margin-top: 10px; }",
254
+ "",
255
+ "/* ==================== \u8868\u5355\u5B57\u6BB5 ==================== */",
256
+ "/* \u5BF9\u9F50\u5B98\u65B9 fields.module.css\uFF1Acolumn \u5E03\u5C40\u3001label \u5728\u4E0A 13px/500\u3001\u5B57\u6BB5\u95F4 border-top \u5206\u9694 */",
257
+ ".ss-field {",
258
+ " display: flex; flex-direction: column; gap: 6px; padding: 12px 0;",
259
+ "}",
260
+ ".ss-field + .ss-field { border-top: 1px solid var(--dsw-alias-border-l2); }",
261
+ ".ss-input {",
262
+ " height: 34px; padding: 0 12px;",
263
+ " border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px;",
264
+ " background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary);",
265
+ " font-size: 13px; line-height: 1.5; font-family: var(--dsw-font-family);",
266
+ " flex: 1; min-width: 0; width: 100%; box-sizing: border-box;",
267
+ "}",
268
+ ".ss-input:focus-visible {",
269
+ " border-color: var(--dsw-alias-brand-primary); outline: none;",
270
+ "}",
271
+ ".ss-input:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }",
272
+ ".ss-input.ss-inputInvalid { border-color: var(--dsw-alias-label-error); }",
273
+ "",
274
+ "/* ==================== \u4EFB\u52A1\u5217\u8868 ==================== */",
275
+ ".ss-tasks { max-height: 160px; overflow-y: auto; margin-bottom: 8px; }",
276
+ ".ss-task {",
277
+ " display: flex; align-items: center; gap: 8px; padding: 5px 6px;",
278
+ " border-radius: 4px; font-size: 12px;",
279
+ " transition: background var(--ds-transition-duration-fast) var(--ds-ease-in-out);",
280
+ "}",
281
+ ".ss-task:hover { background: var(--dsw-alias-interactive-bg-hover); }",
282
+ ".ss-dot { width: 6px; height: 6px; border-radius: 999px; flex: none; }",
283
+ ".ss-dot.scheduled { background: var(--dsw-alias-brand-primary); }",
284
+ ".ss-dot.overdue { background: var(--dsw-alias-state-warn-secondary); }",
285
+ ".ss-dot.delivered { background: var(--dsw-alias-state-success-primary); }",
286
+ ".ss-dot.cancelled { background: var(--dsw-alias-label-dimmed); }",
287
+ ".ss-task-status {",
288
+ " display: inline-block; margin-left: 4px; padding: 0 5px;",
289
+ " border-radius: 999px; font-size: 10px; font-weight: 500;",
290
+ " color: var(--dsw-alias-state-warn-label);",
291
+ " background: var(--dsw-alias-state-warn-tertiary);",
292
+ "}",
293
+ ".ss-task-main { flex: 1; min-width: 0; }",
294
+ ".ss-task-time { color: var(--dsw-alias-label-tertiary); font-size: 11px; }",
295
+ ".ss-task-prompt {",
296
+ " color: var(--dsw-alias-label-primary);",
297
+ " overflow: hidden; text-overflow: ellipsis; white-space: nowrap;",
298
+ "}",
299
+ ".ss-task.cancelled .ss-task-prompt,",
300
+ ".ss-task.cancelled .ss-task-time {",
301
+ " text-decoration: line-through; color: var(--dsw-alias-label-dimmed);",
302
+ "}",
303
+ ".ss-task-del {",
304
+ " flex: none; width: 20px; height: 20px; border: none; background: transparent;",
305
+ " cursor: pointer; color: var(--dsw-alias-label-tertiary);",
306
+ " border-radius: 999px; display: grid; place-items: center; padding: 0;",
307
+ " opacity: 0;",
308
+ " transition: opacity var(--ds-transition-duration-fast) var(--ds-ease-in-out),",
309
+ " background var(--ds-transition-duration-fast) var(--ds-ease-in-out),",
310
+ " color var(--ds-transition-duration-fast) var(--ds-ease-in-out);",
311
+ "}",
312
+ ".ss-task:hover .ss-task-del { opacity: 1; }",
313
+ ".ss-task-del:hover {",
314
+ " background: var(--dsw-alias-interactive-bg-hover-danger);",
315
+ " color: var(--dsw-alias-state-error-secondary);",
316
+ "}",
317
+ ".ss-task-del:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); opacity: 1; }",
318
+ ".ss-empty {",
319
+ " color: var(--dsw-alias-label-tertiary); text-align: center;",
320
+ " padding: 10px 0; font-size: 12px;",
321
+ "}",
322
+ "",
323
+ "/* ==================== \u5E95\u90E8\u64CD\u4F5C\u680F ==================== */",
324
+ "/* \u5BF9\u9F50\u5B98\u65B9\u6309\u94AE\u89C4\u683C\uFF1Aradius 8\u3001padding 5px 14px\u300113px\u3001disabled opacity .4 */",
325
+ ".ss-footer { display: flex; gap: 8px; align-items: center; }",
326
+ ".ss-confirm {",
327
+ " flex: 1; appearance: none; border: 1px solid transparent; border-radius: 8px;",
328
+ " padding: 5px 14px; cursor: pointer;",
329
+ " background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3);",
330
+ " font-size: 13px; line-height: 1.5; font-weight: 500;",
331
+ " transition: background var(--ds-transition-duration-fast) var(--ds-ease-in-out);",
332
+ " font-family: var(--dsw-font-family);",
333
+ "}",
334
+ ".ss-confirm:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); }",
335
+ ".ss-confirm:disabled { opacity: 0.4; cursor: default; }",
336
+ ".ss-confirm:focus-visible {",
337
+ " outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px;",
338
+ "}",
339
+ ".ss-clear {",
340
+ " flex: none; appearance: none; border: 1px solid var(--dsw-alias-border-l2);",
341
+ " border-radius: 8px; padding: 5px 14px; background: none;",
342
+ " color: var(--dsw-alias-label-secondary); cursor: pointer;",
343
+ " font-size: 13px; line-height: 1.5; font-family: var(--dsw-font-family);",
344
+ "}",
345
+ ".ss-clear:hover:not(:disabled) {",
346
+ " color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-dimmed);",
347
+ "}",
348
+ ".ss-clear:disabled { opacity: 0.4; cursor: default; }",
349
+ ".ss-clear:focus-visible {",
350
+ " outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px;",
351
+ "}",
352
+ "",
353
+ "/* ==================== \u9519\u8BEF\u63D0\u793A ==================== */",
354
+ "/* \u5BF9\u9F50\u5B98\u65B9\uFF1A\u7EAF\u6587\u5B57\u8272\u3001\u65E0\u80CC\u666F\u8272\u5757\uFF1Bhint 12px */",
355
+ ".ss-error {",
356
+ " color: var(--dsw-alias-state-error-primary); font-size: 12px;",
357
+ " line-height: 1.5; margin: 0;",
358
+ "}",
359
+ ".ss-hint { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 1.5; margin: 0; }",
360
+ "",
361
+ "/* ==================== \u4FA7\u680F\u4F1A\u8BDD\u884C\u300C\u5B9A\u65F6\u72B6\u6001\u300Dbadge\uFF08docs/ui/09 \u8DEF\u7EBF B\uFF09 ==================== */",
362
+ "/* \u7D2B\u4E3A\u5B9A\u65F6\u4E13\u5C5E\uFF08\u6D45\u6DF1\u901A\u7528\uFF0C\u5BF9\u767D/\u6DF1\u5E95\u5BF9\u6BD4\u5747 \u22653:1\uFF09\uFF1Burgent/overdue \u590D\u7528\u5BBF\u4E3B warn/error \u522B\u540D\u3002 */",
363
+ ".ss-presence { display: inline-flex; flex: none; color: #8b5cf6; }",
364
+ ".ss-presence-beside { margin-left: 4px; }",
365
+ ".ss-presence rect { fill: currentColor; }",
366
+ ".ss-presence .ss-presence-ring { opacity: 0.8; animation: ss-presence-breathe 2.4s var(--ds-ease-in-out, ease-in-out) infinite; }",
367
+ ".ss-presence .ss-presence-hand { opacity: 1; }",
368
+ '.ss-presence[data-state="urgent"] { color: var(--dsw-alias-state-warn-secondary); }',
369
+ '.ss-presence[data-state="urgent"] .ss-presence-ring { animation-name: ss-presence-pulse; animation-duration: 0.9s; }',
370
+ '.ss-presence[data-state="overdue"] { color: var(--dsw-alias-state-error-primary); }',
371
+ '.ss-presence[data-state="overdue"] .ss-presence-ring { animation: none; }',
372
+ "@keyframes ss-presence-breathe { 0%, 100% { opacity: 0.65; } 50% { opacity: 1; } }",
373
+ "@keyframes ss-presence-pulse { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }",
374
+ "@media (prefers-reduced-motion: reduce) { .ss-presence .ss-presence-ring { animation: none; } }",
375
+ "",
376
+ "/* ==================== \u8BBE\u7F6E\u9875\uFF08\u5B9A\u65F6\u63D0\u9192\u5361\u7247\uFF09 ==================== */",
377
+ "/* \u89C6\u89C9\u5BF9\u9F50\u5B98\u65B9 PluginCard\uFF08dsh-client-ui-settings-plugins \u79C1\u6709\u7EC4\u4EF6\uFF0C\u540C\u6784\u81EA\u5B9E\u73B0\uFF0C",
378
+ " \u53C2\u8003 dsh-wakatime \u540C\u6B3E\u505A\u6CD5\uFF09\uFF1A<li> \u5916\u6846\u3001header \u6574\u5361\u6309\u94AE\u3001chevron\u3001\u6298\u53E0 body\u3001",
379
+ " footer \u653E\u5F03/\u4FDD\u5B58\u3002\u7C7B\u540D\u7EDF\u4E00 ss-card- \u524D\u7F00\uFF0C\u907F\u514D\u4E0E\u9762\u677F\uFF08ss-footer/ss-field-*\uFF09\u51B2\u7A81\u3002 */",
380
+ ".ss-card {",
381
+ " border: 1px solid var(--dsw-alias-border-l2);",
382
+ " background: var(--dsw-alias-bg-layer-3);",
383
+ " border-radius: 12px; list-style: none;",
384
+ " transition: border-color .16s, background .16s;",
385
+ "}",
386
+ ".ss-card:hover { border-color: var(--dsw-alias-label-dimmed); }",
387
+ ".ss-cardOpen { background: var(--dsw-alias-bg-layer-2); border-color: var(--dsw-alias-label-dimmed); }",
388
+ ".ss-card-header {",
389
+ " appearance: none; width: 100%; font: inherit; color: inherit; text-align: left;",
390
+ " cursor: pointer; background: none; border: 0; border-radius: 12px;",
391
+ " align-items: center; gap: 12px; padding: 14px 16px; display: flex;",
392
+ "}",
393
+ ".ss-card-header:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }",
394
+ ".ss-card-headText { flex-direction: column; flex: 1; gap: 4px; min-width: 0; display: flex; }",
395
+ ".ss-card-name { color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600; line-height: 1.4; }",
396
+ ".ss-card-description { color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.5; }",
397
+ ".ss-card-chevron { color: var(--dsw-alias-label-tertiary); flex: none; transition: transform .16s; }",
398
+ ".ss-card-chevronOpen { transform: rotate(180deg); }",
399
+ ".ss-card-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding-bottom: 8px; }",
400
+ ".ss-card-readOnly { color: var(--dsw-alias-label-tertiary); margin: 12px 0 0; font-size: 12px; line-height: 1.5; }",
401
+ ".ss-card-pending {",
402
+ " white-space: nowrap; background: var(--dsw-alias-bg-module-platform);",
403
+ " color: var(--dsw-alias-label-secondary); border-radius: 999px; flex: none;",
404
+ " padding: 1px 8px; font-size: 11px; font-weight: 500; line-height: 17px;",
405
+ "}",
406
+ ".ss-card-fieldHead { align-items: center; gap: 8px; display: flex; }",
407
+ ".ss-card-label {",
408
+ " min-width: 0; color: var(--dsw-alias-label-primary); flex: 1;",
409
+ " font-size: 13px; font-weight: 500; line-height: 1.5;",
410
+ "}",
411
+ ".ss-card-badges { align-items: center; gap: 8px; display: inline-flex; }",
412
+ ".ss-card-badge {",
413
+ " white-space: nowrap; background: var(--dsw-alias-bg-module-platform);",
414
+ " color: var(--dsw-alias-label-secondary); border-radius: 999px;",
415
+ " padding: 1px 8px; font-size: 11px; font-weight: 500; line-height: 17px;",
416
+ "}",
417
+ ".ss-card-reset {",
418
+ " font: inherit; color: var(--dsw-alias-label-secondary); cursor: pointer;",
419
+ " background: none; border: none; padding: 0; font-size: 12px; line-height: 1.5;",
420
+ "}",
421
+ ".ss-card-reset:hover:not(:disabled) { color: var(--dsw-alias-label-primary); }",
422
+ ".ss-card-reset:disabled { cursor: default; }",
423
+ ".ss-card-invalid { color: var(--dsw-alias-label-error); margin: 0; font-size: 12px; line-height: 1.5; }",
424
+ ".ss-card-footer {",
425
+ " border-top: 1px solid var(--dsw-alias-border-l2);",
426
+ " justify-content: flex-end; align-items: center; gap: 8px;",
427
+ " padding: 12px 0 4px; display: flex;",
428
+ "}",
429
+ ".ss-card-failed {",
430
+ " min-width: 0; color: var(--dsw-alias-label-error); flex: 1; margin: 0;",
431
+ " font-size: 12px; line-height: 1.5;",
432
+ "}",
433
+ ".ss-card-discard, .ss-card-save {",
434
+ " appearance: none; font: inherit; cursor: pointer;",
435
+ " border: 1px solid transparent; border-radius: 8px;",
436
+ " padding: 5px 14px; font-size: 13px; line-height: 1.5;",
437
+ "}",
438
+ ".ss-card-discard { border-color: var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); background: none; }",
439
+ ".ss-card-discard:hover:not(:disabled) { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-dimmed); }",
440
+ ".ss-card-save { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3); }",
441
+ ".ss-card-save:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); }",
442
+ ".ss-card-discard:disabled, .ss-card-save:disabled { opacity: 0.4; cursor: default; }",
443
+ ".ss-card-discard:focus-visible, .ss-card-save:focus-visible {",
444
+ " outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px;",
445
+ "}"
446
+ ].join("\n");
447
+ function injectStyles() {
448
+ if (typeof document === "undefined") return () => void 0;
449
+ if (document.querySelector(`style[data-plugin-css="${STYLE_ID}"]`) !== null) {
450
+ return () => void 0;
451
+ }
452
+ const tag = document.createElement("style");
453
+ tag.dataset.plugin = "dsh-later";
454
+ tag.dataset.pluginCss = STYLE_ID;
455
+ tag.textContent = COMPONENTS;
456
+ document.head.appendChild(tag);
457
+ return () => {
458
+ tag.remove();
459
+ };
460
+ }
461
+
462
+ // src/client/components/SchedButton.tsx
463
+ var import_react6 = require("react");
464
+
465
+ // src/time-utils.ts
466
+ function detectTimeZone() {
467
+ try {
468
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
469
+ return tz?.length ? tz : "UTC";
470
+ } catch {
471
+ return "UTC";
472
+ }
473
+ }
474
+ function formatRelative(epoch, now = Date.now(), locale = "zh") {
475
+ const diff = epoch - now;
476
+ if (locale === "en") {
477
+ if (diff < 0) return "overdue";
478
+ if (diff < MINUTE_MS) return "sending soon";
479
+ const minutes2 = Math.round(diff / MINUTE_MS);
480
+ if (minutes2 < 60) return `in ~${minutes2} min`;
481
+ const hours2 = Math.floor(minutes2 / 60);
482
+ const restMinutes2 = minutes2 % 60;
483
+ if (hours2 < 24) {
484
+ return restMinutes2 === 0 ? `in ~${hours2} h` : `in ~${hours2} h ${restMinutes2} min`;
485
+ }
486
+ const days2 = Math.floor(hours2 / 24);
487
+ return `in ~${days2} days`;
488
+ }
489
+ if (diff < 0) return "\u5DF2\u5230\u671F";
490
+ if (diff < MINUTE_MS) return "\u5373\u5C06\u53D1\u9001";
491
+ const minutes = Math.round(diff / MINUTE_MS);
492
+ if (minutes < 60) return `\u7EA6 ${minutes} \u5206\u949F\u540E`;
493
+ const hours = Math.floor(minutes / 60);
494
+ const restMinutes = minutes % 60;
495
+ if (hours < 24) {
496
+ return restMinutes === 0 ? `\u7EA6 ${hours} \u5C0F\u65F6\u540E` : `\u7EA6 ${hours} \u5C0F\u65F6 ${restMinutes} \u5206\u540E`;
497
+ }
498
+ const days = Math.floor(hours / 24);
499
+ return `\u7EA6 ${days} \u5929\u540E`;
500
+ }
501
+ var MINUTE_MS = 60 * 1e3;
502
+ var HOUR_MS = 60 * MINUTE_MS;
503
+ var DAY_MS = 24 * HOUR_MS;
504
+ function formatAbsolute(epoch, timeZone, locale = "zh") {
505
+ const formatter = new Intl.DateTimeFormat(locale === "en" ? "en-US" : "zh-CN", {
506
+ timeZone,
507
+ month: "numeric",
508
+ day: "numeric",
509
+ weekday: "short",
510
+ hour: "2-digit",
511
+ minute: "2-digit",
512
+ hourCycle: "h23"
513
+ });
514
+ return formatter.format(epoch).replace(/\s+/g, " ");
515
+ }
516
+ function formatHhmm(epoch, timeZone) {
517
+ const formatter = new Intl.DateTimeFormat("en-GB-u-ca-iso8601-nu-latn", {
518
+ timeZone,
519
+ hour: "2-digit",
520
+ minute: "2-digit",
521
+ hourCycle: "h23"
522
+ });
523
+ return formatter.format(epoch);
524
+ }
525
+
526
+ // src/client/useSchedT.ts
527
+ var import_react = require("react");
528
+
529
+ // src/client/strings.ts
530
+ var zh = {
531
+ /** 输入框按钮 tooltip */
532
+ buttonSchedule: "\u5B9A\u65F6\u53D1\u9001",
533
+ /** 面板标题 */
534
+ panelTitle: "\u5B9A\u65F6\u63D0\u9192",
535
+ /** 关闭面板 */
536
+ close: "\u5173\u95ED",
537
+ /** 快捷选项 */
538
+ quick10m: "10\u5206\u949F\u540E",
539
+ quick1h: "1\u5C0F\u65F6\u540E",
540
+ quickSmart: "\u5DE5\u4F5C\u65F6\u95F4",
541
+ quickCustom: "\u81EA\u5B9A\u4E49\u2026",
542
+ /** 自定义输入 */
543
+ dateLabel: "\u65E5\u671F",
544
+ timeLabel: "\u65F6\u95F4",
545
+ today: "\u4ECA\u5929",
546
+ tomorrow: "\u660E\u5929",
547
+ /** 任务列表 */
548
+ taskListTitle: "\u5DF2\u8BBE\u5B9A",
549
+ emptyTasks: "\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1",
550
+ deleteTask: "\u5220\u9664\u63D0\u9192",
551
+ /** 编辑提醒内容(dock 行内) */
552
+ editTask: "\u7F16\u8F91\u63D0\u9192",
553
+ editSave: "\u4FDD\u5B58",
554
+ editCancel: "\u53D6\u6D88",
555
+ editErrEmpty: "\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A",
556
+ editErrFailed: "\u4FEE\u6539\u5931\u8D25\uFF1A{message}",
557
+ editErrNotAccepted: "\u547D\u4EE4\u672A\u88AB\u53D7\u7406",
558
+ editErrCommandFailed: "\u547D\u4EE4\u5931\u8D25",
559
+ /** 主动插话发送:立即把提醒内容推给 agent(QueueDock「插话发送」语义) */
560
+ steerTask: "\u63D2\u8BDD\u53D1\u9001",
561
+ steerAlreadySent: "\u5DF2\u63D2\u8BDD",
562
+ steerFailed: "\u63D2\u8BDD\u5931\u8D25\uFF1A{message}",
563
+ steerUnsupportedKind: "\u5468\u671F\u63D0\u9192\u6682\u4E0D\u652F\u6301\u63D2\u8BDD",
564
+ /** dock 折叠头计数(QueueDock「N 条排队消息」样式) */
565
+ panelCount: "{n} \u6761\u5B9A\u65F6\u63D0\u9192",
566
+ /** 精准倒计时(dock 行 + 折叠头) */
567
+ countdownLeft: "\u5269",
568
+ dueAnyMoment: "\u5373\u5C06\u53D1\u9001",
569
+ /** 状态 */
570
+ sending: "\u6B63\u5728\u53D1\u9001\u2026",
571
+ /** 确认按钮 */
572
+ confirmAdd: "\u52A0\u5165 \xB7 {time} \u53D1\u9001",
573
+ confirmAddNoTime: "\u9009\u62E9\u53D1\u9001\u65F6\u95F4",
574
+ /** 提示 */
575
+ promptFromDraft: "\u5C06\u4EE5\u8F93\u5165\u6846\u5185\u5BB9\u4F5C\u4E3A\u63D0\u9192\u5185\u5BB9",
576
+ /** 错误提示 */
577
+ errPromptEmpty: "\u8BF7\u5148\u5728\u8F93\u5165\u6846\u8F93\u5165\u5185\u5BB9",
578
+ errPromptTooLong: "\u63D0\u9192\u5185\u5BB9\u4E0D\u80FD\u8D85\u8FC7 1000 \u5B57\u7B26",
579
+ errTimePast: "\u8BF7\u9009\u62E9\u672A\u6765\u7684\u65F6\u95F4",
580
+ errCreateFailed: "\u521B\u5EFA\u5931\u8D25\uFF1A{message}",
581
+ errDeleteFailed: "\u5220\u9664\u5931\u8D25\uFF1A{message}",
582
+ /** 客户端本地错误(hook 抛令牌,UI 按当前语言翻译) */
583
+ errNoSession: "\u5F53\u524D\u65E0\u4F1A\u8BDD",
584
+ errNotAcceptedStashed: "\u547D\u4EE4\u672A\u88AB\u53D7\u7406\uFF0C\u5DF2\u6682\u5B58\u672C\u5730",
585
+ /** 侧栏会话行「定时状态」badge(docs/ui/09) */
586
+ presenceBadgeAria: "\u6709 {n} \u6761\u5B9A\u65F6\u63D0\u9192\uFF0C\u4E0B\u6B21 {time}",
587
+ presenceOverdueAria: "\u6709 {n} \u6761\u5B9A\u65F6\u63D0\u9192\u5DF2\u5230\u671F\uFF0C\u7B49\u5F85\u53D1\u9001",
588
+ presenceTooltip: "\u4E0B\u6B21 {time} \xB7 {n} \u6761\u4EFB\u52A1",
589
+ presenceOverdueTooltip: "\u5DF2\u5230\u671F \xB7 {n} \u6761\u4EFB\u52A1\uFF08\u7B49\u5F85\u4F1A\u8BDD\u7A7A\u95F2\u53D1\u9001\uFF09",
590
+ relMinutes: "{m} \u5206\u540E",
591
+ relHours: "{h} \u5C0F\u65F6\u540E",
592
+ relTomorrow: "\u660E\u5929 {time}",
593
+ relDate: "{date} {time}",
594
+ relClock: "{time}"
595
+ };
596
+ var CLIENT_ERR = {
597
+ NO_SESSION: "SS_ERR_NO_SESSION",
598
+ NOT_ACCEPTED: "SS_ERR_NOT_ACCEPTED"
599
+ };
600
+ var en = {
601
+ buttonSchedule: "Schedule send",
602
+ panelTitle: "Scheduled reminders",
603
+ close: "Close",
604
+ quick10m: "In 10 minutes",
605
+ quick1h: "In 1 hour",
606
+ quickSmart: "Work hours",
607
+ quickCustom: "Custom\u2026",
608
+ dateLabel: "Date",
609
+ timeLabel: "Time",
610
+ today: "Today",
611
+ tomorrow: "Tomorrow",
612
+ taskListTitle: "Scheduled",
613
+ emptyTasks: "No scheduled reminders",
614
+ deleteTask: "Delete reminder",
615
+ editTask: "Edit reminder",
616
+ editSave: "Save",
617
+ editCancel: "Cancel",
618
+ editErrEmpty: "Reminder content cannot be empty",
619
+ editErrFailed: "Edit failed: {message}",
620
+ editErrNotAccepted: "Command was not accepted",
621
+ editErrCommandFailed: "Command failed",
622
+ steerTask: "Send now",
623
+ steerAlreadySent: "Steered",
624
+ steerFailed: "Steer failed: {message}",
625
+ steerUnsupportedKind: "Recurring reminders can't be steered",
626
+ panelCount: "{n} scheduled reminders",
627
+ countdownLeft: "in",
628
+ dueAnyMoment: "Sending soon",
629
+ sending: "Sending\u2026",
630
+ confirmAdd: "Add \xB7 send at {time}",
631
+ confirmAddNoTime: "Pick a send time",
632
+ promptFromDraft: "The composer draft will be used as the reminder content",
633
+ errPromptEmpty: "Type something in the composer first",
634
+ errPromptTooLong: "Reminder content cannot exceed 1000 characters",
635
+ errTimePast: "Pick a future time",
636
+ errCreateFailed: "Create failed: {message}",
637
+ errDeleteFailed: "Delete failed: {message}",
638
+ errNoSession: "No active session",
639
+ errNotAcceptedStashed: "Command was not accepted; saved locally",
640
+ presenceBadgeAria: "{n} scheduled reminder(s), next at {time}",
641
+ presenceOverdueAria: "{n} scheduled reminder(s) due, waiting to send",
642
+ presenceTooltip: "Next at {time} \xB7 {n} task(s)",
643
+ presenceOverdueTooltip: "Due \xB7 {n} task(s) (sends when the session is idle)",
644
+ relMinutes: "in {m} min",
645
+ relHours: "in {h} h",
646
+ relTomorrow: "Tomorrow {time}",
647
+ relDate: "{date} {time}",
648
+ relClock: "{time}"
649
+ };
650
+ var schedDicts = { zh, en };
651
+ function dictFor(localeId) {
652
+ return localeId === "en" ? schedDicts.en : schedDicts.zh;
653
+ }
654
+ function format(template, params) {
655
+ return template.replace(
656
+ /\{(\w+)\}/g,
657
+ (_, key) => key in params ? String(params[key]) : `{${key}}`
658
+ );
659
+ }
660
+
661
+ // src/client/useSchedT.ts
662
+ var noopSubscribe = () => () => {
663
+ };
664
+ function useSchedT(locale) {
665
+ const subscribe = (0, import_react.useCallback)(
666
+ (onStoreChange) => locale === void 0 ? noopSubscribe() : locale.subscribe(onStoreChange),
667
+ [locale]
668
+ );
669
+ const getActive = (0, import_react.useCallback)(() => locale === void 0 ? "zh" : locale.getSnapshot().active, [locale]);
670
+ const active = (0, import_react.useSyncExternalStore)(subscribe, getActive);
671
+ const lang = active === "en" ? "en" : "zh";
672
+ return { lang, t: dictFor(active) };
673
+ }
674
+
675
+ // src/client/components/SchedPanel.tsx
676
+ var import_react3 = require("react");
677
+
678
+ // src/smart-window.ts
679
+ var DEFAULT_SMART_WINDOW = Object.freeze({
680
+ workStart: "09:00",
681
+ workEnd: "18:00",
682
+ lunchStart: "12:00",
683
+ lunchEnd: "14:00",
684
+ eveningEnd: "22:00"
685
+ });
686
+ var HHMM_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
687
+ function isValidHhmm(value) {
688
+ return typeof value === "string" && HHMM_RE.test(value);
689
+ }
690
+ function sanitizeSmartWindowConfig(raw) {
691
+ const pick = (key, fallback) => isValidHhmm(raw?.[key]) ? raw[key] : fallback;
692
+ return {
693
+ workStart: pick("workStart", DEFAULT_SMART_WINDOW.workStart),
694
+ workEnd: pick("workEnd", DEFAULT_SMART_WINDOW.workEnd),
695
+ lunchStart: pick("lunchStart", DEFAULT_SMART_WINDOW.lunchStart),
696
+ lunchEnd: pick("lunchEnd", DEFAULT_SMART_WINDOW.lunchEnd),
697
+ eveningEnd: pick("eveningEnd", DEFAULT_SMART_WINDOW.eveningEnd)
698
+ };
699
+ }
700
+ var SMART_MIN_DELAY_MS = 2 * 60 * 1e3;
701
+ var MINUTE_MS2 = 60 * 1e3;
702
+ var HOUR_MS2 = 60 * MINUTE_MS2;
703
+ var DAY_MS2 = 24 * HOUR_MS2;
704
+ function hhmmToMinutes(value) {
705
+ const match = /^(\d{2}):(\d{2})$/.exec(value.trim());
706
+ if (match === null) return void 0;
707
+ const hour = Number(match[1]);
708
+ const minute = Number(match[2]);
709
+ if (hour > 23 || minute > 59) return void 0;
710
+ return hour * 60 + minute;
711
+ }
712
+ function isValidWindow(window2) {
713
+ const order = [
714
+ hhmmToMinutes(window2.workStart),
715
+ hhmmToMinutes(window2.lunchStart),
716
+ hhmmToMinutes(window2.lunchEnd),
717
+ hhmmToMinutes(window2.workEnd),
718
+ hhmmToMinutes(window2.eveningEnd)
719
+ ];
720
+ if (order.some((value) => value === void 0)) return false;
721
+ return order[0] < order[1] && order[1] < order[2] && order[2] < order[3] && order[3] < order[4];
722
+ }
723
+ function formatterFor(timeZone) {
724
+ return new Intl.DateTimeFormat("en-US-u-ca-iso8601-nu-latn", {
725
+ timeZone,
726
+ year: "numeric",
727
+ month: "2-digit",
728
+ day: "2-digit",
729
+ hour: "2-digit",
730
+ minute: "2-digit",
731
+ second: "2-digit",
732
+ hourCycle: "h23"
733
+ });
734
+ }
735
+ function localFieldsOf(epoch, timeZone) {
736
+ const formatter = formatterFor(timeZone);
737
+ const parts = Object.fromEntries(
738
+ formatter.formatToParts(epoch).map((part) => [part.type, part.value])
739
+ );
740
+ return {
741
+ year: Number(parts["year"]),
742
+ month: Number(parts["month"]),
743
+ day: Number(parts["day"]),
744
+ hour: Number(parts["hour"]),
745
+ minute: Number(parts["minute"]),
746
+ second: Number(parts["second"])
747
+ };
748
+ }
749
+ function offsetOf(epoch, timeZone) {
750
+ const f = localFieldsOf(epoch, timeZone);
751
+ return Date.UTC(f.year, f.month - 1, f.day, f.hour, f.minute, f.second, 0) - epoch;
752
+ }
753
+ function resolveWallClock(epochOnDay, timeZone, hour, minute, second = 0) {
754
+ const base = localFieldsOf(epochOnDay, timeZone);
755
+ const localEpoch = Date.UTC(base.year, base.month - 1, base.day, hour, minute, second, 0);
756
+ const offsets = /* @__PURE__ */ new Set();
757
+ for (let delta = -2; delta <= 2; delta += 1) {
758
+ offsets.add(offsetOf(localEpoch + delta * DAY_MS2, timeZone));
759
+ }
760
+ const candidates = [];
761
+ for (const offset of offsets) {
762
+ const candidate = localEpoch - offset;
763
+ const f = localFieldsOf(candidate, timeZone);
764
+ if (f.year === base.year && f.month === base.month && f.day === base.day && f.hour === hour && f.minute === minute && f.second === second) {
765
+ candidates.push(candidate);
766
+ }
767
+ }
768
+ candidates.sort((a, b) => a - b);
769
+ return candidates[0];
770
+ }
771
+ function nextMinutesAfter(now, timeZone, minutes) {
772
+ const hour = Math.floor(minutes / 60);
773
+ const minute = minutes % 60;
774
+ let probe = resolveWallClock(now, timeZone, hour, minute);
775
+ if (probe === void 0) {
776
+ probe = resolveWallClock(now + DAY_MS2, timeZone, hour, minute);
777
+ }
778
+ if (probe === void 0) {
779
+ return Date.UTC(
780
+ localFieldsOf(now, timeZone).year,
781
+ localFieldsOf(now, timeZone).month - 1,
782
+ localFieldsOf(now, timeZone).day,
783
+ hour,
784
+ minute,
785
+ 0,
786
+ 0
787
+ );
788
+ }
789
+ if (probe > now) return probe;
790
+ const nextDay = resolveWallClock(probe + DAY_MS2, timeZone, hour, minute) ?? probe + DAY_MS2;
791
+ return nextDay > now ? nextDay : nextDay + DAY_MS2;
792
+ }
793
+ function nextSmartTarget(now, timeZone, window2 = DEFAULT_SMART_WINDOW) {
794
+ if (!isValidWindow(window2)) throw new TypeError("smart window configuration is invalid");
795
+ const fields = localFieldsOf(now, timeZone);
796
+ const minutes = fields.hour * 60 + fields.minute;
797
+ const lunchStart = hhmmToMinutes(window2.lunchStart);
798
+ const lunchEnd = hhmmToMinutes(window2.lunchEnd);
799
+ if (minutes >= lunchStart && minutes < lunchEnd) {
800
+ return now + SMART_MIN_DELAY_MS;
801
+ }
802
+ const workStart = hhmmToMinutes(window2.workStart);
803
+ const eveningStart = hhmmToMinutes(window2.workEnd);
804
+ const candidates = [
805
+ nextMinutesAfter(now, timeZone, workStart),
806
+ nextMinutesAfter(now, timeZone, lunchEnd),
807
+ nextMinutesAfter(now, timeZone, eveningStart)
808
+ ].filter((target) => target > now + SMART_MIN_DELAY_MS);
809
+ if (candidates.length === 0) {
810
+ const earliest = Math.min(
811
+ nextMinutesAfter(now, timeZone, workStart),
812
+ nextMinutesAfter(now, timeZone, lunchEnd),
813
+ nextMinutesAfter(now, timeZone, eveningStart)
814
+ );
815
+ return Math.max(earliest, now + SMART_MIN_DELAY_MS);
816
+ }
817
+ return Math.min(...candidates);
818
+ }
819
+ function epochFromLocal(date, time, timeZone) {
820
+ const dateMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
821
+ const timeMatch = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(time);
822
+ if (dateMatch === null || timeMatch === null) return void 0;
823
+ const year = Number(dateMatch[1]);
824
+ const month = Number(dateMatch[2]);
825
+ const day = Number(dateMatch[3]);
826
+ const hour = Number(timeMatch[1]);
827
+ const minute = Number(timeMatch[2]);
828
+ const second = Number(timeMatch[3] ?? "0");
829
+ if (hour > 23 || minute > 59 || second > 59 || month < 1 || month > 12 || day < 1 || day > 31) {
830
+ return void 0;
831
+ }
832
+ const approx = Date.UTC(year, month - 1, day, 0, 0, 0, 0);
833
+ return resolveWallClock(approx, timeZone, hour, minute, second);
834
+ }
835
+ function nextSmartAt(now, timeZone, window2 = DEFAULT_SMART_WINDOW) {
836
+ let target;
837
+ try {
838
+ target = nextSmartTarget(now, timeZone, window2);
839
+ } catch {
840
+ return null;
841
+ }
842
+ let parts;
843
+ try {
844
+ parts = localFieldsOf(target, timeZone);
845
+ } catch {
846
+ return null;
847
+ }
848
+ const date = `${String(parts.year).padStart(4, "0")}-${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}`;
849
+ const time = `${String(parts.hour).padStart(2, "0")}:${String(parts.minute).padStart(2, "0")}:${String(parts.second).padStart(2, "0")}`;
850
+ return {
851
+ date,
852
+ time,
853
+ time_zone: timeZone,
854
+ epoch: target
855
+ };
856
+ }
857
+
858
+ // src/client/components/TaskList.tsx
859
+ var import_jsx_runtime = require("react/jsx-runtime");
860
+ function scheduleStateOf(item, now) {
861
+ return Date.parse(item.scheduled_at) <= now ? "overdue" : "scheduled";
862
+ }
863
+ function TaskList({ items: items2, now, deliveredIds, cancellingIds, onDelete, timeZone, t, lang }) {
864
+ if (items2.length === 0) {
865
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ss-empty", "data-ss-empty": "", children: t.emptyTasks });
866
+ }
867
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "ss-tasks", "data-ss-tasks": "", children: items2.map((item) => {
868
+ const dotState = deliveredIds?.has(item.id) ? "delivered" : cancellingIds?.has(item.id) ? "cancelled" : scheduleStateOf(item, now);
869
+ const isCancelled = dotState === "cancelled";
870
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: isCancelled ? "ss-task cancelled" : "ss-task", "data-ss-task": item.id, children: [
871
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `ss-dot ${dotState}`, "aria-hidden": "true" }),
872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ss-task-main", children: [
873
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ss-task-time", children: [
874
+ formatAbsolute(Date.parse(item.scheduled_at), timeZone, lang),
875
+ " \xB7 ",
876
+ formatRelative(Date.parse(item.scheduled_at), now, lang)
877
+ ] }),
878
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
879
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ss-task-prompt", children: item.prompt })
880
+ ] }),
881
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
882
+ "button",
883
+ {
884
+ type: "button",
885
+ className: "ss-task-del",
886
+ title: t.deleteTask,
887
+ "aria-label": `${t.deleteTask}: ${item.prompt}`,
888
+ onClick: (event) => {
889
+ event.stopPropagation();
890
+ onDelete(item.id);
891
+ },
892
+ children: "\u2715"
893
+ }
894
+ )
895
+ ] }, item.id);
896
+ }) });
897
+ }
898
+
899
+ // src/client/useCountdown.ts
900
+ var import_react2 = require("react");
901
+ function useNow(intervalMs = 1e3) {
902
+ const [now, setNow] = (0, import_react2.useState)(() => Date.now());
903
+ (0, import_react2.useEffect)(() => {
904
+ const timer = setInterval(() => setNow(Date.now()), intervalMs);
905
+ return () => clearInterval(timer);
906
+ }, [intervalMs]);
907
+ return now;
908
+ }
909
+
910
+ // src/client/components/SchedPanel.tsx
911
+ var import_jsx_runtime2 = require("react/jsx-runtime");
912
+ function atOnDay(dayOffset, hhmm, timeZone, now) {
913
+ const match = /^(\d{2}):(\d{2})$/.exec(hhmm);
914
+ if (match === null) return null;
915
+ const fields = localFieldsOf(now + dayOffset * 24 * 3600 * 1e3, timeZone);
916
+ const date = `${String(fields.year).padStart(4, "0")}-${String(fields.month).padStart(2, "0")}-${String(fields.day).padStart(2, "0")}`;
917
+ const epoch = epochFromLocal(date, `${hhmm}:00`, timeZone);
918
+ if (epoch === void 0 || Number.isNaN(epoch)) return null;
919
+ return { at: { date, time: `${hhmm}:00`, time_zone: timeZone }, epoch };
920
+ }
921
+ function SchedPanel({
922
+ defaultPrompt,
923
+ schedules,
924
+ timeZone,
925
+ busy,
926
+ t,
927
+ lang,
928
+ onClose,
929
+ onCreate,
930
+ onDelete,
931
+ bottom,
932
+ smartWindow = DEFAULT_SMART_WINDOW,
933
+ promptLimits
934
+ }) {
935
+ const now = useNow(1e3);
936
+ const [selected, setSelected] = (0, import_react3.useState)("after-600");
937
+ const [customDate, setCustomDate] = (0, import_react3.useState)(() => {
938
+ const f = localFieldsOf(Date.now(), timeZone);
939
+ return `${String(f.year).padStart(4, "0")}-${String(f.month).padStart(2, "0")}-${String(f.day).padStart(2, "0")}`;
940
+ });
941
+ const [customTime, setCustomTime] = (0, import_react3.useState)("18:00");
942
+ const [error, setError] = (0, import_react3.useState)(null);
943
+ const [cancelling, setCancelling] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
944
+ const [delivered, setDelivered] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
945
+ const deliveredTimers = (0, import_react3.useRef)([]);
946
+ const prevIds = (0, import_react3.useRef)(null);
947
+ (0, import_react3.useEffect)(() => {
948
+ const currentIds = new Set(schedules.map((item) => item.id));
949
+ const previous = prevIds.current;
950
+ prevIds.current = currentIds;
951
+ if (previous === null) return;
952
+ const gone = [...previous].filter((id) => !currentIds.has(id) && !cancelling.has(id));
953
+ if (gone.length === 0) return;
954
+ setDelivered((existing) => /* @__PURE__ */ new Set([...existing, ...gone]));
955
+ for (const id of gone) {
956
+ const timer = window.setTimeout(() => {
957
+ setDelivered((existing) => {
958
+ const next = new Set(existing);
959
+ next.delete(id);
960
+ return next;
961
+ });
962
+ }, 3e3);
963
+ deliveredTimers.current.push(timer);
964
+ }
965
+ }, [schedules, cancelling]);
966
+ (0, import_react3.useEffect)(
967
+ () => () => {
968
+ for (const timer of deliveredTimers.current) window.clearTimeout(timer);
969
+ },
970
+ []
971
+ );
972
+ const quickOptions = (0, import_react3.useMemo)(() => {
973
+ const options = [
974
+ { kind: "after", seconds: 600, label: t.quick10m },
975
+ { kind: "after", seconds: 3600, label: t.quick1h }
976
+ ];
977
+ const todayEvening = atOnDay(0, smartWindow.workEnd, timeZone, now);
978
+ if (todayEvening !== null && todayEvening.epoch > now) {
979
+ options.push({ kind: "at", ...todayEvening, label: `${t.today} ${smartWindow.workEnd}` });
980
+ }
981
+ const tomorrowMorning = atOnDay(1, smartWindow.workStart, timeZone, now);
982
+ if (tomorrowMorning !== null && tomorrowMorning.epoch > now) {
983
+ options.push({ kind: "at", ...tomorrowMorning, label: `${t.tomorrow} ${smartWindow.workStart}` });
984
+ }
985
+ const smart = nextSmartAt(now, timeZone, smartWindow);
986
+ if (smart !== null) {
987
+ options.push({
988
+ kind: "smart",
989
+ at: { date: smart.date, time: smart.time, time_zone: smart.time_zone },
990
+ epoch: smart.epoch,
991
+ label: t.quickSmart
992
+ });
993
+ }
994
+ options.push({ kind: "custom", label: t.quickCustom });
995
+ return options;
996
+ }, [timeZone, now, t, smartWindow]);
997
+ const target = (0, import_react3.useMemo)(() => {
998
+ if (selected === "custom") {
999
+ const match = /^(\d{2}):(\d{2})$/.exec(customTime);
1000
+ if (match === null) return null;
1001
+ const epoch = epochFromLocal(customDate, `${customTime}:00`, timeZone);
1002
+ if (epoch === void 0 || Number.isNaN(epoch) || epoch <= now) return null;
1003
+ return { at: { date: customDate, time: `${customTime}:00`, time_zone: timeZone }, epoch };
1004
+ }
1005
+ const option = quickOptions.find((item) => keyOf(item) === selected);
1006
+ if (option === void 0 || option.kind === "custom") return null;
1007
+ if (option.kind === "after") return { after_seconds: option.seconds, epoch: now + option.seconds * 1e3 };
1008
+ return { at: option.at, epoch: option.epoch };
1009
+ }, [selected, customDate, customTime, timeZone, now, quickOptions]);
1010
+ const targetLabel = (0, import_react3.useMemo)(() => {
1011
+ if (target === void 0 || target === null) return null;
1012
+ return `${formatAbsolute(target.epoch, timeZone, lang)} \xB7 ${formatRelative(target.epoch, now, lang)}`;
1013
+ }, [target, timeZone, now]);
1014
+ const handleConfirm = async () => {
1015
+ const text = defaultPrompt.trim();
1016
+ if (text.length === 0) {
1017
+ setError(t.errPromptEmpty);
1018
+ return;
1019
+ }
1020
+ const maxChars = promptLimits?.maxChars ?? 1e3;
1021
+ if (text.length > maxChars) {
1022
+ setError(t.errPromptTooLong);
1023
+ return;
1024
+ }
1025
+ if (target === null) {
1026
+ setError(t.errTimePast);
1027
+ return;
1028
+ }
1029
+ setError(null);
1030
+ try {
1031
+ await onCreate(
1032
+ target.at !== void 0 ? { prompt: text, at: target.at } : { prompt: text, after_seconds: target.after_seconds }
1033
+ );
1034
+ onClose();
1035
+ } catch (cause) {
1036
+ const raw = cause instanceof Error ? cause.message : String(cause);
1037
+ const message = raw === CLIENT_ERR.NO_SESSION ? t.errNoSession : raw === CLIENT_ERR.NOT_ACCEPTED ? t.errNotAcceptedStashed : raw;
1038
+ setError(format(t.errCreateFailed, { message }));
1039
+ }
1040
+ };
1041
+ const handleDelete = (id) => {
1042
+ setCancelling((current) => new Set(current).add(id));
1043
+ try {
1044
+ onDelete(id);
1045
+ } catch (cause) {
1046
+ setError(format(t.errDeleteFailed, { message: cause instanceof Error ? cause.message : String(cause) }));
1047
+ }
1048
+ window.setTimeout(() => {
1049
+ setCancelling((current) => {
1050
+ const next = new Set(current);
1051
+ next.delete(id);
1052
+ return next;
1053
+ });
1054
+ }, 2500);
1055
+ };
1056
+ const promptEmpty = defaultPrompt.trim().length === 0;
1057
+ const canConfirm = !busy && !promptEmpty && target !== null;
1058
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-panel", "data-ss-panel": "", role: "dialog", "aria-label": t.panelTitle, style: { bottom }, children: [
1059
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-panel-header", children: [
1060
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ss-panel-title", children: t.panelTitle }),
1061
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "ss-panel-close", "aria-label": t.close, onClick: onClose, children: "\u2715" })
1062
+ ] }),
1063
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-quick-grid", role: "radiogroup", "aria-label": t.panelTitle, children: quickOptions.map((option) => {
1064
+ const key = keyOf(option);
1065
+ const active = selected === key || option.kind === "custom" && selected === "custom";
1066
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1067
+ "button",
1068
+ {
1069
+ type: "button",
1070
+ role: "radio",
1071
+ "aria-checked": active,
1072
+ className: active ? "ss-quick-chip ss-active" : "ss-quick-chip",
1073
+ onClick: () => {
1074
+ setSelected(key);
1075
+ setError(null);
1076
+ },
1077
+ children: option.label
1078
+ },
1079
+ key
1080
+ );
1081
+ }) }),
1082
+ selected === "custom" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ss-custom-row", children: [
1083
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1084
+ "input",
1085
+ {
1086
+ type: "date",
1087
+ className: "ss-input",
1088
+ "aria-label": t.dateLabel,
1089
+ value: customDate,
1090
+ onChange: (event) => {
1091
+ if (event.target.value) setCustomDate(event.target.value);
1092
+ }
1093
+ }
1094
+ ),
1095
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1096
+ "input",
1097
+ {
1098
+ type: "time",
1099
+ className: "ss-input",
1100
+ "aria-label": t.timeLabel,
1101
+ value: customTime,
1102
+ onChange: (event) => {
1103
+ if (event.target.value) setCustomTime(event.target.value);
1104
+ }
1105
+ }
1106
+ )
1107
+ ] }),
1108
+ target !== null && targetLabel !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-hint", style: { margin: "6px 0 0" }, children: targetLabel }),
1109
+ error !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-error", role: "alert", style: { marginTop: 6 }, children: error }),
1110
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-tasklist-head", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ss-hint", children: [
1111
+ t.taskListTitle,
1112
+ schedules.length > 0 ? ` (${schedules.length})` : ""
1113
+ ] }) }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1115
+ TaskList,
1116
+ {
1117
+ items: schedules,
1118
+ now,
1119
+ deliveredIds: delivered,
1120
+ cancellingIds: cancelling,
1121
+ onDelete: handleDelete,
1122
+ timeZone,
1123
+ t,
1124
+ lang
1125
+ }
1126
+ ),
1127
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-footer", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1128
+ "button",
1129
+ {
1130
+ type: "button",
1131
+ className: "ss-confirm",
1132
+ disabled: !canConfirm,
1133
+ onClick: () => void handleConfirm(),
1134
+ children: busy ? t.sending : target !== null ? format(t.confirmAdd, { time: formatAbsolute(target.epoch, timeZone, lang) }) : t.confirmAddNoTime
1135
+ }
1136
+ ) }),
1137
+ promptEmpty && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ss-hint", style: { marginTop: 6 }, children: t.promptFromDraft })
1138
+ ] });
1139
+ }
1140
+ function keyOf(option) {
1141
+ switch (option.kind) {
1142
+ case "after":
1143
+ return `after-${option.seconds}`;
1144
+ case "at":
1145
+ return `at-${option.at.date}-${option.at.time}`;
1146
+ case "smart":
1147
+ return "smart";
1148
+ case "custom":
1149
+ return "custom";
1150
+ }
1151
+ }
1152
+
1153
+ // src/client/hooks/useSchedules.ts
1154
+ var import_react4 = require("react");
1155
+
1156
+ // src/client/local-store.ts
1157
+ var STORAGE_KEY = "dsh-later:pending";
1158
+ function readPending() {
1159
+ if (typeof localStorage === "undefined") return [];
1160
+ try {
1161
+ const raw = localStorage.getItem(STORAGE_KEY);
1162
+ if (raw === null) return [];
1163
+ const parsed = JSON.parse(raw);
1164
+ if (!Array.isArray(parsed)) return [];
1165
+ return parsed.filter(isPendingSchedule);
1166
+ } catch {
1167
+ return [];
1168
+ }
1169
+ }
1170
+ function writePending(pending) {
1171
+ if (typeof localStorage === "undefined") return;
1172
+ try {
1173
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(pending));
1174
+ } catch {
1175
+ }
1176
+ }
1177
+ function appendPending(payload) {
1178
+ const pending = readPending();
1179
+ const clientSeq = pending.reduce((max, item2) => Math.max(max, item2.clientSeq), 0) + 1;
1180
+ const item = { clientSeq, createdAt: Date.now(), payload };
1181
+ writePending([...pending, item]);
1182
+ return item;
1183
+ }
1184
+ function removePending(clientSeq) {
1185
+ const pending = readPending().filter((item) => item.clientSeq !== clientSeq);
1186
+ writePending(pending);
1187
+ }
1188
+ function isPendingSchedule(value) {
1189
+ if (typeof value !== "object" || value === null) return false;
1190
+ const v = value;
1191
+ return typeof v["clientSeq"] === "number" && typeof v["createdAt"] === "number" && typeof v["payload"] === "object" && v["payload"] !== null;
1192
+ }
1193
+
1194
+ // src/client/hooks/useSchedules.ts
1195
+ function schedulesOf(projection) {
1196
+ return projection?.schedules ?? [];
1197
+ }
1198
+ var createLine = (input) => {
1199
+ const body = { prompt: input.prompt, time_zone: detectTimeZone() };
1200
+ if (input.at !== void 0) body["at"] = input.at;
1201
+ if (input.after_seconds !== void 0) body["after_seconds"] = input.after_seconds;
1202
+ return `/user-schedule-create ${JSON.stringify(body)}`;
1203
+ };
1204
+ var deleteLine = (id) => `/user-schedule-delete ${JSON.stringify({ id })}`;
1205
+ function useSchedules(inject2) {
1206
+ const { callCommand, sessionId, inputActions, input, useProjection, useInput } = inject2;
1207
+ const [busy, setBusy] = (0, import_react4.useState)(false);
1208
+ const [anchorBottom, setAnchorBottom] = (0, import_react4.useState)(80);
1209
+ const [pendingSync, setPendingSync] = (0, import_react4.useState)(() => readPending());
1210
+ const rowRef = (0, import_react4.useRef)(null);
1211
+ const now = useNow(1e3);
1212
+ const draft = input?.draft ?? (typeof useInput === "function" ? useInput((s) => s.draft) : "") ?? "";
1213
+ const projection = typeof useProjection === "function" ? useProjection("userSchedules") : void 0;
1214
+ const schedules = (0, import_react4.useMemo)(() => schedulesOf(projection), [projection]);
1215
+ const nextEpoch = (0, import_react4.useMemo)(() => {
1216
+ return schedules.map((item) => Date.parse(item.scheduled_at)).filter((value) => !Number.isNaN(value)).sort((a, b) => a - b)[0];
1217
+ }, [schedules]);
1218
+ const enabled = draft.trim().length > 0 || schedules.length > 0;
1219
+ const reposition = (0, import_react4.useCallback)(() => {
1220
+ const el = rowRef.current;
1221
+ if (el === null) return;
1222
+ const rect = el.getBoundingClientRect();
1223
+ setAnchorBottom(Math.max(12, window.innerHeight - rect.top + 10));
1224
+ }, []);
1225
+ const syncedPrompts = (0, import_react4.useMemo)(() => {
1226
+ return new Set(schedules.map((item) => item.prompt));
1227
+ }, [schedules]);
1228
+ (0, import_react4.useEffect)(() => {
1229
+ if (pendingSync.length === 0) return;
1230
+ const remaining = pendingSync.filter((item) => !syncedPrompts.has(item.payload.prompt));
1231
+ if (remaining.length !== pendingSync.length) {
1232
+ setPendingSync(remaining);
1233
+ writePendingLocal(remaining);
1234
+ }
1235
+ }, [syncedPrompts, pendingSync]);
1236
+ (0, import_react4.useEffect)(() => {
1237
+ if (pendingSync.length === 0 || !sessionId) return;
1238
+ let cancelled = false;
1239
+ const syncAll = async () => {
1240
+ const items2 = [...pendingSync];
1241
+ for (const item of items2) {
1242
+ if (cancelled) return;
1243
+ try {
1244
+ const accepted = await callCommand(sessionId, createLine(item.payload));
1245
+ if (accepted && !cancelled) {
1246
+ removePending(item.clientSeq);
1247
+ setPendingSync((prev) => prev.filter((p) => p.clientSeq !== item.clientSeq));
1248
+ }
1249
+ } catch {
1250
+ return;
1251
+ }
1252
+ }
1253
+ };
1254
+ void syncAll();
1255
+ const onOnline = () => {
1256
+ if (!cancelled) void syncAll();
1257
+ };
1258
+ window.addEventListener("online", onOnline);
1259
+ return () => {
1260
+ cancelled = true;
1261
+ window.removeEventListener("online", onOnline);
1262
+ };
1263
+ }, [pendingSync, sessionId, callCommand]);
1264
+ const handleCreate = (0, import_react4.useCallback)(async (inputPayload) => {
1265
+ if (!sessionId) {
1266
+ throw new Error(CLIENT_ERR.NO_SESSION);
1267
+ }
1268
+ setBusy(true);
1269
+ try {
1270
+ const accepted = await callCommand(sessionId, createLine(inputPayload));
1271
+ if (!accepted) {
1272
+ appendPending(inputPayload);
1273
+ setPendingSync((prev) => [...prev, { clientSeq: Date.now(), createdAt: Date.now(), payload: inputPayload }]);
1274
+ throw new Error(CLIENT_ERR.NOT_ACCEPTED);
1275
+ }
1276
+ } finally {
1277
+ setBusy(false);
1278
+ }
1279
+ }, [sessionId, callCommand]);
1280
+ const handleDelete = (0, import_react4.useCallback)(async (id) => {
1281
+ if (!sessionId) return;
1282
+ await callCommand(sessionId, deleteLine(id));
1283
+ }, [sessionId, callCommand]);
1284
+ const handleCancelAll = (0, import_react4.useCallback)(async () => {
1285
+ if (!sessionId) return;
1286
+ await Promise.all(schedules.map((item) => callCommand(sessionId, deleteLine(item.id))));
1287
+ }, [sessionId, schedules, callCommand]);
1288
+ const handleClearDraft = (0, import_react4.useCallback)(() => {
1289
+ inputActions?.setDraft("");
1290
+ }, [inputActions]);
1291
+ return {
1292
+ draft,
1293
+ schedules,
1294
+ nextEpoch,
1295
+ enabled,
1296
+ busy,
1297
+ anchorBottom,
1298
+ reposition,
1299
+ handleCreate,
1300
+ handleDelete,
1301
+ handleCancelAll,
1302
+ handleClearDraft,
1303
+ rowRef
1304
+ };
1305
+ }
1306
+ function writePendingLocal(pending) {
1307
+ try {
1308
+ if (typeof localStorage !== "undefined") {
1309
+ localStorage.setItem("dsh-later:pending", JSON.stringify(pending));
1310
+ }
1311
+ } catch {
1312
+ }
1313
+ }
1314
+
1315
+ // src/client/hooks/useSchedulerSettings.ts
1316
+ var import_react5 = require("react");
1317
+ var CLIENT_DEFAULT_MAX_PROMPT_CHARS = 1e3;
1318
+ var FALLBACK_PROMPT = {
1319
+ allowLong: false,
1320
+ maxChars: CLIENT_DEFAULT_MAX_PROMPT_CHARS
1321
+ };
1322
+ var FALLBACK = {
1323
+ ready: false,
1324
+ showButton: true,
1325
+ smartWindow: sanitizeSmartWindowConfig(void 0),
1326
+ promptLimits: FALLBACK_PROMPT
1327
+ };
1328
+ function resolvePromptLimitsFromSnapshot(snap) {
1329
+ if (snap.status !== "ready") return FALLBACK_PROMPT;
1330
+ const value = snap.value ?? {};
1331
+ const allowLong = value["allowLongPrompts"] === true;
1332
+ const raw = value["maxPromptChars"];
1333
+ const valid = typeof raw === "number" && Number.isFinite(raw) && raw >= 1 && Math.floor(raw) === raw;
1334
+ if (allowLong && valid) return { allowLong: true, maxChars: raw };
1335
+ return FALLBACK_PROMPT;
1336
+ }
1337
+ function resolveFromSnapshot(snap) {
1338
+ if (snap.status !== "ready") {
1339
+ return {
1340
+ ready: false,
1341
+ showButton: true,
1342
+ smartWindow: FALLBACK.smartWindow,
1343
+ promptLimits: FALLBACK_PROMPT
1344
+ };
1345
+ }
1346
+ const value = snap.value ?? {};
1347
+ return {
1348
+ ready: true,
1349
+ // 仅显式 false 隐藏;缺失/非法一律回退显示(与 schema 默认 true 同源)。
1350
+ showButton: value["showButton"] !== false,
1351
+ smartWindow: sanitizeSmartWindowConfig(
1352
+ value
1353
+ ),
1354
+ promptLimits: resolvePromptLimitsFromSnapshot(snap)
1355
+ };
1356
+ }
1357
+ var NOOP_SUBSCRIBE = () => () => void 0;
1358
+ var EMPTY_SNAPSHOT = { status: "unavailable" };
1359
+ function useSchedulerSettings(scope) {
1360
+ const getSnapshot = (0, import_react5.useCallback)(
1361
+ () => scope === void 0 ? EMPTY_SNAPSHOT : scope.getSnapshot(),
1362
+ [scope]
1363
+ );
1364
+ const subscribe = (0, import_react5.useCallback)(
1365
+ (onStoreChange) => scope === void 0 ? NOOP_SUBSCRIBE() : scope.subscribe(onStoreChange),
1366
+ [scope]
1367
+ );
1368
+ const raw = (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1369
+ return (0, import_react5.useMemo)(() => resolveFromSnapshot(raw), [raw]);
1370
+ }
1371
+
1372
+ // src/client/components/SchedButton.tsx
1373
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1374
+ function SchedButton(props) {
1375
+ const { callCommand, sessionId, inputActions, input, useProjection, useInput, locale, schedulerScope } = props;
1376
+ const { lang, t } = useSchedT(locale);
1377
+ const { smartWindow, promptLimits, showButton } = useSchedulerSettings(schedulerScope);
1378
+ const [open, setOpen] = (0, import_react6.useState)(false);
1379
+ const {
1380
+ draft,
1381
+ schedules,
1382
+ enabled,
1383
+ busy,
1384
+ anchorBottom,
1385
+ reposition,
1386
+ handleCreate,
1387
+ handleDelete,
1388
+ rowRef
1389
+ } = useSchedules({
1390
+ callCommand,
1391
+ sessionId,
1392
+ inputActions,
1393
+ input,
1394
+ useProjection,
1395
+ useInput
1396
+ });
1397
+ (0, import_react6.useEffect)(() => {
1398
+ if (!open) return;
1399
+ reposition();
1400
+ window.addEventListener("resize", reposition);
1401
+ const onKey = (event) => {
1402
+ if (event.key === "Escape") setOpen(false);
1403
+ };
1404
+ window.addEventListener("keydown", onKey, true);
1405
+ return () => {
1406
+ window.removeEventListener("resize", reposition);
1407
+ window.removeEventListener("keydown", onKey, true);
1408
+ };
1409
+ }, [open, reposition]);
1410
+ if (!showButton) return null;
1411
+ const handleCreateWithErrorBoundary = async (inputPayload) => {
1412
+ try {
1413
+ await handleCreate(inputPayload);
1414
+ } catch {
1415
+ setOpen(false);
1416
+ throw new Error(t.editErrNotAccepted);
1417
+ }
1418
+ };
1419
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { ref: rowRef, className: "ss-sched-row", "data-ss-sched-row": "", children: [
1420
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1421
+ "button",
1422
+ {
1423
+ type: "button",
1424
+ className: open ? "ss-sched-btn ss-active" : "ss-sched-btn",
1425
+ title: t.buttonSchedule,
1426
+ "aria-label": t.buttonSchedule,
1427
+ "aria-expanded": open,
1428
+ disabled: !enabled,
1429
+ onClick: () => setOpen((value) => !value),
1430
+ 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: [
1431
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "12 6 12 12 16 14" })
1433
+ ] })
1434
+ }
1435
+ ),
1436
+ open && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1437
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ss-backdrop", onClick: () => setOpen(false), "aria-hidden": "true" }),
1438
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1439
+ SchedPanel,
1440
+ {
1441
+ defaultPrompt: draft,
1442
+ schedules,
1443
+ timeZone: detectTimeZone(),
1444
+ busy,
1445
+ t,
1446
+ lang,
1447
+ onClose: () => setOpen(false),
1448
+ onCreate: handleCreateWithErrorBoundary,
1449
+ onDelete: (id) => void handleDelete(id),
1450
+ bottom: anchorBottom,
1451
+ smartWindow,
1452
+ promptLimits
1453
+ }
1454
+ )
1455
+ ] })
1456
+ ] });
1457
+ }
1458
+
1459
+ // src/client/components/ScheduleDock.tsx
1460
+ var import_react8 = require("react");
1461
+
1462
+ // src/client/toast.tsx
1463
+ var import_client = require("react-dom/client");
1464
+ var import_react7 = require("react");
1465
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1466
+ var listeners = /* @__PURE__ */ new Set();
1467
+ var items = [];
1468
+ var nextId = 1;
1469
+ function emit() {
1470
+ for (const listener of listeners) listener(items);
1471
+ }
1472
+ function showReminderToast(text, ttlMs = 5e3) {
1473
+ const id = nextId++;
1474
+ const item = { id, text };
1475
+ items = [...items, item];
1476
+ emit();
1477
+ window.setTimeout(() => {
1478
+ items = items.filter((existing) => existing.id !== id);
1479
+ emit();
1480
+ }, ttlMs);
1481
+ }
1482
+ function useToasts() {
1483
+ const [state, setState] = (0, import_react7.useState)(items);
1484
+ (0, import_react7.useEffect)(() => {
1485
+ const listener = (next) => setState(next);
1486
+ listeners.add(listener);
1487
+ return () => {
1488
+ listeners.delete(listener);
1489
+ };
1490
+ }, []);
1491
+ return state;
1492
+ }
1493
+ function ReminderToastHost() {
1494
+ const toasts = useToasts();
1495
+ if (toasts.length === 0) return null;
1496
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ss-toast-host", "data-ss-toast": "", children: toasts.map((item) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ss-toast", role: "status", children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ss-toast-icon", "aria-hidden": "true", children: "\u23F0" }),
1498
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ss-toast-text", children: item.text })
1499
+ ] }, item.id)) });
1500
+ }
1501
+ var mounted = false;
1502
+ function mountReminderToastHost() {
1503
+ if (mounted) return () => void 0;
1504
+ mounted = true;
1505
+ const host = document.createElement("div");
1506
+ host.setAttribute("data-ss-toast-root", "");
1507
+ document.body.appendChild(host);
1508
+ const root = (0, import_client.createRoot)(host);
1509
+ root.render(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ReminderToastHost, {}));
1510
+ return () => {
1511
+ root.unmount();
1512
+ host.remove();
1513
+ mounted = false;
1514
+ };
1515
+ }
1516
+
1517
+ // src/client/components/ScheduleDock.tsx
1518
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1519
+ function formatCountdown(ms) {
1520
+ const total = Math.max(0, Math.round(ms / 1e3));
1521
+ const h = Math.floor(total / 3600);
1522
+ const m = Math.floor(total % 3600 / 60);
1523
+ const s = total % 60;
1524
+ const mm = String(m).padStart(2, "0");
1525
+ const ss = String(s).padStart(2, "0");
1526
+ return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
1527
+ }
1528
+ var BAR_UNIT_MS = 36e5;
1529
+ function barSegments(item, now, firstSeen) {
1530
+ const end = Date.parse(item.scheduled_at);
1531
+ let start;
1532
+ if (item.kind === "after" && item.after_seconds !== void 0) {
1533
+ start = end - item.after_seconds * 1e3;
1534
+ } else if (item.kind === "every" && item.every_seconds !== void 0) {
1535
+ start = end - item.every_seconds * 1e3;
1536
+ } else if (item.created_at !== void 0) {
1537
+ const parsed = Date.parse(item.created_at);
1538
+ start = Number.isNaN(parsed) ? firstSeen.get(item.id) ?? now : parsed;
1539
+ } else {
1540
+ start = firstSeen.get(item.id) ?? now;
1541
+ }
1542
+ const totalMs = Math.max(1e3, end - start);
1543
+ const elapsedMs = Math.min(Math.max(now - start, 0), totalMs);
1544
+ const totalRatio = elapsedMs / totalMs;
1545
+ const count = Math.max(1, Math.ceil(totalMs / BAR_UNIT_MS));
1546
+ return { totalRatio, count };
1547
+ }
1548
+ var deleteLine2 = (id) => `/user-schedule-delete ${JSON.stringify({ id })}`;
1549
+ var editLine = (id, prompt) => `/user-schedule-edit ${JSON.stringify({ id, prompt })}`;
1550
+ var steerLine = (id) => `/user-schedule-steer-now ${JSON.stringify({ id })}`;
1551
+ function ScheduleDock({ callCommand, sessionId, useProjection, locale }) {
1552
+ const now = useNow(1e3);
1553
+ const { t } = useSchedT(locale);
1554
+ const projection = typeof useProjection === "function" ? useProjection("userSchedules") : void 0;
1555
+ const schedules = (0, import_react8.useMemo)(
1556
+ () => [...projection?.schedules ?? []].sort(
1557
+ (a, b) => Date.parse(a.scheduled_at) - Date.parse(b.scheduled_at)
1558
+ ),
1559
+ [projection]
1560
+ );
1561
+ const [collapsed, setCollapsed] = (0, import_react8.useState)(true);
1562
+ const [cancelling, setCancelling] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
1563
+ const [editingId, setEditingId] = (0, import_react8.useState)(null);
1564
+ const [editingText, setEditingText] = (0, import_react8.useState)("");
1565
+ const [editErr, setEditErr] = (0, import_react8.useState)(null);
1566
+ const [saving, setSaving] = (0, import_react8.useState)(false);
1567
+ const [steering, setSteering] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
1568
+ const [steerDone, setSteerDone] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
1569
+ const [steerErr, setSteerErr] = (0, import_react8.useState)(/* @__PURE__ */ new Map());
1570
+ const prevRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
1571
+ const suppressedRef = (0, import_react8.useRef)(/* @__PURE__ */ new Set());
1572
+ const firstSeenRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
1573
+ const byId = (0, import_react8.useMemo)(() => new Map(schedules.map((item) => [item.id, item])), [schedules]);
1574
+ (0, import_react8.useEffect)(() => {
1575
+ let changed = false;
1576
+ const next2 = new Map(firstSeenRef.current);
1577
+ for (const [id] of byId) {
1578
+ if (!next2.has(id)) {
1579
+ next2.set(id, Date.now());
1580
+ changed = true;
1581
+ }
1582
+ }
1583
+ for (const id of [...next2.keys()]) {
1584
+ if (!byId.has(id)) {
1585
+ next2.delete(id);
1586
+ changed = true;
1587
+ }
1588
+ }
1589
+ if (changed) firstSeenRef.current = next2;
1590
+ const prev = prevRef.current;
1591
+ if (prev.size > 0) {
1592
+ for (const [id, prevItem] of prev) {
1593
+ if (byId.has(id)) continue;
1594
+ if (suppressedRef.current.has(id)) continue;
1595
+ if (Date.parse(prevItem.scheduled_at) <= Date.now()) {
1596
+ showReminderToast(prevItem.prompt);
1597
+ }
1598
+ }
1599
+ }
1600
+ prevRef.current = byId;
1601
+ }, [byId]);
1602
+ const suppress = (id) => {
1603
+ suppressedRef.current = new Set(suppressedRef.current).add(id);
1604
+ };
1605
+ (0, import_react8.useEffect)(() => {
1606
+ if (schedules.length <= 1) setCollapsed(true);
1607
+ }, [schedules.length]);
1608
+ if (schedules.length === 0 || sessionId === void 0) return null;
1609
+ const next = schedules[0];
1610
+ if (next === void 0) return null;
1611
+ const expanded = schedules.length === 1 || !collapsed;
1612
+ const handleCancel = (id) => {
1613
+ suppress(id);
1614
+ setCancelling((current) => new Set(current).add(id));
1615
+ void callCommand(sessionId, deleteLine2(id)).finally(() => {
1616
+ window.setTimeout(() => {
1617
+ setCancelling((current) => {
1618
+ const nextSet = new Set(current);
1619
+ nextSet.delete(id);
1620
+ return nextSet;
1621
+ });
1622
+ }, 2500);
1623
+ });
1624
+ };
1625
+ const handleSteer = (item) => {
1626
+ const id = item.id;
1627
+ suppress(id);
1628
+ setSteering((current) => new Set(current).add(id));
1629
+ setSteerErr((current) => {
1630
+ const next2 = new Map(current);
1631
+ next2.delete(id);
1632
+ return next2;
1633
+ });
1634
+ void callCommand(sessionId, steerLine(id)).then((ok) => {
1635
+ if (ok) {
1636
+ setSteerDone((current) => new Set(current).add(id));
1637
+ } else {
1638
+ setSteerErr((current) => new Map(current).set(id, t.editErrNotAccepted));
1639
+ }
1640
+ }).catch(() => {
1641
+ setSteerErr((current) => new Map(current).set(id, t.editErrCommandFailed));
1642
+ }).finally(() => {
1643
+ setSteering((current) => {
1644
+ const next2 = new Set(current);
1645
+ next2.delete(id);
1646
+ return next2;
1647
+ });
1648
+ });
1649
+ };
1650
+ const startEdit = (item) => {
1651
+ setEditingId(item.id);
1652
+ setEditingText(item.prompt);
1653
+ setEditErr(null);
1654
+ };
1655
+ const cancelEdit = () => {
1656
+ setEditingId(null);
1657
+ setEditErr(null);
1658
+ setSaving(false);
1659
+ };
1660
+ const submitEdit = (id) => {
1661
+ const trimmed = editingText.trim();
1662
+ if (trimmed.length === 0) {
1663
+ setEditErr(t.editErrEmpty);
1664
+ return;
1665
+ }
1666
+ suppress(id);
1667
+ setSaving(true);
1668
+ setEditErr(null);
1669
+ void callCommand(sessionId, editLine(id, trimmed)).then((ok) => {
1670
+ if (!ok) setEditErr(format(t.editErrFailed, { message: t.editErrNotAccepted }));
1671
+ else cancelEdit();
1672
+ }).catch(() => {
1673
+ setEditErr(format(t.editErrFailed, { message: t.editErrCommandFailed }));
1674
+ }).finally(() => setSaving(false));
1675
+ };
1676
+ const renderRow = (item) => {
1677
+ const epoch = Date.parse(item.scheduled_at);
1678
+ const state = epoch <= now ? "overdue" : "scheduled";
1679
+ if (editingId === item.id) {
1680
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "ss-dock-row ss-editing", "data-ss-dock-row": item.id, children: [
1681
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1682
+ "input",
1683
+ {
1684
+ className: "ss-dock-edit-input",
1685
+ value: editingText,
1686
+ maxLength: 1e3,
1687
+ autoFocus: true,
1688
+ disabled: saving,
1689
+ onChange: (event) => setEditingText(event.target.value),
1690
+ onKeyDown: (event) => {
1691
+ if (event.key === "Enter") submitEdit(item.id);
1692
+ else if (event.key === "Escape") cancelEdit();
1693
+ event.stopPropagation();
1694
+ },
1695
+ "aria-label": `${t.editTask}: ${item.prompt}`
1696
+ }
1697
+ ),
1698
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1699
+ "button",
1700
+ {
1701
+ type: "button",
1702
+ className: "ss-dock-row-save",
1703
+ disabled: saving || editingText.trim().length === 0,
1704
+ onClick: () => submitEdit(item.id),
1705
+ children: saving ? "\u2026" : t.editSave
1706
+ }
1707
+ ),
1708
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "ss-dock-action", onClick: cancelEdit, "aria-label": t.editCancel, children: "\u2715" }),
1709
+ editErr !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-dock-edit-err", children: editErr })
1710
+ ] }, item.id);
1711
+ }
1712
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "ss-dock-row", "data-ss-dock-row": item.id, children: [
1713
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-dock-preview", children: [
1714
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-dock-row-line", children: [
1715
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-dock-row-time", children: formatHhmm(epoch, detectTimeZone()) }),
1716
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `ss-dock-row-countdown${state === "overdue" ? " ss-due" : ""}`, children: state === "overdue" ? t.dueAnyMoment : `${t.countdownLeft} ${formatCountdown(epoch - now)}` }),
1717
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-dock-row-prompt", title: item.prompt, children: item.prompt })
1718
+ ] }),
1719
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MultiBar, { item, now, firstSeen: firstSeenRef.current, t }),
1720
+ steerErr.get(item.id) !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-dock-steer-err", children: steerErr.get(item.id) })
1721
+ ] }),
1722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-dock-actions", children: [
1723
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1724
+ "button",
1725
+ {
1726
+ type: "button",
1727
+ className: "ss-dock-action",
1728
+ title: t.editTask,
1729
+ "aria-label": t.editTask,
1730
+ onClick: () => startEdit(item),
1731
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QueueEditIcon, {})
1732
+ }
1733
+ ),
1734
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1735
+ "button",
1736
+ {
1737
+ type: "button",
1738
+ className: "ss-dock-action",
1739
+ title: t.deleteTask,
1740
+ "aria-label": t.deleteTask,
1741
+ onClick: () => handleCancel(item.id),
1742
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QueueDeleteIcon, {})
1743
+ }
1744
+ ),
1745
+ item.kind !== "every" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1746
+ "button",
1747
+ {
1748
+ type: "button",
1749
+ className: `ss-dock-action ss-dock-action-steer${steering.has(item.id) ? " ss-pending" : ""}${steerDone.has(item.id) ? " ss-done" : ""}`,
1750
+ title: steerDone.has(item.id) ? t.steerAlreadySent : t.steerTask,
1751
+ "aria-label": steerDone.has(item.id) ? t.steerAlreadySent : t.steerTask,
1752
+ disabled: steering.has(item.id) || steerDone.has(item.id),
1753
+ onClick: () => handleSteer(item),
1754
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QueueSendIcon, {})
1755
+ }
1756
+ )
1757
+ ] })
1758
+ ] }, item.id);
1759
+ };
1760
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ss-dock-root", "data-ss-dock": "", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-dock-body", children: [
1761
+ schedules.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1762
+ "button",
1763
+ {
1764
+ type: "button",
1765
+ className: "ss-dock-header",
1766
+ "aria-expanded": !collapsed,
1767
+ onClick: () => setCollapsed((value) => !value),
1768
+ children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-dock-lead", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QueueClockIcon, {}) }),
1770
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-dock-count", children: format(t.panelCount, { n: schedules.length }) }),
1771
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: expanded ? "ss-dock-chevron ss-open" : "ss-dock-chevron", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(QueueChevronIcon, {}) })
1772
+ ]
1773
+ }
1774
+ ),
1775
+ expanded && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "ss-dock-list", children: schedules.map(renderRow) })
1776
+ ] }) });
1777
+ }
1778
+ function MultiBar({
1779
+ item,
1780
+ now,
1781
+ firstSeen,
1782
+ t
1783
+ }) {
1784
+ const { totalRatio, count } = barSegments(item, now, firstSeen);
1785
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1786
+ "div",
1787
+ {
1788
+ className: `ss-dock-bars${count > 4 ? " ss-dock-bars-compact" : ""}`,
1789
+ role: "progressbar",
1790
+ "aria-valuemin": 0,
1791
+ "aria-valuemax": 100,
1792
+ "aria-valuenow": Math.round(totalRatio * 100),
1793
+ "aria-label": t.panelTitle,
1794
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-dock-bar", children: [
1795
+ count > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1796
+ "div",
1797
+ {
1798
+ className: "ss-dock-bar-divider",
1799
+ style: {
1800
+ backgroundImage: `repeating-linear-gradient(to right, transparent 0, transparent calc((100% - 1px) / ${count}), var(--ss-bar-divider, rgba(0,0,0,0.45)) calc((100% - 1px) / ${count}), var(--ss-bar-divider, rgba(0,0,0,0.45)) calc(100% / ${count}))`
1801
+ }
1802
+ }
1803
+ ),
1804
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ss-dock-bar-fill", style: { width: `${totalRatio * 100}%` } })
1805
+ ] })
1806
+ }
1807
+ );
1808
+ }
1809
+ function QueueClockIcon() {
1810
+ return /* @__PURE__ */ (0, import_jsx_runtime5.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_runtime5.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" }) });
1811
+ }
1812
+ function QueueChevronIcon() {
1813
+ return /* @__PURE__ */ (0, import_jsx_runtime5.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_runtime5.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" }) });
1814
+ }
1815
+ function QueueEditIcon() {
1816
+ return /* @__PURE__ */ (0, import_jsx_runtime5.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_runtime5.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" }) });
1817
+ }
1818
+ function QueueDeleteIcon() {
1819
+ return /* @__PURE__ */ (0, import_jsx_runtime5.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_runtime5.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" }) });
1820
+ }
1821
+ function QueueSendIcon() {
1822
+ return /* @__PURE__ */ (0, import_jsx_runtime5.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_runtime5.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" }) });
1823
+ }
1824
+
1825
+ // src/client/components/SchedulerSettingsCard.tsx
1826
+ var import_react9 = require("react");
1827
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1828
+ var FIELDS = [
1829
+ { kind: "boolean", key: "showButton", labelKey: "showButtonLabel", hintKey: "showButtonHint", defaultValue: true },
1830
+ { kind: "number", key: "maxSchedules", labelKey: "maxSchedulesLabel", hintKey: "maxSchedulesHint", defaultValue: 100 },
1831
+ { kind: "time", key: "workStart", labelKey: "workStartLabel", hintKey: "smartWindowHint" },
1832
+ { kind: "time", key: "workEnd", labelKey: "workEndLabel" },
1833
+ { kind: "time", key: "lunchStart", labelKey: "lunchStartLabel" },
1834
+ { kind: "time", key: "lunchEnd", labelKey: "lunchEndLabel" },
1835
+ { kind: "time", key: "eveningEnd", labelKey: "eveningEndLabel" }
1836
+ ];
1837
+ function ChevronDown({ className }) {
1838
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", className, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M3.5 5.25 7 8.75l3.5-3.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
1839
+ }
1840
+ function SchedulerSettingsCard({ scope, t }) {
1841
+ const [snapshot, setSnapshot] = (0, import_react9.useState)(() => scope.getSnapshot());
1842
+ const [pending, setPending] = (0, import_react9.useState)({});
1843
+ const [saving, setSaving] = (0, import_react9.useState)(false);
1844
+ const [failed, setFailed] = (0, import_react9.useState)(false);
1845
+ const [open, setOpen] = (0, import_react9.useState)(false);
1846
+ (0, import_react9.useEffect)(() => {
1847
+ const initial = scope.getSnapshot();
1848
+ setSnapshot(initial);
1849
+ setPending({});
1850
+ if (initial.status === "ready") setFailed(false);
1851
+ }, [scope]);
1852
+ (0, import_react9.useEffect)(() => {
1853
+ return scope.subscribe(() => {
1854
+ const snap = scope.getSnapshot();
1855
+ setSnapshot(snap);
1856
+ if (snap.status === "ready") {
1857
+ setFailed(false);
1858
+ }
1859
+ });
1860
+ }, [scope]);
1861
+ if (snapshot.status === "unavailable") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, {});
1862
+ if (snapshot.status !== "ready") {
1863
+ if (snapshot.status === "loading") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ss-hint", children: t("loading") });
1864
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ss-hint", children: t("unavailable") });
1865
+ }
1866
+ const value = snapshot.value ?? {};
1867
+ const base = snapshot.base ?? {};
1868
+ const user = snapshot.user;
1869
+ const writable = snapshot.writable === true;
1870
+ const isUserOverridden = (key) => user !== void 0 && typeof user === "object" && Object.prototype.hasOwnProperty.call(user, key);
1871
+ const displayValue = (field) => {
1872
+ const p = pending[field.key];
1873
+ if (p !== void 0) return p.value;
1874
+ if (field.kind === "number") {
1875
+ const v2 = value[field.key];
1876
+ return v2 === void 0 ? "" : String(v2);
1877
+ }
1878
+ if (field.kind === "boolean") {
1879
+ const v2 = value[field.key];
1880
+ return v2 === false ? "false" : "true";
1881
+ }
1882
+ const v = value[field.key];
1883
+ return v ?? "";
1884
+ };
1885
+ const baselineValue = (field) => {
1886
+ if (field.kind === "number") {
1887
+ const v2 = base[field.key];
1888
+ return String(v2 ?? field.defaultValue);
1889
+ }
1890
+ if (field.kind === "boolean") {
1891
+ const v2 = base[field.key];
1892
+ return v2 === false ? "false" : "true";
1893
+ }
1894
+ const v = base[field.key];
1895
+ return v ?? "";
1896
+ };
1897
+ const dirty = Object.keys(pending).length > 0 && Object.values(pending).some((p) => p !== void 0);
1898
+ const validate = (field, raw) => {
1899
+ if (field.kind === "number") {
1900
+ if (raw.trim().length === 0) return t("invalidLabel");
1901
+ const num = Number(raw);
1902
+ if (!Number.isFinite(num) || num < 1 || !Number.isInteger(num)) return t("invalidLabel");
1903
+ return null;
1904
+ }
1905
+ if (field.kind === "boolean") return null;
1906
+ if (raw.trim().length === 0) return null;
1907
+ if (!isValidHhmm(raw)) return t("invalidTimeLabel");
1908
+ return null;
1909
+ };
1910
+ const setFieldPending = (key, value2, reset) => {
1911
+ setPending((current) => ({ ...current, [key]: { value: value2, reset } }));
1912
+ };
1913
+ const handleFieldEdit = (field, raw) => {
1914
+ setFieldPending(field.key, raw, false);
1915
+ setFailed(false);
1916
+ };
1917
+ const handleResetField = (field) => {
1918
+ setFieldPending(field.key, baselineValue(field), true);
1919
+ setFailed(false);
1920
+ };
1921
+ const handleDiscard = (0, import_react9.useCallback)(() => {
1922
+ setPending({});
1923
+ setFailed(false);
1924
+ }, []);
1925
+ const handleSave = (0, import_react9.useCallback)(async () => {
1926
+ if (!dirty || saving || !writable) return;
1927
+ setSaving(true);
1928
+ setFailed(false);
1929
+ try {
1930
+ for (const [key, p] of Object.entries(pending)) {
1931
+ if (p === void 0) continue;
1932
+ if (p.reset) {
1933
+ await scope.unset(key);
1934
+ continue;
1935
+ }
1936
+ if (p.value.trim().length === 0) {
1937
+ await scope.unset(key);
1938
+ continue;
1939
+ }
1940
+ const field = FIELDS.find((entry) => entry.key === key);
1941
+ const coerced = field?.kind === "number" ? Number(p.value) : field?.kind === "boolean" ? p.value === "true" : p.value;
1942
+ await scope.set(key, coerced);
1943
+ }
1944
+ setPending({});
1945
+ } catch {
1946
+ setFailed(true);
1947
+ } finally {
1948
+ setSaving(false);
1949
+ }
1950
+ }, [scope, pending, dirty, saving, writable]);
1951
+ const invalidField = (field) => {
1952
+ const p = pending[field.key];
1953
+ if (p === void 0) return null;
1954
+ return validate(field, p.value);
1955
+ };
1956
+ const anyInvalid = FIELDS.some((field) => invalidField(field) !== null);
1957
+ const saveDisabled = !dirty || saving || !writable || anyInvalid;
1958
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { className: open ? "ss-card ss-cardOpen" : "ss-card", children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1960
+ "button",
1961
+ {
1962
+ type: "button",
1963
+ className: "ss-card-header",
1964
+ "aria-expanded": open,
1965
+ "aria-label": `${t(open ? "collapse" : "expand")}: ${t("title")}`,
1966
+ onClick: () => setOpen(!open),
1967
+ children: [
1968
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ss-card-headText", children: [
1969
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-card-name", children: t("title") }),
1970
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-card-description", children: t("description") })
1971
+ ] }),
1972
+ dirty && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-card-pending", children: t("pendingBadge") }),
1973
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronDown, { className: open ? "ss-card-chevron ss-card-chevronOpen" : "ss-card-chevron" })
1974
+ ]
1975
+ }
1976
+ ),
1977
+ open ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ss-card-body", children: [
1978
+ !writable && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "ss-card-readOnly", role: "status", children: t("readOnly") }),
1979
+ FIELDS.map((field) => {
1980
+ const invalid = invalidField(field);
1981
+ const isTime = field.kind === "time";
1982
+ const labelKey = field.labelKey;
1983
+ const hintKey = field.hintKey;
1984
+ const userHas = isUserOverridden(field.key);
1985
+ const fieldId = `ss-${field.key}`;
1986
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ss-field", children: [
1987
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ss-card-fieldHead", children: [
1988
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { htmlFor: fieldId, className: "ss-card-label", children: t(labelKey) }),
1989
+ userHas && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ss-card-badges", children: [
1990
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ss-card-badge", children: t("overriddenLabel") }),
1991
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1992
+ "button",
1993
+ {
1994
+ type: "button",
1995
+ className: "ss-card-reset",
1996
+ disabled: !writable || saving,
1997
+ onClick: () => handleResetField(field),
1998
+ children: t("resetLabel")
1999
+ }
2000
+ )
2001
+ ] })
2002
+ ] }),
2003
+ field.kind === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2004
+ "select",
2005
+ {
2006
+ id: fieldId,
2007
+ className: invalid !== null ? "ss-input ss-inputInvalid" : "ss-input",
2008
+ "aria-invalid": invalid !== null || void 0,
2009
+ value: displayValue(field) === "false" ? "false" : "true",
2010
+ disabled: !writable || saving,
2011
+ onChange: (event) => handleFieldEdit(field, event.target.value),
2012
+ children: [
2013
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "true", children: t("optionOn") }),
2014
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "false", children: t("optionOff") })
2015
+ ]
2016
+ }
2017
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2018
+ "input",
2019
+ {
2020
+ id: fieldId,
2021
+ type: isTime ? "time" : "text",
2022
+ inputMode: isTime ? void 0 : "numeric",
2023
+ className: invalid !== null ? "ss-input ss-inputInvalid" : "ss-input",
2024
+ "aria-invalid": invalid !== null || void 0,
2025
+ value: displayValue(field),
2026
+ placeholder: field.kind === "number" ? String(field.defaultValue) : void 0,
2027
+ disabled: !writable || saving,
2028
+ onChange: (event) => handleFieldEdit(field, event.target.value)
2029
+ }
2030
+ ),
2031
+ invalid !== null || hintKey !== void 0 && t(hintKey).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: invalid !== null ? "ss-card-invalid" : "ss-hint", children: invalid !== null ? invalid : t(hintKey) }) : null
2032
+ ] }, field.key);
2033
+ }),
2034
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ss-card-footer", children: [
2035
+ failed && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "ss-card-failed", role: "status", children: t("saveFailed") }),
2036
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2037
+ "button",
2038
+ {
2039
+ type: "button",
2040
+ className: "ss-card-discard",
2041
+ disabled: !dirty || saving,
2042
+ onClick: handleDiscard,
2043
+ children: t("discard")
2044
+ }
2045
+ ),
2046
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2047
+ "button",
2048
+ {
2049
+ type: "button",
2050
+ className: "ss-card-save",
2051
+ disabled: saveDisabled,
2052
+ onClick: () => void handleSave(),
2053
+ children: saving ? t("saving") : t("save")
2054
+ }
2055
+ )
2056
+ ] })
2057
+ ] }) : null
2058
+ ] });
2059
+ }
2060
+
2061
+ // src/presence.ts
2062
+ var PRESENCE_URGENT_WINDOW_MS = 5 * 6e4;
2063
+ function summarizeSchedules(schedules) {
2064
+ let nextAt;
2065
+ let count = 0;
2066
+ for (const item of schedules ?? []) {
2067
+ const raw = item.scheduled_at ?? item.scheduledAt;
2068
+ if (raw === void 0) continue;
2069
+ const epoch = Date.parse(raw);
2070
+ if (!Number.isFinite(epoch)) continue;
2071
+ count += 1;
2072
+ if (nextAt === void 0 || epoch < nextAt) nextAt = epoch;
2073
+ }
2074
+ return nextAt === void 0 ? { count: 0 } : { count, nextAt };
2075
+ }
2076
+ function badgeStateFor(nextAt, now) {
2077
+ if (nextAt === void 0) return "scheduled";
2078
+ const diff = nextAt - now;
2079
+ if (diff <= 0) return "overdue";
2080
+ if (diff <= PRESENCE_URGENT_WINDOW_MS) return "urgent";
2081
+ return "scheduled";
2082
+ }
2083
+ function startOfDay(epoch) {
2084
+ const d = new Date(epoch);
2085
+ d.setHours(0, 0, 0, 0);
2086
+ return d.getTime();
2087
+ }
2088
+ function relativeFireLabel(nextAt, now, labels, formatHhmm3, formatDate2) {
2089
+ const diff = nextAt - now;
2090
+ if (diff <= 0) return labels.clock(formatHhmm3(nextAt));
2091
+ if (diff <= 6e4) return labels.imminent;
2092
+ if (diff < 36e5) return labels.minutes(Math.max(1, Math.round(diff / 6e4)));
2093
+ const dayDiff = Math.round((startOfDay(nextAt) - startOfDay(now)) / 864e5);
2094
+ const time = formatHhmm3(nextAt);
2095
+ if (dayDiff <= 0) return labels.hours(Math.max(1, Math.round(diff / 36e5)));
2096
+ if (dayDiff === 1) return labels.tomorrow(time);
2097
+ return labels.date(formatDate2(nextAt), time);
2098
+ }
2099
+
2100
+ // src/client/session-presence.ts
2101
+ var BADGE_CLASS = "ss-presence";
2102
+ var FIBER_MAX_DEPTH = 8;
2103
+ var SCAN_DEBOUNCE_MS = 60;
2104
+ var STATE_TICK_MS = 3e4;
2105
+ var REFRESH_INTERVAL_MS = 6e4;
2106
+ var CLOCK_SVG = '<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>';
2107
+ var hhmmFormatter = new Intl.DateTimeFormat(void 0, { hour: "2-digit", minute: "2-digit", hour12: false });
2108
+ var dateFormatter = new Intl.DateTimeFormat(void 0, { month: "short", day: "numeric" });
2109
+ var formatHhmm2 = (epoch) => hhmmFormatter.format(epoch);
2110
+ var formatDate = (epoch) => dateFormatter.format(epoch);
2111
+ function sessionIdOfRow(row) {
2112
+ const host = row;
2113
+ const fiberKey = Object.keys(host).find((key) => key.startsWith("__reactFiber$"));
2114
+ if (fiberKey === void 0) return void 0;
2115
+ let fiber = host[fiberKey];
2116
+ for (let depth = 0; fiber !== void 0 && depth < FIBER_MAX_DEPTH; depth += 1, fiber = fiber.return) {
2117
+ const node = fiber.memoizedProps?.node;
2118
+ const id = node?.id;
2119
+ if (typeof id === "string" && id.startsWith("session-")) return id;
2120
+ }
2121
+ return void 0;
2122
+ }
2123
+ function buildIndex(state) {
2124
+ const index = /* @__PURE__ */ new Map();
2125
+ for (const [sessionId, summary] of Object.entries(state.byId)) {
2126
+ const schedules = summary.projectionValues?.["userSchedules"]?.schedules;
2127
+ const entry = summarizeSchedules(schedules);
2128
+ if (entry.count > 0) index.set(sessionId, entry);
2129
+ }
2130
+ return index;
2131
+ }
2132
+ function findBadge(row) {
2133
+ for (const child of Array.from(row.children)) {
2134
+ if (child.classList.contains(BADGE_CLASS)) return child;
2135
+ const inside = child.querySelector(`:scope > .${BADGE_CLASS}`);
2136
+ if (inside !== null) return inside;
2137
+ }
2138
+ return null;
2139
+ }
2140
+ function labelsFor(dict) {
2141
+ return {
2142
+ imminent: dict.dueAnyMoment,
2143
+ minutes: (m) => format(dict.relMinutes, { m }),
2144
+ hours: (h) => format(dict.relHours, { h }),
2145
+ tomorrow: (time) => format(dict.relTomorrow, { time }),
2146
+ clock: (time) => format(dict.relClock, { time }),
2147
+ date: (date, time) => format(dict.relDate, { date, time })
2148
+ };
2149
+ }
2150
+ function mountSessionPresence(options) {
2151
+ const { sessions, locale } = options;
2152
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") {
2153
+ return () => void 0;
2154
+ }
2155
+ let index = /* @__PURE__ */ new Map();
2156
+ let scanTimer;
2157
+ let disposed = false;
2158
+ const dictForNow = () => dictFor(locale?.getSnapshot().active);
2159
+ const scan = () => {
2160
+ if (disposed) return;
2161
+ index = buildIndex(sessions.list.getSnapshot());
2162
+ const dict = dictForNow();
2163
+ const now = Date.now();
2164
+ for (const row of Array.from(document.querySelectorAll('[role="treeitem"][draggable="true"]'))) {
2165
+ if (row.hasAttribute("aria-expanded")) continue;
2166
+ applyBadge(row, now, dict);
2167
+ }
2168
+ };
2169
+ const scheduleScan = () => {
2170
+ if (disposed || scanTimer !== void 0) return;
2171
+ scanTimer = setTimeout(() => {
2172
+ scanTimer = void 0;
2173
+ scan();
2174
+ }, SCAN_DEBOUNCE_MS);
2175
+ };
2176
+ function applyBadge(row, now, dict) {
2177
+ const sessionId = sessionIdOfRow(row);
2178
+ if (sessionId === void 0) return;
2179
+ const entry = index.get(sessionId);
2180
+ const existing = findBadge(row);
2181
+ if (entry === void 0) {
2182
+ existing?.remove();
2183
+ return;
2184
+ }
2185
+ const state = badgeStateFor(entry.nextAt, now);
2186
+ const time = entry.nextAt === void 0 ? "" : relativeFireLabel(entry.nextAt, now, labelsFor(dict), formatHhmm2, formatDate);
2187
+ const aria = state === "overdue" ? format(dict.presenceOverdueAria, { n: entry.count }) : format(dict.presenceBadgeAria, { n: entry.count, time });
2188
+ const tooltip = state === "overdue" ? format(dict.presenceOverdueTooltip, { n: entry.count }) : format(dict.presenceTooltip, { n: entry.count, time });
2189
+ if (existing !== null) {
2190
+ if (existing.dataset.state !== state) existing.dataset.state = state;
2191
+ if (existing.getAttribute("aria-label") !== aria) existing.setAttribute("aria-label", aria);
2192
+ if (existing.getAttribute("title") !== tooltip) existing.setAttribute("title", tooltip);
2193
+ return;
2194
+ }
2195
+ const slot = row.firstElementChild;
2196
+ if (slot === null) return;
2197
+ const beside = slot.querySelector("svg") !== null;
2198
+ const badge = document.createElement("span");
2199
+ badge.className = beside ? `${BADGE_CLASS} ${BADGE_CLASS}-beside` : BADGE_CLASS;
2200
+ badge.dataset.state = state;
2201
+ badge.setAttribute("role", "img");
2202
+ badge.setAttribute("aria-label", aria);
2203
+ badge.setAttribute("title", tooltip);
2204
+ badge.innerHTML = CLOCK_SVG;
2205
+ if (beside) slot.after(badge);
2206
+ else slot.append(badge);
2207
+ }
2208
+ const unsubscribeList = sessions.list.subscribe(scheduleScan);
2209
+ const unsubscribeLocale = locale?.subscribe(scheduleScan);
2210
+ const observer = new MutationObserver(scheduleScan);
2211
+ observer.observe(document.body, { childList: true, subtree: true });
2212
+ const refreshNow = () => {
2213
+ if (disposed) return;
2214
+ try {
2215
+ void sessions.refresh().catch(() => void 0);
2216
+ } catch {
2217
+ }
2218
+ };
2219
+ const onVisible = () => {
2220
+ if (document.visibilityState === "visible") {
2221
+ refreshNow();
2222
+ scheduleScan();
2223
+ }
2224
+ };
2225
+ document.addEventListener("visibilitychange", onVisible);
2226
+ window.addEventListener("online", onVisible);
2227
+ const stateTick = setInterval(scheduleScan, STATE_TICK_MS);
2228
+ const refreshTick = setInterval(() => {
2229
+ if (document.visibilityState === "visible") refreshNow();
2230
+ }, REFRESH_INTERVAL_MS);
2231
+ refreshNow();
2232
+ scheduleScan();
2233
+ return () => {
2234
+ disposed = true;
2235
+ if (scanTimer !== void 0) clearTimeout(scanTimer);
2236
+ clearInterval(stateTick);
2237
+ clearInterval(refreshTick);
2238
+ observer.disconnect();
2239
+ unsubscribeList();
2240
+ unsubscribeLocale?.();
2241
+ document.removeEventListener("visibilitychange", onVisible);
2242
+ window.removeEventListener("online", onVisible);
2243
+ for (const badge of Array.from(document.querySelectorAll(`.${BADGE_CLASS}`))) badge.remove();
2244
+ };
2245
+ }
2246
+
2247
+ // src/client/index.tsx
2248
+ var INPUT_RIGHT_SLOT = "conversation.input.right";
2249
+ var inject = ["slots", "sessions", "conversation", "settingsScope", "locale"];
2250
+ var NS = "settings.plugins.later";
2251
+ var SETTINGS_NS = "dsh-later";
2252
+ function apply(ctx) {
2253
+ const slots = ctx.get("slots");
2254
+ const sessions = ctx.get("sessions");
2255
+ const settingsScope = ctx.get("settingsScope");
2256
+ const locale = ctx.get("locale");
2257
+ if (slots === void 0 || sessions === void 0) return;
2258
+ ctx.effect(() => injectStyles(), "dsh-later: styles");
2259
+ ctx.effect(() => mountReminderToastHost(), "dsh-later: toast host");
2260
+ ctx.effect(
2261
+ () => mountSessionPresence({
2262
+ sessions,
2263
+ ...locale === void 0 ? {} : { locale }
2264
+ }),
2265
+ "dsh-later: presence"
2266
+ );
2267
+ if (locale !== void 0) {
2268
+ ctx.effect(() => locale.register(NS, {
2269
+ zh: {
2270
+ title: "\u5B9A\u65F6\u63D0\u9192",
2271
+ description: "\u914D\u7F6E\u4F1A\u8BDD\u5185\u5B9A\u65F6\u63D0\u9192\u7684\u53C2\u6570\u3002",
2272
+ pendingBadge: "\u672A\u4FDD\u5B58",
2273
+ expand: "\u5C55\u5F00\u8BBE\u7F6E",
2274
+ collapse: "\u6536\u8D77\u8BBE\u7F6E",
2275
+ readOnly: "\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002",
2276
+ showButtonLabel: "\u663E\u793A\u5B9A\u65F6\u6309\u94AE",
2277
+ 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",
2278
+ optionOn: "\u663E\u793A",
2279
+ optionOff: "\u9690\u85CF",
2280
+ maxSchedulesLabel: "\u5355\u4F1A\u8BDD\u4EFB\u52A1\u4E0A\u9650",
2281
+ maxSchedulesHint: "\u5355\u4E2A\u4F1A\u8BDD\u5185\u5141\u8BB8\u521B\u5EFA\u7684\u6700\u5927\u5B9A\u65F6\u4EFB\u52A1\u6570\u91CF\u3002",
2282
+ smartWindowHint: "\u7528\u4E8E\u300C\u5DE5\u4F5C\u65F6\u95F4\u300D\u82AF\u7247\u4E0E\u667A\u80FD\u6A21\u5F0F\u81EA\u52A8\u6392\u671F\u3002",
2283
+ workStartLabel: "\u5DE5\u4F5C\u65F6\u95F4\u5F00\u59CB",
2284
+ workEndLabel: "\u5DE5\u4F5C\u65F6\u95F4\u7ED3\u675F\uFF08\u665A\u95F4\u5F00\u59CB\uFF09",
2285
+ lunchStartLabel: "\u5348\u4F11\u5F00\u59CB",
2286
+ lunchEndLabel: "\u5348\u4F11\u7ED3\u675F",
2287
+ eveningEndLabel: "\u591C\u95F4\u9759\u9ED8\u8D77\u70B9",
2288
+ overriddenLabel: "\u5DF2\u8986\u76D6",
2289
+ resetLabel: "\u91CD\u7F6E",
2290
+ invalidLabel: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u6570\u5B57\u3002",
2291
+ invalidTimeLabel: "\u8BF7\u4F7F\u7528 HH:mm 24 \u5C0F\u65F6\u5236\u65F6\u95F4\u3002",
2292
+ save: "\u4FDD\u5B58",
2293
+ saving: "\u4FDD\u5B58\u4E2D\u2026",
2294
+ discard: "\u653E\u5F03\u4FEE\u6539",
2295
+ saveFailed: "\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002",
2296
+ loading: "\u52A0\u8F7D\u4E2D\u2026",
2297
+ unavailable: "\u8BBE\u7F6E\u6682\u4E0D\u53EF\u7528\u3002"
2298
+ },
2299
+ en: {
2300
+ title: "Session Scheduler",
2301
+ description: "Configure in-session scheduled reminders.",
2302
+ pendingBadge: "Unsaved",
2303
+ expand: "Show settings",
2304
+ collapse: "Hide settings",
2305
+ readOnly: "Settings for this deployment are read-only.",
2306
+ showButtonLabel: "Show schedule button",
2307
+ showButtonHint: "Hide the clock button on the input bar. Existing reminders keep firing and stay visible in the dock below the composer.",
2308
+ optionOn: "Show",
2309
+ optionOff: "Hide",
2310
+ maxSchedulesLabel: "Max schedules per session",
2311
+ maxSchedulesHint: "Maximum number of scheduled reminders allowed per session.",
2312
+ smartWindowHint: "Used by the Work-hours chip and smart auto-scheduling.",
2313
+ workStartLabel: "Work day start",
2314
+ workEndLabel: "Work day end (evening start)",
2315
+ lunchStartLabel: "Lunch break start",
2316
+ lunchEndLabel: "Lunch break end",
2317
+ eveningEndLabel: "Night quiet starts at",
2318
+ overriddenLabel: "Overridden",
2319
+ resetLabel: "Reset",
2320
+ invalidLabel: "Please enter a valid number.",
2321
+ invalidTimeLabel: "Use 24-hour HH:mm format.",
2322
+ save: "Save",
2323
+ saving: "Saving\u2026",
2324
+ discard: "Discard",
2325
+ saveFailed: "Save failed, please try again.",
2326
+ loading: "Loading\u2026",
2327
+ unavailable: "Settings unavailable."
2328
+ }
2329
+ }), "dsh-later: locale");
2330
+ }
2331
+ const callCommand = async (sessionId, line) => {
2332
+ const actx = sessions.scope(sessionId);
2333
+ const face = actx === void 0 ? void 0 : sessions.sessionOf(actx);
2334
+ if (face === void 0) return false;
2335
+ try {
2336
+ const result = await face.command(line);
2337
+ return result?.ok === true && result.value?.matched === true;
2338
+ } catch {
2339
+ return false;
2340
+ }
2341
+ };
2342
+ const schedulerScope = settingsScope === void 0 ? void 0 : settingsScope.bind({ namespace: SETTINGS_NS });
2343
+ ctx.slots.inject(
2344
+ INPUT_RIGHT_SLOT,
2345
+ () => ctx.slots.register(
2346
+ {
2347
+ name: INPUT_RIGHT_SLOT,
2348
+ id: "later",
2349
+ order: 50,
2350
+ inject: () => ({ callCommand, locale, schedulerScope })
2351
+ },
2352
+ SchedButton
2353
+ )
2354
+ );
2355
+ ctx.slots.inject(
2356
+ "conversation.input.dock",
2357
+ () => ctx.slots.register(
2358
+ {
2359
+ name: "conversation.input.dock",
2360
+ id: "later-dock",
2361
+ order: 30,
2362
+ inject: () => ({ callCommand, locale })
2363
+ },
2364
+ ScheduleDock
2365
+ )
2366
+ );
2367
+ if (settingsScope !== void 0 && locale !== void 0) {
2368
+ ctx.slots.inject("settings.plugin.item", function* () {
2369
+ yield ctx.slots.register({
2370
+ name: "settings.plugin.item",
2371
+ key: SETTINGS_NS,
2372
+ locale: NS,
2373
+ inject: () => ({
2374
+ scope: settingsScope.bind({ namespace: SETTINGS_NS }),
2375
+ t: locale.bind(NS)
2376
+ })
2377
+ }, SchedulerSettingsCard);
2378
+ });
2379
+ }
2380
+ }
2381
+
2382
+ return module.exports;
2383
+ }
2384
+ });