dsh-global-todo 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ ## [0.6.1] — 2026-10-20
2
+
3
+ The follow-up pass from the design spec: the breakpoint now follows the panel, not the window,
4
+ and the form stops competing with itself.
5
+
6
+ - **Panel-measured breakpoint.** A `ResizeObserver` on the column sets `data-wide`; the detail card
7
+ becomes a right-hand card only when the column itself is at least 960px. At 1100 the detail now
8
+ expands inline under its row instead of squeezing the list into a 416px strip.
9
+ - **Inline detail stops repeating itself.** The card title is hidden in inline mode (the row title
10
+ is directly above it) and keeps only the close affordance.
11
+ - **Add form.** The header action turns into an icon-only close while the form is open, so there is
12
+ one `+ Add` on screen; the form orders 更多字段 before 添加, matching the spec.
13
+ - **Rows are 72px tall** at minimum, matching the spec's rhythm, and the detail/card radius is 10px.
14
+ - Dark theme verified against the real host: layer-2 rows, overlay inspector and ghost-active
15
+ selected states all resolve correctly.
16
+
17
+ ## [0.6.0] — 2026-10-20
18
+
19
+ A design pass over the whole panel: one list, fewer competing controls, one type scale.
20
+
21
+ - **The layout is a column, not a wrapping row.** `.gt-root`/`.gt-inner` gained `min-width: 0` so the
22
+ column shrinks with its slot instead of overflowing at 1280 and 1100; the header wraps its actions
23
+ and the toolbar is two semantic rows (filter/search above, context above the list). The working
24
+ directory, handoff target and browse controls move behind a single Context toggle, collapsed by
25
+ default.
26
+ - **Five type sizes and one weight scale.** 20/28 panel title, 14/22 row title, 13/20 controls and
27
+ body, 12/18 metadata and section labels, 11.5 mono for paths. Buttons, inputs and tabs share a
28
+ 28px control height; the settings card is a 340px floating inspector (fixed, mutually exclusive
29
+ with the detail card) instead of an in-flow block that pushed the list down.
30
+ - **Rows read top-down.** The title opens the detail (the kebab remains as a redundant door); the
31
+ dispatch button is a quiet action instead of an outlined primary; metadata loses the origin path
32
+ and the stray separator (CSS-generated dots), and tags cap at four. Group-level priority hides
33
+ while the group is expanded, where every row already shows it.
34
+ - **Copy and states.** Full-width colons no longer leak into the English UI, `1 items` is fixed,
35
+ priority chips say Urgent/High/Normal/Low instead of P-codes, the empty state has a primary
36
+ action, the error banner can be dismissed, and the reminder bar keeps its accent on a 2px edge
37
+ (amber text failed contrast). The footer's store path and version move into Settings → About.
38
+
1
39
  ## [0.5.3] — 2026-10-20
2
40
 
3
41
  Settings a person can reach, and reminders that behave like reminders.
Binary file
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 = 'ui35'
53
+ const UI_REV = 'ui37'
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
 
@@ -64,6 +64,7 @@ window.__ModuleLoader__.load({
64
64
  'action.retry': '重试',
65
65
  'action.add': '添加',
66
66
  'action.more': '更多字段',
67
+ 'action.overflow': '更多操作',
67
68
  'action.less': '收起字段',
68
69
  'action.detail': '详情',
69
70
  'action.hide': '收起',
@@ -91,6 +92,7 @@ window.__ModuleLoader__.load({
91
92
  'action.exportHint': '把整份清单导出成 JSON 文件(就是存储用的那份文档)',
92
93
  'action.importHint': '从 JSON 文件导入:同 id 覆盖、新 id 追加,不删除现有待办',
93
94
  'settings.title': '设置',
95
+ 'settings.context': '上下文',
94
96
  'settings.reminders': '到点提醒',
95
97
  'settings.remindersEnable': '开启到点提醒',
96
98
  'settings.lead': '提前(分钟)',
@@ -103,6 +105,7 @@ window.__ModuleLoader__.load({
103
105
  'settings.save': '保存',
104
106
  'settings.cancel': '取消',
105
107
  'settings.saved': '设置已保存',
108
+ 'settings.about': '关于',
106
109
  'notice.exported': '已导出 {n} 条待办',
107
110
  'notice.imported': '已导入 {n} 条(合并,现有共 {total} 条)',
108
111
  'import.badJson': '这个文件不是合法的 JSON',
@@ -124,7 +127,7 @@ window.__ModuleLoader__.load({
124
127
  'notice.bulk': '已更新 {n} 条',
125
128
  'notice.bulkPartial': '已更新 {n} 条,跳过 {m} 条',
126
129
  'select.clear': '清除',
127
- 'select.done': '完成',
130
+ 'select.done': '退出选择',
128
131
  'select.hint': '先勾几条待办,再点「一键分配」:优先回到写下它的那个对话,其次同工作区的对话,都找不到才让你选。',
129
132
  'assign.working': '正在分配…',
130
133
  'assign.summary': '已分配 {n} 条(原对话 {origin} · 同工作区 {workspace} · 你选的 {chosen})',
@@ -218,10 +221,10 @@ window.__ModuleLoader__.load({
218
221
  'field.linkValue': '文档绝对路径或网址',
219
222
  'field.linkTitle': '给它起个短名字(可空)',
220
223
  'field.search': '搜标题、描述、标签、附件…',
221
- 'priority.low': 'P3 低',
222
- 'priority.normal': 'P2 常规',
223
- 'priority.high': 'P1 高',
224
- 'priority.urgent': 'P0 紧急',
224
+ 'priority.low': '低',
225
+ 'priority.normal': '常规',
226
+ 'priority.high': '高',
227
+ 'priority.urgent': '紧急',
225
228
  'group.by': '分组',
226
229
  'group.conversation': '对话',
227
230
  'group.time': '时间',
@@ -229,6 +232,7 @@ window.__ModuleLoader__.load({
229
232
  'group.manual': '手动添加',
230
233
  'group.other': '其他',
231
234
  'group.items': '{n} 条',
235
+ 'group.item': '1 条',
232
236
  'group.thisConversation': '本对话',
233
237
  'group.overdue': '逾期',
234
238
  'group.today': '今天',
@@ -238,6 +242,7 @@ window.__ModuleLoader__.load({
238
242
  'group.done': '已完成',
239
243
  'group.cancelled': '已取消',
240
244
  'empty.none': '还没有待办。上面写一句,回车就记下了。',
245
+ 'empty.add': '记第一条',
241
246
  'empty.filtered': '这个筛选下没有待办。',
242
247
  'meta.added': '加入于',
243
248
  'meta.due': '截止',
@@ -300,6 +305,7 @@ window.__ModuleLoader__.load({
300
305
  'action.retry': 'Retry',
301
306
  'action.add': 'Add',
302
307
  'action.more': 'More fields',
308
+ 'action.overflow': 'More actions',
303
309
  'action.less': 'Fewer fields',
304
310
  'action.detail': 'Details',
305
311
  'action.hide': 'Hide',
@@ -327,6 +333,7 @@ window.__ModuleLoader__.load({
327
333
  'action.exportHint': 'Download the whole list as JSON (the document the store keeps)',
328
334
  'action.importHint': 'Import a JSON file: same ids are updated, new ids appended, nothing deleted',
329
335
  'settings.title': 'Settings',
336
+ 'settings.context': 'Context',
330
337
  'settings.reminders': 'Due reminders',
331
338
  'settings.remindersEnable': 'Announce deadlines',
332
339
  'settings.lead': 'Lead time (min)',
@@ -339,6 +346,7 @@ window.__ModuleLoader__.load({
339
346
  'settings.save': 'Save',
340
347
  'settings.cancel': 'Cancel',
341
348
  'settings.saved': 'Settings saved',
349
+ 'settings.about': 'About',
342
350
  'notice.exported': 'Exported {n} todos',
343
351
  'notice.imported': 'Imported {n} (merged; {total} in the list now)',
344
352
  'import.badJson': 'That file is not valid JSON',
@@ -456,16 +464,17 @@ window.__ModuleLoader__.load({
456
464
  'field.linkValue': 'Absolute document path or URL',
457
465
  'field.linkTitle': 'Short label (optional)',
458
466
  'field.search': 'Search title, description, tags, attachments…',
459
- 'priority.low': 'P3 Low',
460
- 'priority.normal': 'P2 Normal',
461
- 'priority.high': 'P1 High',
462
- 'priority.urgent': 'P0 Urgent',
467
+ 'priority.low': 'Low',
468
+ 'priority.normal': 'Normal',
469
+ 'priority.high': 'High',
470
+ 'priority.urgent': 'Urgent',
463
471
  'group.by': 'Group',
464
472
  'group.time': 'Time',
465
473
  'group.level': 'Level',
466
474
  'group.manual': 'Added by hand',
467
475
  'group.other': 'Other',
468
476
  'group.items': '{n} items',
477
+ 'group.item': '1 item',
469
478
  'group.thisConversation': 'this conversation',
470
479
  'group.overdue': 'Overdue',
471
480
  'group.today': 'Today',
@@ -475,6 +484,7 @@ window.__ModuleLoader__.load({
475
484
  'group.done': 'Done',
476
485
  'group.cancelled': 'Cancelled',
477
486
  'empty.none': 'No todos yet. Write one above and press Enter.',
487
+ 'empty.add': 'Add your first todo',
478
488
  'empty.filtered': 'Nothing matches this filter.',
479
489
  'meta.added': 'added',
480
490
  'meta.due': 'due',
@@ -871,6 +881,17 @@ window.__ModuleLoader__.load({
871
881
  )
872
882
  }
873
883
  if (props.name === 'clock') return h('svg', common, h('circle', { cx: 8, cy: 8, r: 5.6 }), h('path', { d: 'M8 4.8V8l2.2 1.4' }))
884
+ if (props.name === 'close') return h('svg', common, h('path', { d: 'M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6' }))
885
+ if (props.name === 'gear') {
886
+ return h(
887
+ 'svg',
888
+ common,
889
+ h('path', { d: 'M2.6 4.4h10.8M2.6 8h10.8M2.6 11.6h10.8' }),
890
+ h('circle', { cx: 6, cy: 4.4, r: 1.5, fill: 'currentColor', stroke: 'none' }),
891
+ h('circle', { cx: 10, cy: 8, r: 1.5, fill: 'currentColor', stroke: 'none' }),
892
+ h('circle', { cx: 5.2, cy: 11.6, r: 1.5, fill: 'currentColor', stroke: 'none' }),
893
+ )
894
+ }
874
895
  if (props.name === 'chevron') return h('svg', common, h('path', { d: 'M5.6 3.8L10 8l-4.4 4.2' }))
875
896
  if (props.name === 'flag') return h('svg', common, h('path', { d: 'M4 14V2.6h7l-1.2 2.8L11 8.2H4' }))
876
897
  if (props.name === 'mute') {
@@ -887,25 +908,29 @@ window.__ModuleLoader__.load({
887
908
  // ------------------------------------------------------------------ 样式
888
909
 
889
910
  const CSS = `
890
- .gt-root { display: flex; flex-direction: column; height: 100%; min-height: 0;
891
- color: var(--dsw-alias-label-primary); font-size: 13px; }
892
- .gt-inner { display: flex; flex-direction: column; gap: 12px; width: 100%; max-width: 980px;
893
- margin: 0 auto; padding: 16px 20px 28px; min-height: 0; flex: 1; }
894
- .gt-head { display: flex; align-items: flex-start; gap: 12px; }
911
+ .gt-root { display: flex; flex-direction: column; height: 100%; min-height: 0; min-width: 0;
912
+ color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 1.5; }
913
+ .gt-inner { display: flex; flex-direction: column; gap: 14px; width: 100%; max-width: 1040px; min-width: 0;
914
+ box-sizing: border-box; margin: 0 auto; padding: 20px 24px 32px; flex: 1; }
915
+ .gt-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
916
+ .gt-head-actions { display: flex; align-items: center; gap: 6px; margin-left: auto; flex: 0 0 auto; }
895
917
  .gt-headline { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
896
- .gt-title { font-size: 17px; font-weight: 650; letter-spacing: .2px; }
897
- .gt-sub { color: var(--dsw-alias-label-secondary); font-size: 12px; }
918
+ .gt-title { font-size: 20px; line-height: 28px; font-weight: 600; letter-spacing: 0; }
919
+ .gt-sub { color: var(--dsw-alias-label-secondary); font-size: 13px; line-height: 20px; }
898
920
  .gt-spacer { flex: 1; }
899
921
  .gt-iconbtn { display: inline-flex; align-items: center; justify-content: center; gap: 5px;
900
922
  background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid transparent;
901
- border-radius: 8px; padding: 5px 7px; font: inherit; cursor: pointer; }
923
+ border-radius: 8px; padding: 0 7px; min-height: 28px; min-width: 28px; box-sizing: border-box;
924
+ font: inherit; cursor: pointer; }
902
925
  .gt-iconbtn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l1);
903
926
  background: var(--dsw-alias-bg-layer-1); }
904
- .gt-btn { display: inline-flex; align-items: center; gap: 5px; background: var(--dsw-alias-bg-layer-1);
927
+ .gt-btn { display: inline-flex; align-items: center; gap: 6px; background: var(--dsw-alias-bg-layer-1);
905
928
  color: var(--dsw-alias-label-primary); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px;
906
- padding: 4px 9px; font: inherit; cursor: pointer; white-space: nowrap; }
929
+ padding: 0 11px; min-height: 28px; box-sizing: border-box; font: inherit; font-size: 13px;
930
+ font-weight: 500; line-height: 20px; cursor: pointer; white-space: nowrap; }
907
931
  .gt-btn:hover { border-color: var(--dsw-alias-border-l2); }
908
- .gt-btn:disabled { opacity: .5; cursor: default; }
932
+ .gt-btn:disabled { opacity: 1; background: var(--dsw-alias-border-l1); color: var(--dsw-alias-label-secondary);
933
+ border-color: transparent; cursor: default; }
909
934
  .gt-btn-primary { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary);
910
935
  color: var(--dsw-alias-bg-base); font-weight: 600; }
911
936
  .gt-btn-primary:hover { border-color: var(--dsw-alias-brand-primary); }
@@ -922,22 +947,22 @@ window.__ModuleLoader__.load({
922
947
  .gt-new { display: flex; flex-direction: column; gap: 10px; }
923
948
  .gt-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
924
949
  .gt-input, .gt-select, .gt-textarea { background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary);
925
- border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 6px 9px; font: inherit; }
950
+ border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 7px 10px; font: inherit; }
926
951
  .gt-input:focus, .gt-select:focus, .gt-textarea:focus { outline: none; border-color: var(--dsw-alias-brand-primary); }
927
952
  .gt-input { flex: 1; min-width: 120px; }
928
953
  .gt-input-sm { flex: 0 1 auto; min-width: 96px; }
929
954
  .gt-textarea { width: 100%; min-height: 54px; resize: vertical; font-family: inherit; }
930
955
  .gt-select { padding: 6px 7px; }
931
- .gt-search { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 160px;
956
+ .gt-search { display: flex; align-items: center; gap: 6px; flex: 1 1 220px; min-width: 0;
932
957
  background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px;
933
- padding: 0 9px; color: var(--dsw-alias-label-secondary); }
958
+ min-height: 30px; box-sizing: border-box; padding: 0 9px; color: var(--dsw-alias-label-secondary); }
934
959
  .gt-search input { flex: 1; background: transparent; border: none; color: var(--dsw-alias-label-primary);
935
- font: inherit; padding: 6px 0; min-width: 0; }
960
+ font: inherit; font-size: 13px; padding: 0; min-width: 0; }
936
961
  .gt-search input:focus { outline: none; }
937
962
  .gt-search:focus-within { border-color: var(--dsw-alias-brand-primary); }
938
963
  .gt-kbd { font-size: 10.5px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 4px;
939
964
  padding: 0 4px; color: var(--dsw-alias-label-secondary); }
940
- .gt-dir { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px;
965
+ .gt-dir { display: inline-flex; align-items: center; gap: 5px; font-size: 12px;
941
966
  color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1);
942
967
  border-radius: 8px; padding: 3px 6px 3px 8px; max-width: 560px; }
943
968
  /* Only the path may shrink. Everything else in the pill used to shrink with it, so a
@@ -946,20 +971,30 @@ window.__ModuleLoader__.load({
946
971
  .gt-dir > .gt-muted, .gt-dir > .gt-btn, .gt-dir > svg { white-space: nowrap; flex-shrink: 0; }
947
972
  .gt-dir-path { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--dsw-alias-label-primary);
948
973
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 300px; }
949
- .gt-toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
974
+ .gt-toolbar { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
975
+ gap: 8px 10px; }
976
+ .gt-toolbar-main { grid-column: 1 / -1; grid-row: 1; display: flex; align-items: center;
977
+ gap: 8px; min-width: 0; flex-wrap: nowrap; }
978
+ .gt-toolbar-main .gt-search { flex: 1 1 auto; }
979
+ .gt-toolbar > .gt-spacer { display: none; }
980
+ .gt-toolbar > .gt-context-summary { grid-row: 2; grid-column: 1; min-width: 0; }
981
+ .gt-toolbar > .gt-tabs { grid-row: 2; grid-column: 2; justify-self: end; }
982
+ .gt-toolbar > .gt-context-details { grid-column: 1 / -1; grid-row: 3; }
983
+ @media (max-width: 1250px) { .gt-toolbar-main { flex-wrap: wrap; } }
950
984
  .gt-seg { display: inline-flex; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1);
951
985
  border-radius: 9px; padding: 2px; gap: 2px; }
952
986
  .gt-seg button { border: none; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit;
953
987
  border-radius: 7px; padding: 3px 10px; cursor: pointer; }
954
- .gt-seg button[data-on="1"] { background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-weight: 600; }
988
+ .gt-seg button[data-on="1"] { background: var(--dsw-alias-button-ghost-active-fill); color: var(--dsw-alias-label-primary); font-weight: 600; }
955
989
  .gt-chips { display: flex; gap: 6px; flex-wrap: wrap; }
956
990
  /* One segmented style for every control that is "pick one of these": filters, grouping,
957
991
  the two toggles. Chips stay for facts about a todo (tags, levels, notes). */
958
992
  .gt-tabs { display: inline-flex; gap: 2px; padding: 2px; border-radius: 8px; background: var(--dsw-alias-bg-layer-2); }
959
993
  .gt-tab { border: none; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit;
960
- font-size: 12px; border-radius: 6px; padding: 3px 9px; cursor: pointer; white-space: nowrap; }
994
+ font-size: 12.5px; border-radius: 6px; padding: 0 10px; min-height: 24px; box-sizing: border-box;
995
+ cursor: pointer; white-space: nowrap; }
961
996
  .gt-tab:hover { color: var(--dsw-alias-label-primary); }
962
- .gt-tab[data-on="1"] { background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-weight: 600; }
997
+ .gt-tab[data-on="1"] { background: var(--dsw-alias-button-ghost-active-fill); color: var(--dsw-alias-label-primary); font-weight: 600; }
963
998
  .gt-summary { color: var(--dsw-alias-label-secondary); font-size: 12px; }
964
999
  .gt-browse { border: 1px solid var(--dsw-alias-border-l1); border-radius: 9px; padding: 8px 10px;
965
1000
  display: flex; flex-direction: column; gap: 6px; max-height: 220px; overflow: auto; }
@@ -971,7 +1006,7 @@ window.__ModuleLoader__.load({
971
1006
  .gt-browse-item:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-brand-primary); }
972
1007
  .gt-divider { width: 1px; height: 16px; background: var(--dsw-alias-border-l1); }
973
1008
  .gt-context { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12px; }
974
- .gt-chip { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; padding: 2px 8px;
1009
+ .gt-chip { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; line-height: 16px; padding: 2px 8px;
975
1010
  border-radius: 999px; border: 1px solid var(--dsw-alias-border-l1); background: transparent;
976
1011
  color: var(--dsw-alias-label-secondary); }
977
1012
  button.gt-chip { cursor: pointer; font-family: inherit; }
@@ -981,27 +1016,31 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
981
1016
  .gt-chip-warn { color: var(--dsw-alias-state-warn-primary); border-color: var(--dsw-alias-state-warn-primary); }
982
1017
  .gt-chip-bad { color: var(--dsw-alias-state-error-primary); border-color: var(--dsw-alias-state-error-primary); }
983
1018
  .gt-chip-ok { color: var(--dsw-alias-state-success-primary); }
984
- .gt-groups { display: flex; flex-direction: column; gap: 14px; overflow: auto; min-height: 0; flex: 1; }
1019
+ .gt-groups { display: flex; flex-direction: column; gap: 18px; overflow: auto; min-height: 0; flex: 1; }
985
1020
  .gt-group { display: flex; flex-direction: column; gap: 6px; }
986
- .gt-group-head { display: flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary);
987
- font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em; padding: 0 2px; }
1021
+ .gt-group-head { display: flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary);
1022
+ font-size: 12px; line-height: 18px; padding: 0 2px; }
988
1023
  .gt-group-head button { border: none; background: transparent; color: inherit; cursor: pointer;
989
1024
  display: inline-flex; align-items: center; gap: 6px; font: inherit; padding: 2px 0; }
990
1025
  .gt-group-head svg { transition: transform .12s ease; transform: rotate(90deg); }
991
1026
  .gt-group[data-collapsed="1"] .gt-group-head svg { transform: rotate(0deg); }
992
1027
  .gt-group-action { margin-left: 4px; border: 1px solid transparent; background: transparent; color: inherit;
993
- border-radius: 999px; padding: 1px 8px; font: inherit; font-size: 11px; letter-spacing: .04em; cursor: pointer;
994
- display: inline-flex; align-items: center; gap: 5px; opacity: .72; transition: opacity .12s ease; }
1028
+ border-radius: 999px; padding: 3px 8px; min-height: 24px; box-sizing: border-box; font: inherit;
1029
+ font-size: 0; letter-spacing: .04em; cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
1030
+ opacity: .72; transition: opacity .12s ease; }
1031
+ .gt-group:hover .gt-group-action, .gt-group-action:focus-visible { font-size: 11px; }
995
1032
  .gt-group:hover .gt-group-action, .gt-group-action:focus-visible { opacity: 1; }
996
1033
  .gt-group-action:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l2); }
997
1034
  /* An exemption is a state, not an action: it stays visible and reads as "on" without a hover. */
998
1035
  .gt-group-action[data-on="1"] { opacity: 1; color: var(--dsw-alias-label-primary);
999
1036
  border-color: var(--dsw-alias-border-l2); }
1000
- .gt-group-count { color: var(--dsw-alias-label-secondary); }
1001
- .gt-group-label { color: var(--dsw-alias-label-primary); font-weight: 600; text-transform: none;
1002
- letter-spacing: 0; max-width: 420px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1003
- .gt-level { display: inline-flex; align-items: center; gap: 3px; font-size: 11px; padding: 0 5px;
1004
- border-radius: 999px; border: 1px solid var(--dsw-alias-border-l1); color: var(--dsw-alias-label-secondary); }
1037
+ .gt-group-count { color: var(--dsw-alias-label-secondary); font-size: 12px; font-weight: 400; }
1038
+ /* A group's level is redundant while it is expanded — the rows show it. */
1039
+ .gt-group:not([data-collapsed="1"]) .gt-group-head .gt-level { display: none; }
1040
+ .gt-group-label { color: var(--dsw-alias-label-primary); font-weight: 600; font-size: 12px;
1041
+ line-height: 18px; max-width: 480px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1042
+ .gt-level { display: inline-flex; align-items: center; gap: 3px; font-size: 11.5px; padding: 0;
1043
+ border: none; color: var(--dsw-alias-label-secondary); }
1005
1044
  .gt-level[data-level="urgent"] { color: var(--dsw-alias-state-error-primary); border-color: var(--dsw-alias-state-error-primary); font-weight: 600; }
1006
1045
  .gt-level[data-level="high"] { color: var(--dsw-alias-state-warn-primary); border-color: var(--dsw-alias-state-warn-primary); }
1007
1046
  .gt-level[data-level="low"] { color: var(--dsw-alias-label-secondary); }
@@ -1009,57 +1048,79 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1009
1048
  display: inline-block; }
1010
1049
  .gt-here { font-size: 10.5px; padding: 0 5px; border-radius: 999px; border: 1px solid var(--dsw-alias-brand-primary);
1011
1050
  color: var(--dsw-alias-brand-primary); }
1012
- .gt-list { display: flex; flex-direction: column; gap: 6px; }
1013
- .gt-item { position: relative; display: flex; flex-direction: column; gap: 9px;
1051
+ .gt-list { display: flex; flex-direction: column; gap: 8px; }
1052
+ .gt-item { position: relative; display: flex; flex-direction: column; gap: 8px;
1014
1053
  background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1);
1015
- border-radius: 11px; padding: 11px 13px; }
1054
+ border-radius: 10px; padding: 12px 14px; min-height: 72px; box-sizing: border-box; }
1016
1055
  .gt-item:hover { border-color: var(--dsw-alias-border-l2); }
1017
1056
  .gt-item[data-status="doing"] { border-left: 2px solid var(--dsw-alias-brand-primary); }
1018
1057
  .gt-item[data-overdue="1"] { border-left: 2px solid var(--dsw-alias-state-error-primary); }
1019
1058
  .gt-item[data-status="done"], .gt-item[data-status="cancelled"] { opacity: .72; }
1020
1059
  .gt-item[data-open="1"] { border-color: var(--dsw-alias-brand-primary); }
1021
1060
  .gt-item-head { display: flex; gap: 10px; align-items: flex-start; }
1022
- .gt-check { flex: 0 0 auto; width: 19px; height: 19px; margin-top: 1px; border-radius: 999px; cursor: pointer;
1061
+ .gt-check { flex: 0 0 auto; width: 24px; height: 24px; margin-top: 0; border-radius: 999px; cursor: pointer;
1023
1062
  border: 1.4px solid var(--dsw-alias-border-l2); background: transparent; color: transparent;
1024
1063
  display: inline-flex; align-items: center; justify-content: center; padding: 0; }
1025
1064
  .gt-check:hover { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); }
1026
1065
  .gt-check[data-on="1"] { border-color: var(--dsw-alias-state-success-primary);
1027
1066
  background: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-bg-base); }
1028
- .gt-pick { flex: 0 0 auto; width: 17px; height: 17px; margin-top: 2px; border-radius: 5px; cursor: pointer;
1067
+ .gt-pick { flex: 0 0 auto; width: 22px; height: 22px; margin-top: 1px; border-radius: 6px; cursor: pointer;
1029
1068
  border: 1.4px solid var(--dsw-alias-border-l2); background: transparent; color: transparent;
1030
1069
  display: inline-flex; align-items: center; justify-content: center; padding: 0; }
1031
1070
  .gt-pick[data-on="1"] { border-color: var(--dsw-alias-brand-primary); background: var(--dsw-alias-brand-primary); color: var(--dsw-alias-bg-base); }
1032
1071
  .gt-item[data-selected="1"] { border-color: var(--dsw-alias-brand-primary); }
1033
- .gt-selectbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 7px 10px;
1034
- border: 1px solid var(--dsw-alias-brand-primary); border-radius: 9px; background: var(--dsw-alias-bg-layer-1); font-size: 12.5px; }
1035
- .gt-item-body { flex: 1; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
1036
- .gt-item-title { font-weight: 600; font-size: 13.5px; word-break: break-word; line-height: 1.45; }
1037
- .gt-input-xs { width: 68px; padding: 3px 6px; font-size: 12px; }
1072
+ .gt-selectbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 9px 12px;
1073
+ border: 1px solid var(--dsw-alias-brand-primary); border-radius: 10px; background: var(--dsw-alias-bg-layer-1); font-size: 13px; }
1074
+ .gt-item-body { flex: 1; display: flex; flex-direction: column; gap: 5px; min-width: 0; cursor: pointer; }
1075
+ .gt-item-body:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; border-radius: 6px; }
1076
+ .gt-item-title { font-weight: 600; font-size: 14px; word-break: break-word; line-height: 22px;
1077
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
1078
+ .gt-input-xs { width: 64px; flex: 0 0 auto; min-width: 0; padding: 0 8px; min-height: 28px;
1079
+ box-sizing: border-box; font-size: 12.5px; }
1038
1080
  .gt-seq { font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary); }
1039
- .gt-reminders { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 10px;
1040
- border: 1px solid var(--dsw-alias-state-warn-primary); border-radius: 10px;
1041
- background: var(--dsw-alias-bg-layer-1); font-size: 12.5px; }
1042
- .gt-reminders-title { color: var(--dsw-alias-state-warn-primary); font-weight: 600; }
1043
- .gt-reminder-item { border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2);
1044
- color: var(--dsw-alias-label-primary); font: inherit; font-size: 12.5px; padding: 3px 8px;
1045
- border-radius: 999px; cursor: pointer; max-width: 260px; overflow: hidden; text-overflow: ellipsis;
1081
+ .gt-reminders { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 12px;
1082
+ min-height: 36px; box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l1);
1083
+ border-radius: 10px; background: var(--dsw-alias-bg-layer-1); font-size: 13px; }
1084
+ .gt-reminders { border-left: 2px solid var(--dsw-alias-state-warn-primary); }
1085
+ .gt-reminders-title { color: var(--dsw-alias-label-primary); font-weight: 600; }
1086
+ .gt-reminder-item { border: 1px solid transparent; background: transparent;
1087
+ color: var(--dsw-alias-label-primary); font: inherit; font-size: 13px; padding: 2px 6px;
1088
+ border-radius: 6px; cursor: pointer; max-width: 300px; overflow: hidden; text-overflow: ellipsis;
1046
1089
  white-space: nowrap; }
1047
- .gt-settings { align-self: flex-end; width: 330px; display: flex; flex-direction: column; gap: 8px;
1048
- padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px;
1049
- background: var(--dsw-alias-bg-layer-1); font-size: 12.5px; }
1050
- .gt-settings-head { font-weight: 650; }
1051
- .gt-check-line { display: flex; align-items: center; gap: 8px; justify-content: space-between;
1090
+ .gt-reminder-item:hover { background: var(--dsw-alias-button-ghost-active-fill); }
1091
+ .gt-settings { display: flex; flex-direction: column; gap: 12px; padding: 16px; width: 340px;
1092
+ box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px;
1093
+ background: var(--dsw-alias-bg-overlay); font-size: 13px; line-height: 20px; }
1094
+ .gt-settings-path { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1095
+ .gt-settings .gt-input { flex: 0 0 auto; width: 100%; min-height: 32px; }
1096
+ .gt-settings .gt-check-line .gt-input { width: 64px; }
1097
+ .gt-settings-head { font-weight: 650; font-size: 14px; }
1098
+ .gt-check-line { display: flex; align-items: center; gap: 8px; justify-content: flex-start;
1052
1099
  color: var(--dsw-alias-label-primary); }
1053
- .gt-settings-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
1100
+ .gt-settings-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
1101
+ .gt-context-summary { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
1102
+ .gt-context-summary .gt-chip { max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1103
+ .gt-context-details { display: none; flex-direction: column; gap: 8px; padding: 10px 12px;
1104
+ border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px;
1105
+ background: var(--dsw-alias-bg-layer-1); }
1106
+ .gt-context-details[data-open="1"] { display: flex; }
1107
+ .gt-detail-head { display: flex; align-items: flex-start; gap: 8px; justify-content: space-between; }
1108
+ /* Inline detail sits directly under its row: repeating the title would read as a second item. */
1109
+ .gt-root[data-wide="0"][data-wide-detail="1"] .gt-detail-title { display: none; }
1110
+ .gt-root[data-wide="0"][data-wide-detail="1"] .gt-detail-head { justify-content: flex-end; }
1111
+ .gt-detail-title { font-size: 14px; line-height: 22px; font-weight: 600; min-width: 0;
1112
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1113
+ .gt-truncated { color: var(--dsw-alias-state-warn-primary); font-size: 12px; }
1114
+ .gt-bulk-tag { flex: 0 1 120px; min-width: 90px; }
1054
1115
  .gt-inbox { margin-left: 6px; padding: 1px 6px; border-radius: 999px; font-size: 10.5px; font-weight: 600;
1055
1116
  color: var(--dsw-alias-state-warn-primary); border: 1px solid var(--dsw-alias-state-warn-primary);
1056
1117
  vertical-align: 1px; }
1057
1118
  .gt-item[data-status="done"] .gt-item-title, .gt-item[data-status="cancelled"] .gt-item-title {
1058
1119
  text-decoration: line-through; color: var(--dsw-alias-label-secondary); }
1059
- .gt-meta { display: flex; gap: 5px 8px; flex-wrap: wrap; color: var(--dsw-alias-label-secondary); font-size: 11.5px;
1060
- align-items: center; }
1120
+ .gt-meta { display: flex; gap: 2px 0; flex-wrap: wrap; color: var(--dsw-alias-label-secondary); font-size: 12px;
1121
+ line-height: 18px; align-items: center; }
1061
1122
  .gt-meta > span { display: inline-flex; align-items: center; }
1062
- .gt-meta-sep { opacity: .45; }
1123
+ .gt-meta > span + span::before { content: '·'; margin: 0 6px; opacity: .45; }
1063
1124
  .gt-tag { color: var(--dsw-alias-brand-primary); }
1064
1125
  /* The dispatch action stays put: hiding the only way to hand work to an agent
1065
1126
  behind a hover makes a lone todo look like it can do nothing. */
@@ -1068,25 +1129,25 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1068
1129
  .gt-attach .gt-chip { max-width: 280px; color: var(--dsw-alias-label-secondary); }
1069
1130
  .gt-attach a.gt-chip:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l2); }
1070
1131
  .gt-attach-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1071
- .gt-menu { position: absolute; top: 40px; right: 10px; z-index: 3; min-width: 168px; display: flex;
1072
- flex-direction: column; padding: 4px; background: var(--dsw-alias-bg-overlay);
1132
+ .gt-menu { position: absolute; top: 40px; right: 10px; z-index: 3; min-width: 184px; display: flex;
1133
+ flex-direction: column; padding: 5px; background: var(--dsw-alias-bg-overlay);
1073
1134
  border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px; }
1074
1135
  .gt-menu button { display: flex; align-items: center; gap: 8px; text-align: left; border: none;
1075
- background: transparent; color: var(--dsw-alias-label-primary); font: inherit; padding: 6px 8px;
1136
+ background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 13px; padding: 8px 10px;
1076
1137
  border-radius: 7px; cursor: pointer; }
1077
- .gt-menu button:hover { background: var(--dsw-alias-bg-layer-2); }
1138
+ .gt-menu button:hover { background: var(--dsw-alias-button-ghost-active-fill); }
1078
1139
  .gt-menu button[data-danger="1"] { color: var(--dsw-alias-state-error-primary); }
1079
- .gt-detail { display: flex; flex-direction: column; gap: 10px; border-top: 1px solid var(--dsw-alias-border-l1);
1080
- padding-top: 10px; margin-top: 2px; }
1140
+ .gt-detail { display: flex; flex-direction: column; gap: 12px; border-top: 1px solid var(--dsw-alias-border-l1);
1141
+ padding-top: 12px; margin-top: 2px; }
1081
1142
  /* Wide screens: the open row's detail becomes a card on the right instead of pushing the list
1082
1143
  down. Deliberately CSS-only over unchanged markup: one detail implementation, and it only
1083
1144
  applies where there is room for a second column. */
1084
- @media (min-width: 1100px) {
1145
+ /* Wide layout follows the panel's measured width (data-wide), not the viewport. */
1085
1146
  /* Only the list gives way: the title row and the toolbar keep their full width, so the
1086
1147
  controls never wrap or hide behind the card. */
1087
- .gt-root[data-wide-detail="1"] .gt-groups { padding-right: 380px; }
1088
- .gt-root[data-wide-detail="1"] .gt-inner { position: relative; }
1089
- .gt-root[data-wide-detail="1"] .gt-item[data-open="1"] .gt-detail {
1148
+ .gt-root[data-wide="1"][data-wide-detail="1"] .gt-groups { padding-right: 360px; }
1149
+ .gt-root[data-wide="1"][data-wide-detail="1"] .gt-inner { position: relative; }
1150
+ .gt-root[data-wide="1"][data-wide-detail="1"] .gt-item[data-open="1"] .gt-detail {
1090
1151
  /* Anchored to the panel's own column (.gt-inner), not to the viewport: the slot does not
1091
1152
  always fill the window, and a viewport-centred offset put the card over the list. */
1092
1153
  /* The var fallback matters for the static previews (server-rendered, so effects never
@@ -1095,21 +1156,24 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1095
1156
  a gap instead of overlapping by the padding width. */
1096
1157
  box-sizing: border-box;
1097
1158
  position: fixed; top: var(--gt-detail-top, 208px);
1098
- right: var(--gt-detail-right, max(24px, calc((100vw - 980px) / 2 + 24px)));
1099
- bottom: var(--gt-detail-bottom, 64px); width: 360px;
1159
+ right: var(--gt-detail-right, max(24px, calc((100vw - 1040px) / 2 + 24px)));
1160
+ bottom: var(--gt-detail-bottom, 64px); width: 340px;
1100
1161
  overflow: auto; padding: 16px; margin: 0; border: 1px solid var(--dsw-alias-border-l2);
1101
1162
  border-radius: 12px; background: var(--dsw-alias-bg-overlay); z-index: 30;
1102
1163
  }
1103
- }
1104
1164
  .gt-section { display: flex; flex-direction: column; gap: 5px; }
1105
1165
  .gt-completion { white-space: pre-wrap; word-break: break-word; line-height: 1.5; }
1106
- .gt-section-title { color: var(--dsw-alias-label-secondary); font-size: 11.5px; text-transform: uppercase;
1107
- letter-spacing: .06em; }
1166
+ .gt-section-title { color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; font-weight: 600; }
1108
1167
  .gt-pre { white-space: pre-wrap; word-break: break-word; margin: 0; line-height: 1.6; }
1109
1168
  .gt-muted { color: var(--dsw-alias-label-secondary); }
1110
1169
  .gt-links { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
1111
1170
  .gt-link { display: flex; gap: 8px; align-items: center; padding: 4px 0; min-width: 0; }
1112
1171
  .gt-link-main { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1; }
1172
+ .gt-note-item { display: flex; flex-direction: column; gap: 4px; padding: 8px 0; }
1173
+ .gt-note-item + .gt-note-item { border-top: 1px solid var(--dsw-alias-border-l1); }
1174
+ .gt-note-meta { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--dsw-alias-label-secondary);
1175
+ white-space: nowrap; }
1176
+ .gt-note-text { font-size: 13px; line-height: 1.6; white-space: pre-wrap; word-break: break-word; max-width: 68ch; }
1113
1177
  .gt-link a { color: var(--dsw-alias-brand-primary); text-decoration: none; }
1114
1178
  .gt-link a:hover { text-decoration: underline; }
1115
1179
  .gt-link-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 320px; }
@@ -1471,9 +1535,19 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1471
1535
  if (Number.isInteger(todo.seq) && todo.seq > 0) {
1472
1536
  meta.push(h('span', { key: 'seq', className: 'gt-seq', title: t('meta.seqHint') }, `#${todo.seq}`))
1473
1537
  }
1474
- meta.push(
1475
- h('span', { key: 'added', title: new Date(todo.createdAt).toLocaleString() }, `${t('meta.added')} ${absoluteTime(todo.createdAt)}(${ageText(lang, todo.createdAt, now)})`),
1476
- )
1538
+ if (todo.dueAt !== null) {
1539
+ meta.push(
1540
+ h(
1541
+ 'span',
1542
+ { key: 'due', className: overdue ? 'gt-chip-bad' : '', title: new Date(todo.dueAt).toLocaleString() },
1543
+ overdue
1544
+ ? t('meta.overdue', { n: daysLate(todo.dueAt, now) })
1545
+ : sameLocalDay(Date.parse(todo.dueAt), now)
1546
+ ? t('meta.today')
1547
+ : `${t('meta.due')} ${absoluteTime(Date.parse(todo.dueAt))}`,
1548
+ ),
1549
+ )
1550
+ }
1477
1551
  if (todo.priority !== 'normal') {
1478
1552
  meta.push(
1479
1553
  h(
@@ -1484,23 +1558,17 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1484
1558
  ),
1485
1559
  )
1486
1560
  }
1487
- if (todo.dueAt !== null) {
1561
+ meta.push(h('span', { key: 'added', title: new Date(todo.createdAt).toLocaleString() }, ageText(lang, todo.createdAt, now)))
1562
+ if (todo.tags.length > 0) {
1563
+ const shown = todo.tags.slice(0, 4)
1488
1564
  meta.push(
1489
1565
  h(
1490
1566
  'span',
1491
- { key: 'due', className: overdue ? 'gt-chip-bad' : '', title: new Date(todo.dueAt).toLocaleString() },
1492
- overdue
1493
- ? t('meta.overdue', { n: daysLate(todo.dueAt, now) })
1494
- : sameLocalDay(Date.parse(todo.dueAt), now)
1495
- ? t('meta.today')
1496
- : `${t('meta.due')} ${absoluteTime(Date.parse(todo.dueAt))}`,
1567
+ { key: 'tags', className: 'gt-tag', title: todo.tags.map((tag) => `#${tag}`).join(' ') },
1568
+ shown.map((tag) => `#${tag}`).join(' ') + (todo.tags.length > shown.length ? ` +${todo.tags.length - shown.length}` : ''),
1497
1569
  ),
1498
1570
  )
1499
1571
  }
1500
- if (todo.tags.length > 0) {
1501
- meta.push(h('span', { key: 'tags', className: 'gt-tag' }, todo.tags.map((tag) => `#${tag}`).join(' ')))
1502
- }
1503
- meta.push(h('span', { key: 'by' }, t(`meta.by.${todo.createdBy === 'agent' ? 'agent' : 'human'}`)))
1504
1572
  if (Array.isArray(todo.notes) && todo.notes.length > 0) {
1505
1573
  const latest = todo.notes[todo.notes.length - 1]
1506
1574
  meta.push(h('span', { key: 'notes', title: latest.text }, t('note.count', { n: todo.notes.length })))
@@ -1508,12 +1576,8 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1508
1576
  if (todo.completion !== null && todo.completion !== undefined) {
1509
1577
  meta.push(h('span', { key: 'completed', title: todo.completion.summary }, t('meta.completed', { when: absoluteTime(todo.completion.at) })))
1510
1578
  }
1511
- if (typeof todo.originDir === 'string' && todo.originDir !== '') {
1512
- meta.push(h('span', { key: 'origin', title: todo.originDir }, t('meta.origin', { dir: shortPath(todo.originDir) })))
1513
- }
1579
+ meta.push(h('span', { key: 'by' }, t(`meta.by.${todo.createdBy === 'agent' ? 'agent' : 'human'}`)))
1514
1580
  if (todo.sessionId !== '') {
1515
- // A live conversation with no working directory cannot do the work. Say
1516
- // so on the row instead of letting the next hand-off land there again.
1517
1581
  const stranded = todo.sessionAlive === true && (todo.sessionDir ?? '') === ''
1518
1582
  meta.push(
1519
1583
  h(
@@ -1521,9 +1585,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1521
1585
  {
1522
1586
  key: 'session',
1523
1587
  className: stranded ? 'gt-chip-warn' : todo.sessionAlive === true ? 'gt-chip-ok' : '',
1524
- title: stranded
1525
- ? t('meta.session.noDirHint')
1526
- : (todo.sessionDir ?? ''),
1588
+ title: stranded ? t('meta.session.noDirHint') : (todo.sessionDir ?? ''),
1527
1589
  },
1528
1590
  stranded ? t('meta.session.noDir') : todo.sessionAlive === true ? t('meta.session.live') : t('meta.session.bound'),
1529
1591
  ),
@@ -1588,7 +1650,21 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1588
1650
  ),
1589
1651
  h(
1590
1652
  'div',
1591
- { className: 'gt-item-body' },
1653
+ {
1654
+ className: 'gt-item-body',
1655
+ role: 'button',
1656
+ tabIndex: 0,
1657
+ 'aria-expanded': open,
1658
+ onClick: (event) => {
1659
+ if (event.target?.closest?.('a,button,input,textarea,select') !== null) return
1660
+ onToggleOpen(todo.id)
1661
+ },
1662
+ onKeyDown: (event) => {
1663
+ if (event.key !== 'Enter' && event.key !== ' ') return
1664
+ event.preventDefault()
1665
+ onToggleOpen(todo.id)
1666
+ },
1667
+ },
1592
1668
  h(
1593
1669
  'div',
1594
1670
  { className: 'gt-item-title' },
@@ -1600,9 +1676,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1600
1676
  h(
1601
1677
  'div',
1602
1678
  { className: 'gt-meta' },
1603
- meta.flatMap((node, index) =>
1604
- index === 0 ? [node] : [h('span', { key: `sep${index}`, className: 'gt-meta-sep' }, '·'), node],
1605
- ),
1679
+ meta,
1606
1680
  ),
1607
1681
  // What is attached is half the point of the list, so the first few are
1608
1682
  // shown as chips instead of being reported as a number.
@@ -1701,7 +1775,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1701
1775
  'button',
1702
1776
  {
1703
1777
  type: 'button',
1704
- className: 'gt-btn gt-btn-brand',
1778
+ className: 'gt-btn gt-btn-quiet',
1705
1779
  disabled: busy,
1706
1780
  title: dispatchLabel,
1707
1781
  onClick: () => onAction('dispatch', todo),
@@ -1752,6 +1826,16 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1752
1826
  ? h(
1753
1827
  'div',
1754
1828
  { className: 'gt-detail' },
1829
+ h(
1830
+ 'div',
1831
+ { className: 'gt-detail-head' },
1832
+ h('span', { className: 'gt-detail-title' }, `${Number.isInteger(todo.seq) && todo.seq > 0 ? `#${todo.seq} ` : ''}${todo.title}`),
1833
+ h(
1834
+ 'button',
1835
+ { type: 'button', className: 'gt-iconbtn', title: t('action.hide'), 'aria-label': t('action.hide'), onClick: () => onToggleOpen(todo.id) },
1836
+ h(Icon, { name: 'close', size: 13 }),
1837
+ ),
1838
+ ),
1755
1839
  h(
1756
1840
  'div',
1757
1841
  { className: 'gt-section' },
@@ -1836,14 +1920,15 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1836
1920
  (todo.notes ?? []).map((entry, index) =>
1837
1921
  h(
1838
1922
  'li',
1839
- { className: 'gt-link', key: `note:${index}:${entry.at}` },
1923
+ { className: 'gt-note-item', key: `note:${index}:${entry.at}` },
1840
1924
  h(
1841
- 'span',
1842
- { className: 'gt-link-main' },
1843
- h(Icon, { name: 'doc', size: 13 }),
1844
- h('span', { className: 'gt-muted' }, `${absoluteTime(entry.at)} · ${entry.by === 'agent' ? t('note.by.agent') : t('note.by.human')}`),
1845
- h('span', { className: 'gt-completion' }, entry.text),
1925
+ 'div',
1926
+ { className: 'gt-note-meta' },
1927
+ h(Icon, { name: 'doc', size: 12 }),
1928
+ h('span', null, absoluteTime(entry.at)),
1929
+ h('span', null, entry.by === 'agent' ? t('note.by.agent') : t('note.by.human')),
1846
1930
  ),
1931
+ h('div', { className: 'gt-note-text' }, entry.text),
1847
1932
  ),
1848
1933
  ),
1849
1934
  ),
@@ -2082,6 +2167,12 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
2082
2167
  // One settings popover: reminders and the external door both need a place a person can
2083
2168
  // reach without curl.
2084
2169
  const [settingsOpen, setSettingsOpen] = useState(false)
2170
+ const [settingsBox, setSettingsBox] = useState(null)
2171
+ // The detail card's breakpoint follows the panel's own width, not the viewport: the
2172
+ // slot is ~265px narrower than the window, so a viewport query opens the two-column
2173
+ // layout while the panel is still too narrow for it.
2174
+ const [panelWidth, setPanelWidth] = useState(null)
2175
+ const [contextOpen, setContextOpen] = useState(false)
2085
2176
  const [tokenDraft, setTokenDraft] = useState('')
2086
2177
  const [ingestDraft, setIngestDraft] = useState(false)
2087
2178
  const [belowDraft, setBelowDraft] = useState('0.8')
@@ -2321,6 +2412,40 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
2321
2412
  }
2322
2413
  }, [lang])
2323
2414
 
2415
+ useEffect(() => {
2416
+ const element = innerRef.current
2417
+ if (element === null || typeof window === 'undefined') return undefined
2418
+ const measure = () => setPanelWidth(element.getBoundingClientRect().width)
2419
+ measure()
2420
+ window.addEventListener('resize', measure)
2421
+ const observer = typeof window.ResizeObserver === 'function' ? new window.ResizeObserver(measure) : null
2422
+ if (observer !== null) observer.observe(element)
2423
+ return () => {
2424
+ window.removeEventListener('resize', measure)
2425
+ if (observer !== null) observer.disconnect()
2426
+ }
2427
+ }, [])
2428
+
2429
+ useEffect(() => {
2430
+ if (!settingsOpen || typeof window === 'undefined') {
2431
+ setSettingsBox(null)
2432
+ return undefined
2433
+ }
2434
+ const measure = () => {
2435
+ const element = innerRef.current
2436
+ if (element === null) return
2437
+ const rect = element.getBoundingClientRect()
2438
+ setSettingsBox({
2439
+ top: Math.round(rect.top + 210),
2440
+ right: Math.round(window.innerWidth - rect.right + 20),
2441
+ bottom: Math.round(window.innerHeight - rect.bottom + 20),
2442
+ })
2443
+ }
2444
+ measure()
2445
+ window.addEventListener('resize', measure)
2446
+ return () => window.removeEventListener('resize', measure)
2447
+ }, [settingsOpen, lang])
2448
+
2324
2449
  useEffect(() => {
2325
2450
  if (expandedId === '' || typeof window === 'undefined') {
2326
2451
  setDetailAnchor(null)
@@ -2916,6 +3041,8 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
2916
3041
 
2917
3042
  /** Fill the popover from the host's state, so saving cannot send stale values. */
2918
3043
  const openSettings = () => {
3044
+ // One inspector at a time: the settings card and the detail card occupy the same side.
3045
+ setExpandedId('')
2919
3046
  const current = state.health?.settings ?? {}
2920
3047
  setIngestDraft(current.ingest?.enabled === true)
2921
3048
  setBelowDraft(String(current.ingest?.confirmBelow ?? 0.8))
@@ -3050,6 +3177,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3050
3177
  {
3051
3178
  className: 'gt-root',
3052
3179
  'data-wide-detail': expandedId === '' ? '0' : '1',
3180
+ 'data-wide': panelWidth === null || panelWidth >= 960 ? '1' : '0',
3053
3181
  // The slot does not always fill the window, so the card's viewport offsets are
3054
3182
  // measured from the panel itself instead of assumed from 50vw.
3055
3183
  style:
@@ -3071,71 +3199,48 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3071
3199
  'div',
3072
3200
  { className: 'gt-headline' },
3073
3201
  h('div', { className: 'gt-title' }, t('panel.title')),
3074
- h(
3075
- 'div',
3076
- { className: 'gt-sub' },
3077
- t('panel.subtitle'),
3078
- h('span', { className: 'gt-summary' }, ' · '),
3079
- h('span', { className: 'gt-summary' }, t('summary.active', { n: counts.open + counts.doing })),
3080
- counts.overdue > 0
3081
- ? h('span', { className: 'gt-summary' }, ` · ${t('summary.overdue', { n: counts.overdue })}`)
3082
- : null,
3083
- counts.done > 0
3084
- ? h('span', { className: 'gt-summary' }, ` · ${t('summary.done', { n: counts.done })}`)
3085
- : null,
3086
- ),
3202
+ h('div', { className: 'gt-sub' }, t('panel.subtitle')),
3087
3203
  ),
3088
- h('div', { className: 'gt-spacer' }),
3089
3204
  h(
3090
- 'button',
3091
- {
3092
- type: 'button',
3093
- className: 'gt-btn gt-btn-primary',
3094
- 'aria-expanded': adding,
3095
- onClick: () => {
3096
- setAdding((value) => !value)
3097
- setMore(false)
3205
+ 'div',
3206
+ { className: 'gt-head-actions' },
3207
+ h(
3208
+ 'button',
3209
+ {
3210
+ type: 'button',
3211
+ className: 'gt-iconbtn',
3212
+ title: t('settings.title'),
3213
+ 'aria-label': t('settings.title'),
3214
+ onClick: openSettings,
3098
3215
  },
3099
- },
3100
- h(Icon, { name: 'plus', size: 13 }),
3101
- t('action.add'),
3102
- ),
3103
- h(
3104
- 'button',
3105
- { type: 'button', className: 'gt-iconbtn', onClick: () => void reload(), disabled: loading, title: t('action.refresh'), 'aria-label': t('action.refresh') },
3106
- h(Icon, { name: 'refresh' }),
3107
- ),
3108
- // Import/export of the same document the store keeps: the small door
3109
- // out of a plugin whose whole point is that the list is a file.
3110
- h(
3111
- 'button',
3112
- {
3113
- type: 'button',
3114
- className: 'gt-btn gt-btn-quiet',
3115
- title: t('action.exportHint'),
3116
- onClick: () => void exportJson(),
3117
- },
3118
- t('action.export'),
3119
- ),
3120
- h(
3121
- 'button',
3122
- {
3123
- type: 'button',
3124
- className: 'gt-btn gt-btn-quiet',
3125
- title: t('action.importHint'),
3126
- onClick: () => importRef.current?.click?.(),
3127
- },
3128
- t('action.import'),
3216
+ h(Icon, { name: 'gear' }),
3217
+ ),
3218
+ h(
3219
+ 'button',
3220
+ {
3221
+ type: 'button',
3222
+ className: adding ? 'gt-btn gt-btn-quiet' : 'gt-btn gt-btn-primary',
3223
+ 'aria-expanded': adding,
3224
+ title: adding ? t('action.less') : t('action.add'),
3225
+ onClick: () => {
3226
+ setAdding((value) => !value)
3227
+ setMore(false)
3228
+ },
3229
+ },
3230
+ h(Icon, { name: adding ? 'close' : 'plus', size: 13 }),
3231
+ adding ? '' : t('action.add'),
3129
3232
  ),
3130
- h(
3131
- 'button',
3132
- {
3133
- type: 'button',
3134
- className: 'gt-btn gt-btn-quiet',
3135
- 'aria-expanded': settingsOpen,
3136
- onClick: () => (settingsOpen ? setSettingsOpen(false) : openSettings()),
3137
- },
3138
- t('settings.title'),
3233
+ // One overflow menu instead of four competing buttons: the primary action is
3234
+ // adding a todo; everything else is occasional.
3235
+ h(Menu, {
3236
+ label: t('action.overflow'),
3237
+ items: [
3238
+ { key: 'refresh', label: t('action.refresh'), icon: 'refresh', onSelect: () => void reload() },
3239
+ { key: 'export', label: t('action.export'), icon: 'doc', onSelect: () => void exportJson() },
3240
+ { key: 'import', label: t('action.import'), icon: 'plus', onSelect: () => importRef.current?.click?.() },
3241
+ { key: 'settings', label: t('settings.title'), icon: 'gear', onSelect: () => openSettings() },
3242
+ ],
3243
+ }),
3139
3244
  ),
3140
3245
  h('input', {
3141
3246
  ref: importRef,
@@ -3149,7 +3254,23 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3149
3254
  settingsOpen
3150
3255
  ? h(
3151
3256
  'div',
3152
- { className: 'gt-settings', role: 'dialog', 'aria-label': t('settings.title') },
3257
+ {
3258
+ className: 'gt-settings',
3259
+ role: 'dialog',
3260
+ 'aria-label': t('settings.title'),
3261
+ style:
3262
+ settingsBox === null
3263
+ ? undefined
3264
+ : {
3265
+ position: 'fixed',
3266
+ top: `${settingsBox.top}px`,
3267
+ right: `${settingsBox.right}px`,
3268
+ bottom: `${settingsBox.bottom}px`,
3269
+ maxHeight: 'none',
3270
+ overflow: 'auto',
3271
+ zIndex: 40,
3272
+ },
3273
+ },
3153
3274
  h('div', { className: 'gt-settings-head' }, t('settings.title')),
3154
3275
  h('div', { className: 'gt-section-title' }, t('settings.reminders')),
3155
3276
  h(
@@ -3191,8 +3312,8 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3191
3312
  'div',
3192
3313
  { className: 'gt-settings-row' },
3193
3314
  h('button', { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: generateToken }, t('settings.generate')),
3194
- h('button', { type: 'button', className: 'gt-btn gt-btn-brand', onClick: () => void saveSettings() }, t('settings.save')),
3195
3315
  h('button', { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => setSettingsOpen(false) }, t('settings.cancel')),
3316
+ h('button', { type: 'button', className: 'gt-btn gt-btn-primary', onClick: () => void saveSettings() }, t('settings.save')),
3196
3317
  ),
3197
3318
  h(
3198
3319
  'label',
@@ -3208,6 +3329,13 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3208
3329
  onChange: (event) => setBelowDraft(event.target.value),
3209
3330
  }),
3210
3331
  ),
3332
+ h('div', { className: 'gt-section-title' }, t('settings.about')),
3333
+ h(
3334
+ 'div',
3335
+ { className: 'gt-muted gt-mono gt-settings-path', title: state.health?.store?.file ?? '' },
3336
+ state.health?.store?.file ?? '',
3337
+ ),
3338
+ h('div', { className: 'gt-muted' }, `v${state.health?.version ?? ''} · ${UI_REV}`),
3211
3339
  )
3212
3340
  : null,
3213
3341
 
@@ -3215,8 +3343,13 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3215
3343
  ? h(
3216
3344
  'div',
3217
3345
  { className: 'gt-banner', role: 'alert' },
3218
- `${t('error.prefix')}: ${error.message} (${error.reason ?? 'internal'})`,
3346
+ h('span', { className: 'gt-banner-text' }, `${t('error.prefix')}: ${error.message}`),
3219
3347
  h('button', { type: 'button', className: 'gt-btn', onClick: () => void reload() }, t('action.retry')),
3348
+ h(
3349
+ 'button',
3350
+ { type: 'button', className: 'gt-iconbtn', title: t('action.hide'), 'aria-label': t('action.hide'), onClick: () => setError(null) },
3351
+ h(Icon, { name: 'close', size: 13 }),
3352
+ ),
3220
3353
  )
3221
3354
  : null,
3222
3355
 
@@ -3240,14 +3373,14 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3240
3373
  }),
3241
3374
  h(
3242
3375
  'button',
3243
- { type: 'button', className: 'gt-btn gt-btn-primary', disabled: busyId === '__new__' || draft.title.trim() === '', onClick: () => void submit() },
3244
- h(Icon, { name: 'plus', size: 13 }),
3245
- t('action.add'),
3376
+ { type: 'button', className: 'gt-btn gt-btn-quiet', 'aria-expanded': more, onClick: () => setMore((value) => !value) },
3377
+ more ? t('action.less') : t('action.more'),
3246
3378
  ),
3247
3379
  h(
3248
3380
  'button',
3249
- { type: 'button', className: 'gt-btn gt-btn-quiet', 'aria-expanded': more, onClick: () => setMore((value) => !value) },
3250
- more ? t('action.less') : t('action.more'),
3381
+ { type: 'button', className: 'gt-btn gt-btn-primary', disabled: busyId === '__new__' || draft.title.trim() === '', onClick: () => void submit() },
3382
+ h(Icon, { name: 'plus', size: 13 }),
3383
+ t('action.add'),
3251
3384
  ),
3252
3385
  ),
3253
3386
  more
@@ -3306,6 +3439,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3306
3439
  h(
3307
3440
  'div',
3308
3441
  { className: 'gt-toolbar' },
3442
+ h('div', { className: 'gt-toolbar-main' },
3309
3443
  h(
3310
3444
  'span',
3311
3445
  { className: 'gt-tabs', role: 'group', 'aria-label': t('filter.label') },
@@ -3361,12 +3495,37 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3361
3495
  ),
3362
3496
  ),
3363
3497
  ),
3498
+ ),
3364
3499
  h('div', { className: 'gt-spacer' }),
3500
+ h(
3501
+ 'div',
3502
+ { className: 'gt-context-summary' },
3503
+ h(
3504
+ 'button',
3505
+ { type: 'button', className: 'gt-chip', title: effectiveDir === '' ? t('dir.none') : effectiveDir, onClick: () => setContextOpen((value) => !value) },
3506
+ h(Icon, { name: 'doc', size: 12 }),
3507
+ effectiveDir === '' ? t('dir.none') : effectiveDir,
3508
+ ),
3509
+ h(
3510
+ 'button',
3511
+ { type: 'button', className: 'gt-chip', title: t('target.pick'), onClick: () => setContextOpen((value) => !value) },
3512
+ h(Icon, { name: 'play', size: 11 }),
3513
+ `${t('target.label')}: ${conversations.find((entry) => entry.id === targetValue)?.title ?? t('target.auto')}`,
3514
+ ),
3515
+ h(
3516
+ 'button',
3517
+ { type: 'button', className: 'gt-btn gt-btn-quiet', 'aria-expanded': contextOpen, onClick: () => setContextOpen((value) => !value) },
3518
+ t('settings.context'),
3519
+ ),
3520
+ ),
3521
+ h(
3522
+ 'div',
3523
+ { className: 'gt-context-details', 'data-open': contextOpen ? '1' : '0' },
3365
3524
  h(
3366
3525
  'span',
3367
3526
  { className: 'gt-dir', title: effectiveDir === '' ? t('dir.none') : effectiveDir },
3368
3527
  h(Icon, { name: 'doc', size: 12 }),
3369
- h('span', { className: 'gt-muted' }, `${t('field.dir')}:`),
3528
+ h('span', { className: 'gt-muted' }, `${t('field.dir')}: `),
3370
3529
  h('span', { className: 'gt-dir-path' }, effectiveDir === '' ? t('dir.none') : effectiveDir),
3371
3530
  h('span', { className: 'gt-muted' }, pickedDir === '' ? t('dir.fromSession') : t('dir.picked')),
3372
3531
  h('button', { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => void chooseDir() }, t('action.pickDir')),
@@ -3471,7 +3630,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3471
3630
  ),
3472
3631
  ),
3473
3632
  ),
3474
- h('span', { className: 'gt-muted' }, `${t('target.label')}:`),
3633
+ h('span', { className: 'gt-muted' }, `${t('target.label')}: `),
3475
3634
  h(
3476
3635
  'select',
3477
3636
  {
@@ -3501,6 +3660,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3501
3660
  { value: 'auto', label: t('target.auto') },
3502
3661
  ].map((option) => h('option', { key: option.value, value: option.value }, option.label)),
3503
3662
  ),
3663
+ ),
3504
3664
  h('span', { className: 'gt-spacer' }),
3505
3665
  h('span', { className: 'gt-tabs', role: 'group', 'aria-label': t('autoCollect.label') }, togglesNodes),
3506
3666
  ),
@@ -3731,6 +3891,20 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3731
3891
  'div',
3732
3892
  { className: 'gt-empty' },
3733
3893
  h('div', { className: 'gt-empty-title' }, (state.counts?.total ?? 0) === 0 && query.trim() === '' ? t('empty.none') : t('empty.filtered')),
3894
+ (state.counts?.total ?? 0) === 0 && query.trim() === ''
3895
+ ? h(
3896
+ 'button',
3897
+ {
3898
+ type: 'button',
3899
+ className: 'gt-btn gt-btn-primary',
3900
+ onClick: () => {
3901
+ setAdding(true)
3902
+ setMore(false)
3903
+ },
3904
+ },
3905
+ t('empty.add'),
3906
+ )
3907
+ : null,
3734
3908
  h('div', { className: 'gt-sub' }, t('hint.keys')),
3735
3909
  )
3736
3910
  : h(
@@ -3755,7 +3929,11 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3755
3929
  },
3756
3930
  h(Icon, { name: 'chevron', size: 12 }),
3757
3931
  h('span', { className: 'gt-group-label' }, group.label),
3758
- h('span', { className: 'gt-group-count' }, t('group.items', { n: group.todos.length })),
3932
+ h(
3933
+ 'span',
3934
+ { className: 'gt-group-count' },
3935
+ group.todos.length === 1 ? t('group.item') : t('group.items', { n: group.todos.length }),
3936
+ ),
3759
3937
  group.level === 'normal'
3760
3938
  ? null
3761
3939
  : h(
@@ -3821,7 +3999,10 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3821
3999
  open: expandedId === todo.id,
3822
4000
  dispatchLabel,
3823
4001
  onAction,
3824
- onToggleOpen: (id) => setExpandedId((current) => (current === id ? '' : id)),
4002
+ onToggleOpen: (id) => {
4003
+ setSettingsOpen(false)
4004
+ setExpandedId((current) => (current === id ? '' : id))
4005
+ },
3825
4006
  }),
3826
4007
  ),
3827
4008
  ),
@@ -3837,15 +4018,6 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3837
4018
  )
3838
4019
  : null,
3839
4020
 
3840
- h(
3841
- 'div',
3842
- { className: 'gt-foot' },
3843
- state.health !== null ? h('span', null, t('store.at', { file: state.health.store?.file ?? '' })) : null,
3844
- state.health !== null
3845
- ? h('span', { className: 'gt-mono' }, `v${state.health.version ?? ''}·${UI_REV}`)
3846
- : null,
3847
- h('span', null, t('hint.keys')),
3848
- ),
3849
4021
  ),
3850
4022
  notice !== null && notice !== undefined
3851
4023
  ? h(
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.5.3'
39
+ export const VERSION = '0.6.1'
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.5.3",
3
+ "version": "0.6.1",
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",