dsh-global-todo 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +3 -1
- package/README.zh.md +3 -1
- package/docs/screenshots/panel-detail.png +0 -0
- package/docs/screenshots/panel-select.png +0 -0
- package/docs/screenshots/panel-settings.png +0 -0
- package/docs/screenshots/panel.png +0 -0
- package/lib/client.js +307 -139
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
## [0.6.5] — 2026-10-20
|
|
2
|
+
|
|
3
|
+
The three loose ends the spec called out but the earlier passes left behind.
|
|
4
|
+
|
|
5
|
+
- **Five type sizes, for real.** 12.5/11.5/10.5 collapse into 12 and 11, so the panel now uses
|
|
6
|
+
exactly 20 / 14 / 13 / 12 / 11 — one ladder instead of eight near-identical steps.
|
|
7
|
+
- **Group actions move behind one kebab.** `Collect` and `Exempt` stop competing with the group
|
|
8
|
+
title; an exemption keeps a small always-visible mute mark (it is a state, not an action) and
|
|
9
|
+
the menu states both intents in words (`Collect from this conversation`, `Mute this workspace`).
|
|
10
|
+
- **Escape closes the innermost layer.** Settings, then the add form, then the open row — and a
|
|
11
|
+
click outside the settings card dismisses it, matching every other popover. The gear toggles.
|
|
12
|
+
|
|
13
|
+
## [0.6.4] — 2026-10-20
|
|
14
|
+
|
|
15
|
+
The settings panel, rebuilt as an inspector instead of a stack of labelled rows.
|
|
16
|
+
|
|
17
|
+
- **Header and footer, finally.** A title row with a close button, three labelled groups divided
|
|
18
|
+
by hairlines, and a save row pinned at the bottom — the token field no longer pushes the save
|
|
19
|
+
buttons into the middle of the form with a field stranded beneath them.
|
|
20
|
+
- **Every field says what it does.** Help text sits under each label (`settings.*Hint`) rather than
|
|
21
|
+
being implied by the label; the label-and-control grid keeps number inputs aligned at a fixed
|
|
22
|
+
76px right-aligned box.
|
|
23
|
+
- **Ingest fields dim when ingest is off**, and `New token` becomes a quiet text link on the token
|
|
24
|
+
label row (`Rotate token`) instead of a third equal-weight button.
|
|
25
|
+
- **About section** shows the data file and version on labelled rows; the card sizes to its content
|
|
26
|
+
and scrolls internally when the window is short.
|
|
27
|
+
- `npm run screenshots` now also photographs a settings variant, so the README image is
|
|
28
|
+
reproducible instead of hand-made.
|
|
29
|
+
|
|
1
30
|
## [0.6.3] — 2026-10-20
|
|
2
31
|
|
|
3
32
|
States that were still speaking to nobody.
|
package/README.md
CHANGED
|
@@ -33,6 +33,8 @@ one record:
|
|
|
33
33
|
|
|
34
34
|

|
|
35
35
|
|
|
36
|
+

|
|
37
|
+
|
|
36
38
|
These are produced by `npm run screenshots` (which chains `npm run preview`): the real client
|
|
37
39
|
half is rendered to static HTML with no Host and no dependencies, then photographed at 1280×720
|
|
38
40
|
into `docs/screenshots/`. Playwright is not a dependency — the tool borrows the copy a DSH
|
|
@@ -339,7 +341,7 @@ npm run preview # render the panel to preview/panel.html (+ detail and sel
|
|
|
339
341
|
npm run screenshots # re-photograph those previews into docs/screenshots/, 1280×720
|
|
340
342
|
```
|
|
341
343
|
|
|
342
|
-
The
|
|
344
|
+
The PNGs under `docs/screenshots/` ship with the package and are embedded in both
|
|
343
345
|
READMEs, so re-run `npm run screenshots` whenever the panel UI changes.
|
|
344
346
|
|
|
345
347
|
```text
|
package/README.zh.md
CHANGED
|
@@ -30,6 +30,8 @@ DSH 自带的 `todo_write` 属于**某一个 agent 会话**:整表替换、三
|
|
|
30
30
|
|
|
31
31
|

|
|
32
32
|
|
|
33
|
+

|
|
34
|
+
|
|
33
35
|
图都由 `npm run screenshots`(内部串起 `npm run preview`)生成:先把真实的客户端半区渲染成静态
|
|
34
36
|
HTML(不依赖宿主、零依赖),再以 1280×720 拍进 `docs/screenshots/`。Playwright 不是依赖——工具
|
|
35
37
|
借的是 DSH profile 里已经装好的那份,或 `DSH_PLAYWRIGHT` 指向的那份。
|
|
@@ -230,7 +232,7 @@ npm run preview # 把面板渲染成 preview/panel.html(另附详情态
|
|
|
230
232
|
npm run screenshots # 把这些预览重新拍成 docs/screenshots/,1280×720
|
|
231
233
|
```
|
|
232
234
|
|
|
233
|
-
`docs/screenshots/`
|
|
235
|
+
`docs/screenshots/` 下那几张 PNG 会随包发布、并嵌在两份 README 里,所以面板 UI 一改就要重跑
|
|
234
236
|
`npm run screenshots`。
|
|
235
237
|
|
|
236
238
|
```text
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -50,7 +50,7 @@ window.__ModuleLoader__.load({
|
|
|
50
50
|
* different mechanisms (restart vs. hot reload), so "which one am I looking
|
|
51
51
|
* at" needs an answer that the bundle itself carries.
|
|
52
52
|
*/
|
|
53
|
-
const UI_REV = '
|
|
53
|
+
const UI_REV = 'ui41'
|
|
54
54
|
/** Last language resolved for the shell; module-level so a fetch failure can be worded too. */
|
|
55
55
|
let activeLang = 'zh'
|
|
56
56
|
|
|
@@ -100,12 +100,22 @@ window.__ModuleLoader__.load({
|
|
|
100
100
|
'settings.ingestEnable': '启用外部写入',
|
|
101
101
|
'settings.token': '访问 token',
|
|
102
102
|
'settings.tokenSet': '已设置,留空保持不变',
|
|
103
|
-
'settings.generate': '
|
|
104
|
-
'settings.confirmBelow': '
|
|
103
|
+
'settings.generate': '轮换 token',
|
|
104
|
+
'settings.confirmBelow': '置信度阈值',
|
|
105
105
|
'settings.save': '保存',
|
|
106
106
|
'settings.cancel': '取消',
|
|
107
107
|
'settings.saved': '设置已保存',
|
|
108
108
|
'settings.about': '关于',
|
|
109
|
+
'group.actions': '分组操作',
|
|
110
|
+
'group.capture': '从本对话抓取',
|
|
111
|
+
'group.exemptOn': '本工作区免打扰',
|
|
112
|
+
'group.exemptOff': '取消本工作区免打扰',
|
|
113
|
+
'settings.remindersHint': '到期时在面板顶部提示,并按设置提前。',
|
|
114
|
+
'settings.ingestHint': '允许其他工具带 token 写入,先进收件箱。',
|
|
115
|
+
'settings.tokenHint': '留空表示不改动;轮换后旧 token 立刻失效。',
|
|
116
|
+
'settings.confirmBelowHint': '置信度低于这个值的条目进收件箱,不进清单。',
|
|
117
|
+
'settings.storeFile': '数据文件',
|
|
118
|
+
'settings.version': '版本',
|
|
109
119
|
'inbox.explain': '以下由外部写入或 Agent 提交,确认后才进入清单。',
|
|
110
120
|
'notice.exported': '已导出 {n} 条待办',
|
|
111
121
|
'notice.imported': '已导入 {n} 条(合并,现有共 {total} 条)',
|
|
@@ -347,12 +357,22 @@ window.__ModuleLoader__.load({
|
|
|
347
357
|
'settings.ingestEnable': 'Accept external writes',
|
|
348
358
|
'settings.token': 'Access token',
|
|
349
359
|
'settings.tokenSet': 'Set — leave blank to keep',
|
|
350
|
-
'settings.generate': '
|
|
351
|
-
'settings.confirmBelow': '
|
|
360
|
+
'settings.generate': 'Rotate token',
|
|
361
|
+
'settings.confirmBelow': 'Confidence threshold',
|
|
352
362
|
'settings.save': 'Save',
|
|
353
363
|
'settings.cancel': 'Cancel',
|
|
354
364
|
'settings.saved': 'Settings saved',
|
|
355
365
|
'settings.about': 'About',
|
|
366
|
+
'group.actions': 'Group actions',
|
|
367
|
+
'group.capture': 'Collect from this conversation',
|
|
368
|
+
'group.exemptOn': 'Mute this workspace',
|
|
369
|
+
'group.exemptOff': 'Unmute this workspace',
|
|
370
|
+
'settings.remindersHint': 'Flag a deadline at the top of the panel when it comes due.',
|
|
371
|
+
'settings.ingestHint': 'Let other tools POST todos with a token; they start in the inbox.',
|
|
372
|
+
'settings.tokenHint': 'Leave blank to keep the current one; rotating invalidates the old token.',
|
|
373
|
+
'settings.confirmBelowHint': 'Items below this confidence go to the inbox instead of the list.',
|
|
374
|
+
'settings.storeFile': 'Data file',
|
|
375
|
+
'settings.version': 'Version',
|
|
356
376
|
'inbox.explain': 'Written by external sources or the agent. Confirm it before it joins the list.',
|
|
357
377
|
'notice.exported': 'Exported {n} todos',
|
|
358
378
|
'notice.imported': 'Imported {n} (merged; {total} in the list now)',
|
|
@@ -971,7 +991,7 @@ window.__ModuleLoader__.load({
|
|
|
971
991
|
font: inherit; font-size: 13px; padding: 0; min-width: 0; }
|
|
972
992
|
.gt-search input:focus { outline: none; }
|
|
973
993
|
.gt-search:focus-within { border-color: var(--dsw-alias-brand-primary); }
|
|
974
|
-
.gt-kbd { font-size:
|
|
994
|
+
.gt-kbd { font-size: 11px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 4px;
|
|
975
995
|
padding: 0 4px; color: var(--dsw-alias-label-secondary); }
|
|
976
996
|
.gt-dir { display: inline-flex; align-items: center; gap: 5px; font-size: 12px;
|
|
977
997
|
color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1);
|
|
@@ -1002,7 +1022,7 @@ window.__ModuleLoader__.load({
|
|
|
1002
1022
|
the two toggles. Chips stay for facts about a todo (tags, levels, notes). */
|
|
1003
1023
|
.gt-tabs { display: inline-flex; gap: 2px; padding: 2px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2); }
|
|
1004
1024
|
.gt-tab { border: none; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit;
|
|
1005
|
-
font-size:
|
|
1025
|
+
font-size: 12px; border-radius: 6px; padding: 0 10px; min-height: 24px; box-sizing: border-box;
|
|
1006
1026
|
cursor: pointer; white-space: nowrap; }
|
|
1007
1027
|
.gt-tab:hover { color: var(--dsw-alias-label-primary); }
|
|
1008
1028
|
.gt-tab[data-on="1"] { background: var(--dsw-alias-button-ghost-active-fill); color: var(--dsw-alias-label-primary); font-weight: 600; }
|
|
@@ -1035,29 +1055,26 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1035
1055
|
display: inline-flex; align-items: center; gap: 6px; font: inherit; padding: 2px 0; }
|
|
1036
1056
|
.gt-group-head svg { transition: transform .12s ease; transform: rotate(90deg); }
|
|
1037
1057
|
.gt-group[data-collapsed="1"] .gt-group-head svg { transform: rotate(0deg); }
|
|
1038
|
-
.gt-group-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
.gt-group:hover .gt-group-
|
|
1043
|
-
.gt-group
|
|
1044
|
-
.gt-group-
|
|
1045
|
-
/* An exemption is a state, not an action: it stays visible and reads as "on" without a hover. */
|
|
1046
|
-
.gt-group-action[data-on="1"] { opacity: 1; color: var(--dsw-alias-label-primary);
|
|
1047
|
-
border-color: var(--dsw-alias-border-l2); }
|
|
1058
|
+
.gt-group-flag { margin-left: 2px; display: inline-flex; align-items: center; color: var(--dsw-alias-label-secondary); }
|
|
1059
|
+
/* The group kebab stays quiet until the row is hovered or focused. */
|
|
1060
|
+
.gt-group-head .gt-iconbtn { margin-left: 2px; min-width: 24px; min-height: 24px; opacity: 0;
|
|
1061
|
+
transition: opacity .12s ease; }
|
|
1062
|
+
.gt-group:hover .gt-group-head .gt-iconbtn, .gt-group-head .gt-iconbtn:focus-visible,
|
|
1063
|
+
.gt-group-head .gt-iconbtn[aria-expanded="true"] { opacity: .72; }
|
|
1064
|
+
.gt-group-head .gt-iconbtn:hover { opacity: 1; }
|
|
1048
1065
|
.gt-group-count { color: var(--dsw-alias-label-secondary); font-size: 12px; font-weight: 400; }
|
|
1049
1066
|
/* A group's level is redundant while it is expanded — the rows show it. */
|
|
1050
1067
|
.gt-group:not([data-collapsed="1"]) .gt-group-head .gt-level { display: none; }
|
|
1051
1068
|
.gt-group-label { color: var(--dsw-alias-label-primary); font-weight: 600; font-size: 12px;
|
|
1052
1069
|
line-height: 18px; max-width: 480px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1053
|
-
.gt-level { display: inline-flex; align-items: center; gap: 3px; font-size:
|
|
1070
|
+
.gt-level { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; padding: 0;
|
|
1054
1071
|
border: none; color: var(--dsw-alias-label-secondary); }
|
|
1055
1072
|
.gt-level[data-level="urgent"] { color: var(--dsw-alias-state-error-primary); border-color: var(--dsw-alias-state-error-primary); font-weight: 600; }
|
|
1056
1073
|
.gt-level[data-level="high"] { color: var(--dsw-alias-state-warn-primary); border-color: var(--dsw-alias-state-warn-primary); }
|
|
1057
1074
|
.gt-level[data-level="low"] { color: var(--dsw-alias-label-secondary); }
|
|
1058
1075
|
.gt-dot { width: 6px; height: 6px; border-radius: 999px; background: var(--dsw-alias-state-success-primary);
|
|
1059
1076
|
display: inline-block; }
|
|
1060
|
-
.gt-here { font-size:
|
|
1077
|
+
.gt-here { font-size: 11px; padding: 0 5px; border-radius: 999px; border: 1px solid var(--dsw-alias-brand-primary);
|
|
1061
1078
|
color: var(--dsw-alias-brand-primary); }
|
|
1062
1079
|
.gt-list { display: flex; flex-direction: column; gap: 8px; }
|
|
1063
1080
|
.gt-item { position: relative; display: flex; flex-direction: column; gap: 8px;
|
|
@@ -1090,7 +1107,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1090
1107
|
.gt-item-title { font-weight: 600; font-size: 14px; word-break: break-word; line-height: 22px;
|
|
1091
1108
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
1092
1109
|
.gt-input-xs { width: 64px; flex: 0 0 auto; min-width: 0; padding: 0 8px; min-height: 28px;
|
|
1093
|
-
box-sizing: border-box; font-size:
|
|
1110
|
+
box-sizing: border-box; font-size: 12px; }
|
|
1094
1111
|
.gt-seq { font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary); }
|
|
1095
1112
|
.gt-reminders { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 12px;
|
|
1096
1113
|
min-height: 36px; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l1);
|
|
@@ -1102,20 +1119,45 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1102
1119
|
border-radius: 6px; cursor: pointer; max-width: 300px; overflow: hidden; text-overflow: ellipsis;
|
|
1103
1120
|
white-space: nowrap; }
|
|
1104
1121
|
.gt-reminder-item:hover { background: var(--dsw-alias-button-ghost-active-fill); }
|
|
1105
|
-
.gt-settings { display: flex; flex-direction: column;
|
|
1106
|
-
box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2);
|
|
1107
|
-
background: var(--dsw-alias-bg-overlay); font-size: 13px; line-height: 20px; }
|
|
1122
|
+
.gt-settings { display: flex; flex-direction: column; width: 360px; max-width: 100%;
|
|
1123
|
+
box-sizing: border-box; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2);
|
|
1124
|
+
border-radius: 12px; background: var(--dsw-alias-bg-overlay); font-size: 13px; line-height: 20px; }
|
|
1125
|
+
.gt-settings-head { display: flex; flex: 0 0 auto; align-items: center; justify-content: space-between;
|
|
1126
|
+
gap: 8px; padding: 10px 10px 10px 16px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
1127
|
+
.gt-settings-title { font-size: 14px; font-weight: 600; }
|
|
1128
|
+
.gt-settings-body { display: flex; flex: 1 1 auto; flex-direction: column; gap: 16px;
|
|
1129
|
+
padding: 14px 16px; overflow: auto; min-height: 0; }
|
|
1130
|
+
.gt-settings-group { display: flex; flex-direction: column; gap: 8px; }
|
|
1131
|
+
.gt-settings-group + .gt-settings-group { border-top: 1px solid var(--dsw-alias-border-l1); padding-top: 14px; }
|
|
1132
|
+
.gt-settings-group-title { font-size: 12px; line-height: 18px; font-weight: 600;
|
|
1133
|
+
color: var(--dsw-alias-label-secondary); }
|
|
1134
|
+
.gt-check-row { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; }
|
|
1135
|
+
.gt-check-row input { margin: 3px 0 0; flex: 0 0 auto; }
|
|
1136
|
+
.gt-check-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1137
|
+
.gt-check-label { color: var(--dsw-alias-label-primary); }
|
|
1138
|
+
.gt-hint { font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-secondary); }
|
|
1139
|
+
.gt-field { display: flex; flex-direction: column; gap: 6px; }
|
|
1140
|
+
.gt-field[data-muted="1"], .gt-field-row[data-muted="1"] { opacity: .55; }
|
|
1141
|
+
.gt-field-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
1142
|
+
.gt-field-label { font-size: 13px; color: var(--dsw-alias-label-primary); }
|
|
1143
|
+
.gt-field-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 8px; }
|
|
1144
|
+
.gt-input-num { width: 76px; text-align: right; font-variant-numeric: tabular-nums; }
|
|
1145
|
+
.gt-link-btn { border: none; background: transparent; padding: 0; font: inherit; font-size: 12px;
|
|
1146
|
+
color: var(--dsw-alias-link); cursor: pointer; }
|
|
1147
|
+
.gt-link-btn:disabled { color: var(--dsw-alias-label-secondary); cursor: default; }
|
|
1148
|
+
.gt-about-row { display: grid; grid-template-columns: 88px minmax(0, 1fr); align-items: baseline; gap: 8px; }
|
|
1149
|
+
.gt-settings-foot { display: flex; flex: 0 0 auto; justify-content: flex-end; gap: 8px;
|
|
1150
|
+
padding: 12px 16px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1108
1151
|
.gt-group-select { display: none; min-height: 28px; max-width: 150px; padding: 0 8px; font: inherit;
|
|
1109
|
-
font-size:
|
|
1152
|
+
font-size: 12px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1);
|
|
1110
1153
|
border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; }
|
|
1111
1154
|
.gt-root[data-wide="0"] .gt-group-tabs { display: none; }
|
|
1112
1155
|
.gt-root[data-wide="0"] .gt-group-select { display: inline-flex; align-items: center; }
|
|
1113
1156
|
.gt-inbox-note { padding: 0 2px 2px; color: var(--dsw-alias-label-secondary); font-size: 13px;
|
|
1114
1157
|
line-height: 20px; }
|
|
1115
|
-
.gt-settings-path { font-size:
|
|
1158
|
+
.gt-settings-path { font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1116
1159
|
.gt-settings .gt-input { flex: 0 0 auto; width: 100%; min-height: 32px; }
|
|
1117
|
-
.gt-settings .gt-
|
|
1118
|
-
.gt-settings-head { font-weight: 650; font-size: 14px; }
|
|
1160
|
+
.gt-settings .gt-input-num { width: 76px; }
|
|
1119
1161
|
.gt-check-line { display: flex; align-items: center; gap: 8px; justify-content: flex-start;
|
|
1120
1162
|
color: var(--dsw-alias-label-primary); }
|
|
1121
1163
|
.gt-settings-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
|
|
@@ -1133,7 +1175,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1133
1175
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1134
1176
|
.gt-truncated { color: var(--dsw-alias-state-warn-primary); font-size: 12px; }
|
|
1135
1177
|
.gt-bulk-tag { flex: 0 1 120px; min-width: 90px; }
|
|
1136
|
-
.gt-inbox { margin-left: 6px; padding: 1px 6px; border-radius: 999px; font-size:
|
|
1178
|
+
.gt-inbox { margin-left: 6px; padding: 1px 6px; border-radius: 999px; font-size: 11px; font-weight: 600;
|
|
1137
1179
|
color: var(--dsw-alias-state-warn-primary); border: 1px solid var(--dsw-alias-state-warn-primary);
|
|
1138
1180
|
vertical-align: 1px; }
|
|
1139
1181
|
.gt-item[data-status="done"] .gt-item-title, .gt-item[data-status="cancelled"] .gt-item-title {
|
|
@@ -1192,17 +1234,17 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
1192
1234
|
.gt-link-main { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1; }
|
|
1193
1235
|
.gt-note-item { display: flex; flex-direction: column; gap: 4px; padding: 8px 0; }
|
|
1194
1236
|
.gt-note-item + .gt-note-item { border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
1195
|
-
.gt-note-meta { display: flex; align-items: center; gap: 6px; font-size:
|
|
1237
|
+
.gt-note-meta { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--dsw-alias-label-secondary);
|
|
1196
1238
|
white-space: nowrap; }
|
|
1197
1239
|
.gt-note-text { font-size: 13px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; max-width: 68ch; }
|
|
1198
1240
|
.gt-link a { color: var(--dsw-alias-brand-primary); text-decoration: none; }
|
|
1199
1241
|
.gt-link a:hover { text-decoration: underline; }
|
|
1200
1242
|
.gt-link-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 320px; }
|
|
1201
|
-
.gt-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:
|
|
1243
|
+
.gt-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
|
|
1202
1244
|
color: var(--dsw-alias-label-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1203
1245
|
.gt-banner { border-radius: 9px; padding: 8px 11px; border: 1px solid var(--dsw-alias-state-error-primary);
|
|
1204
1246
|
color: var(--dsw-alias-state-error-primary); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
1205
|
-
.gt-activity { display: flex; flex-direction: column; gap: 3px; color: var(--dsw-alias-label-secondary); font-size:
|
|
1247
|
+
.gt-activity { display: flex; flex-direction: column; gap: 3px; color: var(--dsw-alias-label-secondary); font-size: 12px; }
|
|
1206
1248
|
.gt-empty { display: flex; flex-direction: column; gap: 6px; align-items: center; justify-content: center;
|
|
1207
1249
|
color: var(--dsw-alias-label-secondary); padding: 42px 16px; text-align: center; flex: 1; }
|
|
1208
1250
|
.gt-empty-title { font-size: 14px; color: var(--dsw-alias-label-primary); }
|
|
@@ -2169,6 +2211,9 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2169
2211
|
// The preview harness cannot re-render after a click, so it may ask to start in
|
|
2170
2212
|
// selection mode; a person toggles it with the `选择` control.
|
|
2171
2213
|
const [selectMode, setSelectMode] = useState(props.initialSelectMode === true)
|
|
2214
|
+
// Only the static preview asks for this: the panel opens settings by click, but the
|
|
2215
|
+
// SSR harness cannot click, so the screenshot variant has to start there.
|
|
2216
|
+
const initialSettingsOpen = props.initialSettingsOpen === true
|
|
2172
2217
|
// The picker's filters mirror the shell's own visibility rule, but the shape of the
|
|
2173
2218
|
// real snapshot has never been observable from here — a preview fixture is written by
|
|
2174
2219
|
// the same hand as the filter, so it can only confirm itself. Report the shape once per
|
|
@@ -2187,7 +2232,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2187
2232
|
const [importAsk, setImportAsk] = useState(null)
|
|
2188
2233
|
// One settings popover: reminders and the external door both need a place a person can
|
|
2189
2234
|
// reach without curl.
|
|
2190
|
-
const [settingsOpen, setSettingsOpen] = useState(
|
|
2235
|
+
const [settingsOpen, setSettingsOpen] = useState(initialSettingsOpen)
|
|
2191
2236
|
const [settingsBox, setSettingsBox] = useState(null)
|
|
2192
2237
|
// The detail card's breakpoint follows the panel's own width, not the viewport: the
|
|
2193
2238
|
// slot is ~265px narrower than the window, so a viewport query opens the two-column
|
|
@@ -2371,6 +2416,8 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2371
2416
|
// The wide-screen detail card floats over the panel, so its offsets are measured from
|
|
2372
2417
|
// the panel's own column rather than derived from the viewport width.
|
|
2373
2418
|
const innerRef = useRef(null)
|
|
2419
|
+
const settingsRef = useRef(null)
|
|
2420
|
+
const settingsTriggerRef = useRef(null)
|
|
2374
2421
|
const [detailAnchor, setDetailAnchor] = useState(null)
|
|
2375
2422
|
const titleRef = useRef(null)
|
|
2376
2423
|
const searchRef = useRef(null)
|
|
@@ -2436,13 +2483,15 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2436
2483
|
useEffect(() => {
|
|
2437
2484
|
const element = innerRef.current
|
|
2438
2485
|
if (element === null || typeof window === 'undefined') return undefined
|
|
2486
|
+
// The static preview supplies a `window` stub without event plumbing.
|
|
2487
|
+
const listen = typeof window.addEventListener === 'function'
|
|
2439
2488
|
const measure = () => setPanelWidth(element.getBoundingClientRect().width)
|
|
2440
2489
|
measure()
|
|
2441
|
-
window.addEventListener('resize', measure)
|
|
2490
|
+
if (listen === true) window.addEventListener('resize', measure)
|
|
2442
2491
|
const observer = typeof window.ResizeObserver === 'function' ? new window.ResizeObserver(measure) : null
|
|
2443
2492
|
if (observer !== null) observer.observe(element)
|
|
2444
2493
|
return () => {
|
|
2445
|
-
window.removeEventListener('resize', measure)
|
|
2494
|
+
if (listen === true) window.removeEventListener('resize', measure)
|
|
2446
2495
|
if (observer !== null) observer.disconnect()
|
|
2447
2496
|
}
|
|
2448
2497
|
}, [])
|
|
@@ -2457,14 +2506,19 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2457
2506
|
if (element === null) return
|
|
2458
2507
|
const rect = element.getBoundingClientRect()
|
|
2459
2508
|
setSettingsBox({
|
|
2460
|
-
|
|
2509
|
+
// Just under the title block: high enough that the whole card fits, low enough
|
|
2510
|
+
// that it never covers the panel title or the header actions.
|
|
2511
|
+
top: Math.round(rect.top + 56),
|
|
2461
2512
|
right: Math.round(window.innerWidth - rect.right + 20),
|
|
2462
2513
|
bottom: Math.round(window.innerHeight - rect.bottom + 20),
|
|
2463
2514
|
})
|
|
2464
2515
|
}
|
|
2516
|
+
const listen = typeof window.addEventListener === 'function'
|
|
2465
2517
|
measure()
|
|
2466
|
-
window.addEventListener('resize', measure)
|
|
2467
|
-
return () =>
|
|
2518
|
+
if (listen === true) window.addEventListener('resize', measure)
|
|
2519
|
+
return () => {
|
|
2520
|
+
if (listen === true) window.removeEventListener('resize', measure)
|
|
2521
|
+
}
|
|
2468
2522
|
}, [settingsOpen, lang])
|
|
2469
2523
|
|
|
2470
2524
|
useEffect(() => {
|
|
@@ -2525,8 +2579,8 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2525
2579
|
return () => clearInterval(timer)
|
|
2526
2580
|
}, [reload])
|
|
2527
2581
|
|
|
2528
|
-
// Keyboard: `/` search, `n` new, Escape
|
|
2529
|
-
// user is typing in a field.
|
|
2582
|
+
// Keyboard: `/` search, `n` new, Escape closes the innermost layer — but never
|
|
2583
|
+
// while the user is typing in a field, where the first Escape only drops focus.
|
|
2530
2584
|
useEffect(() => {
|
|
2531
2585
|
if (typeof document === 'undefined') return undefined
|
|
2532
2586
|
const onKey = (event) => {
|
|
@@ -2536,8 +2590,16 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2536
2590
|
target !== undefined &&
|
|
2537
2591
|
(target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable === true)
|
|
2538
2592
|
if (event.key === 'Escape') {
|
|
2539
|
-
setExpandedId('')
|
|
2540
2593
|
if (typing && typeof target.blur === 'function') target.blur()
|
|
2594
|
+
if (settingsOpen) {
|
|
2595
|
+
setSettingsOpen(false)
|
|
2596
|
+
return
|
|
2597
|
+
}
|
|
2598
|
+
if (adding) {
|
|
2599
|
+
setAdding(false)
|
|
2600
|
+
return
|
|
2601
|
+
}
|
|
2602
|
+
setExpandedId('')
|
|
2541
2603
|
return
|
|
2542
2604
|
}
|
|
2543
2605
|
if (typing || event.metaKey || event.ctrlKey || event.altKey) return
|
|
@@ -2551,7 +2613,23 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
2551
2613
|
}
|
|
2552
2614
|
document.addEventListener('keydown', onKey)
|
|
2553
2615
|
return () => document.removeEventListener('keydown', onKey)
|
|
2554
|
-
}, [])
|
|
2616
|
+
}, [settingsOpen, adding])
|
|
2617
|
+
|
|
2618
|
+
// The settings card floats over the list, so a click anywhere else dismisses it
|
|
2619
|
+
// the way every other popover on the web does.
|
|
2620
|
+
useEffect(() => {
|
|
2621
|
+
if (!settingsOpen || typeof document === 'undefined') return undefined
|
|
2622
|
+
const onDown = (event) => {
|
|
2623
|
+
const card = settingsRef.current
|
|
2624
|
+
const gear = settingsTriggerRef.current
|
|
2625
|
+
if (card === null) return
|
|
2626
|
+
if (card.contains(event.target) === true) return
|
|
2627
|
+
if (gear !== null && gear.contains(event.target) === true) return
|
|
2628
|
+
setSettingsOpen(false)
|
|
2629
|
+
}
|
|
2630
|
+
document.addEventListener('mousedown', onDown)
|
|
2631
|
+
return () => document.removeEventListener('mousedown', onDown)
|
|
2632
|
+
}, [settingsOpen])
|
|
2555
2633
|
|
|
2556
2634
|
const run = async (id, operation) => {
|
|
2557
2635
|
setBusyId(id)
|
|
@@ -3226,15 +3304,20 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3226
3304
|
'div',
|
|
3227
3305
|
{ className: 'gt-head-actions' },
|
|
3228
3306
|
h(
|
|
3229
|
-
'
|
|
3230
|
-
{
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3307
|
+
'span',
|
|
3308
|
+
{ ref: settingsTriggerRef, style: { display: 'inline-flex' } },
|
|
3309
|
+
h(
|
|
3310
|
+
'button',
|
|
3311
|
+
{
|
|
3312
|
+
type: 'button',
|
|
3313
|
+
className: 'gt-iconbtn',
|
|
3314
|
+
title: t('settings.title'),
|
|
3315
|
+
'aria-label': t('settings.title'),
|
|
3316
|
+
'aria-expanded': settingsOpen,
|
|
3317
|
+
onClick: () => (settingsOpen ? setSettingsOpen(false) : openSettings()),
|
|
3318
|
+
},
|
|
3319
|
+
h(Icon, { name: 'gear' }),
|
|
3320
|
+
),
|
|
3238
3321
|
),
|
|
3239
3322
|
h(
|
|
3240
3323
|
'button',
|
|
@@ -3276,6 +3359,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3276
3359
|
? h(
|
|
3277
3360
|
'div',
|
|
3278
3361
|
{
|
|
3362
|
+
ref: settingsRef,
|
|
3279
3363
|
className: 'gt-settings',
|
|
3280
3364
|
role: 'dialog',
|
|
3281
3365
|
'aria-label': t('settings.title'),
|
|
@@ -3286,77 +3370,141 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3286
3370
|
position: 'fixed',
|
|
3287
3371
|
top: `${settingsBox.top}px`,
|
|
3288
3372
|
right: `${settingsBox.right}px`,
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3373
|
+
// Height follows the content, capped by the room below: stretching to
|
|
3374
|
+
// `bottom` left a tall empty card with the footer stranded at the floor.
|
|
3375
|
+
maxHeight: `calc(100dvh - ${settingsBox.top + 20}px)`,
|
|
3292
3376
|
zIndex: 40,
|
|
3293
3377
|
},
|
|
3294
3378
|
},
|
|
3295
|
-
h('div', { className: 'gt-settings-head' }, t('settings.title')),
|
|
3296
|
-
h('div', { className: 'gt-section-title' }, t('settings.reminders')),
|
|
3297
|
-
h(
|
|
3298
|
-
'label',
|
|
3299
|
-
{ className: 'gt-check-line' },
|
|
3300
|
-
h('input', { type: 'checkbox', checked: remindersDraft, onChange: (event) => setRemindersDraft(event.target.checked) }),
|
|
3301
|
-
t('settings.remindersEnable'),
|
|
3302
|
-
),
|
|
3303
3379
|
h(
|
|
3304
|
-
'
|
|
3305
|
-
{ className: 'gt-
|
|
3306
|
-
t('settings.
|
|
3307
|
-
h(
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3380
|
+
'div',
|
|
3381
|
+
{ className: 'gt-settings-head' },
|
|
3382
|
+
h('span', { className: 'gt-settings-title' }, t('settings.title')),
|
|
3383
|
+
h(
|
|
3384
|
+
'button',
|
|
3385
|
+
{
|
|
3386
|
+
type: 'button',
|
|
3387
|
+
className: 'gt-iconbtn',
|
|
3388
|
+
title: t('settings.cancel'),
|
|
3389
|
+
'aria-label': t('settings.cancel'),
|
|
3390
|
+
onClick: () => setSettingsOpen(false),
|
|
3391
|
+
},
|
|
3392
|
+
h(Icon, { name: 'close', size: 13 }),
|
|
3393
|
+
),
|
|
3315
3394
|
),
|
|
3316
|
-
h('div', { className: 'gt-section-title' }, t('settings.ingest')),
|
|
3317
3395
|
h(
|
|
3318
|
-
'
|
|
3319
|
-
{ className: 'gt-
|
|
3320
|
-
h(
|
|
3321
|
-
|
|
3396
|
+
'div',
|
|
3397
|
+
{ className: 'gt-settings-body' },
|
|
3398
|
+
h(
|
|
3399
|
+
'div',
|
|
3400
|
+
{ className: 'gt-settings-group' },
|
|
3401
|
+
h('div', { className: 'gt-settings-group-title' }, t('settings.reminders')),
|
|
3402
|
+
h(
|
|
3403
|
+
'label',
|
|
3404
|
+
{ className: 'gt-check-row' },
|
|
3405
|
+
h('input', { type: 'checkbox', checked: remindersDraft, onChange: (event) => setRemindersDraft(event.target.checked) }),
|
|
3406
|
+
h(
|
|
3407
|
+
'span',
|
|
3408
|
+
{ className: 'gt-check-main' },
|
|
3409
|
+
h('span', { className: 'gt-check-label' }, t('settings.remindersEnable')),
|
|
3410
|
+
h('span', { className: 'gt-hint' }, t('settings.remindersHint')),
|
|
3411
|
+
),
|
|
3412
|
+
),
|
|
3413
|
+
h(
|
|
3414
|
+
'div',
|
|
3415
|
+
{ className: 'gt-field-row' },
|
|
3416
|
+
h('label', { className: 'gt-field-label', htmlFor: 'gt-lead' }, t('settings.lead')),
|
|
3417
|
+
h('input', {
|
|
3418
|
+
id: 'gt-lead',
|
|
3419
|
+
type: 'number',
|
|
3420
|
+
min: 0,
|
|
3421
|
+
max: 1440,
|
|
3422
|
+
className: 'gt-input gt-input-num',
|
|
3423
|
+
disabled: remindersDraft !== true,
|
|
3424
|
+
value: leadDraft,
|
|
3425
|
+
onChange: (event) => setLeadDraft(event.target.value),
|
|
3426
|
+
}),
|
|
3427
|
+
),
|
|
3428
|
+
),
|
|
3429
|
+
h(
|
|
3430
|
+
'div',
|
|
3431
|
+
{ className: 'gt-settings-group' },
|
|
3432
|
+
h('div', { className: 'gt-settings-group-title' }, t('settings.ingest')),
|
|
3433
|
+
h(
|
|
3434
|
+
'label',
|
|
3435
|
+
{ className: 'gt-check-row' },
|
|
3436
|
+
h('input', { type: 'checkbox', checked: ingestDraft, onChange: (event) => setIngestDraft(event.target.checked) }),
|
|
3437
|
+
h(
|
|
3438
|
+
'span',
|
|
3439
|
+
{ className: 'gt-check-main' },
|
|
3440
|
+
h('span', { className: 'gt-check-label' }, t('settings.ingestEnable')),
|
|
3441
|
+
h('span', { className: 'gt-hint' }, t('settings.ingestHint')),
|
|
3442
|
+
),
|
|
3443
|
+
),
|
|
3444
|
+
h(
|
|
3445
|
+
'div',
|
|
3446
|
+
{ className: 'gt-field', 'data-muted': ingestDraft === true ? '0' : '1' },
|
|
3447
|
+
h(
|
|
3448
|
+
'div',
|
|
3449
|
+
{ className: 'gt-field-head' },
|
|
3450
|
+
h('label', { className: 'gt-field-label', htmlFor: 'gt-token' }, t('settings.token')),
|
|
3451
|
+
h('button', { type: 'button', className: 'gt-link-btn', disabled: ingestDraft !== true, onClick: generateToken }, t('settings.generate')),
|
|
3452
|
+
),
|
|
3453
|
+
h('input', {
|
|
3454
|
+
id: 'gt-token',
|
|
3455
|
+
className: 'gt-input',
|
|
3456
|
+
type: 'password',
|
|
3457
|
+
disabled: ingestDraft !== true,
|
|
3458
|
+
value: tokenDraft,
|
|
3459
|
+
placeholder:
|
|
3460
|
+
state.health?.settings?.ingest?.tokenSet === true ? t('settings.tokenSet') : t('settings.token'),
|
|
3461
|
+
autoComplete: 'new-password',
|
|
3462
|
+
onChange: (event) => setTokenDraft(event.target.value),
|
|
3463
|
+
}),
|
|
3464
|
+
h('span', { className: 'gt-hint' }, t('settings.tokenHint')),
|
|
3465
|
+
),
|
|
3466
|
+
h(
|
|
3467
|
+
'div',
|
|
3468
|
+
{ className: 'gt-field-row', 'data-muted': ingestDraft === true ? '0' : '1' },
|
|
3469
|
+
h('label', { className: 'gt-field-label', htmlFor: 'gt-below' }, t('settings.confirmBelow')),
|
|
3470
|
+
h('input', {
|
|
3471
|
+
id: 'gt-below',
|
|
3472
|
+
type: 'number',
|
|
3473
|
+
min: 0,
|
|
3474
|
+
max: 1,
|
|
3475
|
+
step: 0.05,
|
|
3476
|
+
className: 'gt-input gt-input-num',
|
|
3477
|
+
disabled: ingestDraft !== true,
|
|
3478
|
+
value: belowDraft,
|
|
3479
|
+
onChange: (event) => setBelowDraft(event.target.value),
|
|
3480
|
+
}),
|
|
3481
|
+
),
|
|
3482
|
+
h('span', { className: 'gt-hint' }, t('settings.confirmBelowHint')),
|
|
3483
|
+
),
|
|
3484
|
+
h(
|
|
3485
|
+
'div',
|
|
3486
|
+
{ className: 'gt-settings-group' },
|
|
3487
|
+
h('div', { className: 'gt-settings-group-title' }, t('settings.about')),
|
|
3488
|
+
h(
|
|
3489
|
+
'div',
|
|
3490
|
+
{ className: 'gt-about-row' },
|
|
3491
|
+
h('span', { className: 'gt-field-label' }, t('settings.storeFile')),
|
|
3492
|
+
h('span', { className: 'gt-mono gt-settings-path', title: state.health?.store?.file ?? '' }, state.health?.store?.file ?? ''),
|
|
3493
|
+
),
|
|
3494
|
+
h(
|
|
3495
|
+
'div',
|
|
3496
|
+
{ className: 'gt-about-row' },
|
|
3497
|
+
h('span', { className: 'gt-field-label' }, t('settings.version')),
|
|
3498
|
+
h('span', { className: 'gt-mono gt-hint' }, `v${state.health?.version ?? ''} · ${UI_REV}`),
|
|
3499
|
+
),
|
|
3500
|
+
),
|
|
3322
3501
|
),
|
|
3323
|
-
h('input', {
|
|
3324
|
-
className: 'gt-input',
|
|
3325
|
-
type: 'password',
|
|
3326
|
-
value: tokenDraft,
|
|
3327
|
-
placeholder:
|
|
3328
|
-
state.health?.settings?.ingest?.tokenSet === true ? t('settings.tokenSet') : t('settings.token'),
|
|
3329
|
-
autoComplete: 'new-password',
|
|
3330
|
-
onChange: (event) => setTokenDraft(event.target.value),
|
|
3331
|
-
}),
|
|
3332
3502
|
h(
|
|
3333
3503
|
'div',
|
|
3334
|
-
{ className: 'gt-settings-
|
|
3335
|
-
h('button', { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: generateToken }, t('settings.generate')),
|
|
3504
|
+
{ className: 'gt-settings-foot' },
|
|
3336
3505
|
h('button', { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => setSettingsOpen(false) }, t('settings.cancel')),
|
|
3337
3506
|
h('button', { type: 'button', className: 'gt-btn gt-btn-primary', onClick: () => void saveSettings() }, t('settings.save')),
|
|
3338
3507
|
),
|
|
3339
|
-
h(
|
|
3340
|
-
'label',
|
|
3341
|
-
{ className: 'gt-check-line' },
|
|
3342
|
-
t('settings.confirmBelow'),
|
|
3343
|
-
h('input', {
|
|
3344
|
-
type: 'number',
|
|
3345
|
-
min: 0,
|
|
3346
|
-
max: 1,
|
|
3347
|
-
step: 0.05,
|
|
3348
|
-
className: 'gt-input gt-input-xs',
|
|
3349
|
-
value: belowDraft,
|
|
3350
|
-
onChange: (event) => setBelowDraft(event.target.value),
|
|
3351
|
-
}),
|
|
3352
|
-
),
|
|
3353
|
-
h('div', { className: 'gt-section-title' }, t('settings.about')),
|
|
3354
|
-
h(
|
|
3355
|
-
'div',
|
|
3356
|
-
{ className: 'gt-muted gt-mono gt-settings-path', title: state.health?.store?.file ?? '' },
|
|
3357
|
-
state.health?.store?.file ?? '',
|
|
3358
|
-
),
|
|
3359
|
-
h('div', { className: 'gt-muted' }, `v${state.health?.version ?? ''} · ${UI_REV}`),
|
|
3360
3508
|
)
|
|
3361
3509
|
: null,
|
|
3362
3510
|
|
|
@@ -3997,39 +4145,59 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
|
|
|
3997
4145
|
? h('span', { className: 'gt-dot', title: t('meta.session.live'), 'aria-label': t('meta.session.live') })
|
|
3998
4146
|
: null,
|
|
3999
4147
|
),
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
className: 'gt-group-action',
|
|
4007
|
-
title: t('action.captureGroupHint'),
|
|
4008
|
-
onClick: (event) => {
|
|
4009
|
-
event.stopPropagation()
|
|
4010
|
-
void requestCapture(group.sessionId, group.label)
|
|
4011
|
-
},
|
|
4012
|
-
},
|
|
4013
|
-
h(Icon, { name: 'doc', size: 11 }),
|
|
4014
|
-
t('action.captureShort'),
|
|
4015
|
-
),
|
|
4016
|
-
group.dir === undefined || group.dir === ''
|
|
4017
|
-
? null
|
|
4018
|
-
: h(
|
|
4019
|
-
'button',
|
|
4148
|
+
// An exemption is a state, so it keeps a small always-visible mark; the
|
|
4149
|
+
// actions themselves live behind one kebab instead of two text buttons
|
|
4150
|
+
// competing with the group title.
|
|
4151
|
+
exemptGroup
|
|
4152
|
+
? h(
|
|
4153
|
+
'span',
|
|
4020
4154
|
{
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
'data-on': exemptGroup ? '1' : '0',
|
|
4024
|
-
title: exemptGroup ? t('action.exemptHintOn') : t('action.exemptHint'),
|
|
4025
|
-
onClick: (event) => {
|
|
4026
|
-
event.stopPropagation()
|
|
4027
|
-
void toggleExemptWorkspace(group.dir, !exemptGroup)
|
|
4028
|
-
},
|
|
4155
|
+
className: 'gt-group-flag',
|
|
4156
|
+
title: t('action.exemptHintOn'),
|
|
4029
4157
|
},
|
|
4030
4158
|
h(Icon, { name: 'mute', size: 11 }),
|
|
4031
|
-
|
|
4032
|
-
|
|
4159
|
+
)
|
|
4160
|
+
: null,
|
|
4161
|
+
[
|
|
4162
|
+
group.sessionId === undefined
|
|
4163
|
+
? null
|
|
4164
|
+
: {
|
|
4165
|
+
key: 'capture',
|
|
4166
|
+
label: t('action.captureShort'),
|
|
4167
|
+
icon: 'doc',
|
|
4168
|
+
onSelect: () => void requestCapture(group.sessionId, group.label),
|
|
4169
|
+
},
|
|
4170
|
+
group.dir === undefined || group.dir === ''
|
|
4171
|
+
? null
|
|
4172
|
+
: {
|
|
4173
|
+
key: 'exempt',
|
|
4174
|
+
label: exemptGroup ? t('action.exemptOn') : t('action.exempt'),
|
|
4175
|
+
icon: 'mute',
|
|
4176
|
+
onSelect: () => void toggleExemptWorkspace(group.dir, !exemptGroup),
|
|
4177
|
+
},
|
|
4178
|
+
].filter((item) => item !== null).length === 0
|
|
4179
|
+
? null
|
|
4180
|
+
: h(Menu, {
|
|
4181
|
+
label: t('group.actions'),
|
|
4182
|
+
items: [
|
|
4183
|
+
group.sessionId === undefined
|
|
4184
|
+
? null
|
|
4185
|
+
: {
|
|
4186
|
+
key: 'capture',
|
|
4187
|
+
label: t('group.capture'),
|
|
4188
|
+
icon: 'doc',
|
|
4189
|
+
onSelect: () => void requestCapture(group.sessionId, group.label),
|
|
4190
|
+
},
|
|
4191
|
+
group.dir === undefined || group.dir === ''
|
|
4192
|
+
? null
|
|
4193
|
+
: {
|
|
4194
|
+
key: 'exempt',
|
|
4195
|
+
label: exemptGroup ? t('group.exemptOff') : t('group.exemptOn'),
|
|
4196
|
+
icon: 'mute',
|
|
4197
|
+
onSelect: () => void toggleExemptWorkspace(group.dir, !exemptGroup),
|
|
4198
|
+
},
|
|
4199
|
+
].filter((item) => item !== null && item !== undefined),
|
|
4200
|
+
}),
|
|
4033
4201
|
),
|
|
4034
4202
|
isCollapsed
|
|
4035
4203
|
? null
|
package/lib/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import { createStore, resolveStorePath } from './store.js'
|
|
|
36
36
|
import { createTools } from './tools.js'
|
|
37
37
|
|
|
38
38
|
/** Must equal `package.json`'s version; `npm run check` enforces that. */
|
|
39
|
-
export const VERSION = '0.6.
|
|
39
|
+
export const VERSION = '0.6.5'
|
|
40
40
|
|
|
41
41
|
/** Plugin id. Also the Loader row id in `cordis.patch.yml`. */
|
|
42
42
|
export const name = 'dsh-global-todo'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-global-todo",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"icon": "./docs/icon.svg",
|
|
5
5
|
"description": "Global, cross-session todos for DeepSeek Harness: one list owned by the human and the agent together, with creation time, attached documents and links, and one-click hand-off to an agent session. 全局待办:跨会话一份清单,记录加入时间,可挂文档与地址,可一键交给 Agent 继续干。",
|
|
6
6
|
"type": "module",
|