dsh-global-todo 0.6.1 → 0.6.3

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,27 @@
1
+ ## [0.6.3] — 2026-10-20
2
+
3
+ States that were still speaking to nobody.
4
+
5
+ - **The selection bar has one primary action.** Count and 全选/清空 sit on the left, `Assign` is
6
+ the only filled button, `Complete`/`Archive`/tag stay quiet, and the ambiguous `Done` becomes an
7
+ icon-only exit (it read as a second `Complete`).
8
+ - **The inbox explains itself**, and an empty filter now offers `Clear filter` instead of dead air.
9
+ - **The reminder bar says which set it is.** Copy moves to `Dismiss all` and the bar carries a note
10
+ that these reached their reminder time, unlike Overdue.
11
+ - **Selection bar is sticky and 36px tall**, so the batch actions stay reachable while scrolling.
12
+
13
+ ## [0.6.2] — 2026-10-20
14
+
15
+ The narrow-width details the spec called for after the two-column work.
16
+
17
+ - **Grouping becomes a dropdown when the column is narrow.** The three segmented buttons collapse
18
+ into one `select` (Conversation / Time / Level) below 960px, so the toolbar stops taking a row
19
+ for grouping at the width where rows are already tight.
20
+ - **The inbox explains itself.** Opening the inbox view now leads with one line: items were written
21
+ by external sources or the agent and only join the list after confirmation.
22
+ - **Toasts are anchored to the panel.** `position: absolute` on the panel root instead of `fixed`
23
+ against the viewport, so a notice appears above the list rather than at the bottom of the window.
24
+
1
25
  ## [0.6.1] — 2026-10-20
2
26
 
3
27
  The follow-up pass from the design spec: the breakpoint now follows the panel, not the window,
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 = 'ui37'
53
+ const UI_REV = 'ui39'
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
 
@@ -106,6 +106,7 @@ window.__ModuleLoader__.load({
106
106
  'settings.cancel': '取消',
107
107
  'settings.saved': '设置已保存',
108
108
  'settings.about': '关于',
109
+ 'inbox.explain': '以下由外部写入或 Agent 提交,确认后才进入清单。',
109
110
  'notice.exported': '已导出 {n} 条待办',
110
111
  'notice.imported': '已导入 {n} 条(合并,现有共 {total} 条)',
111
112
  'import.badJson': '这个文件不是合法的 JSON',
@@ -128,6 +129,7 @@ window.__ModuleLoader__.load({
128
129
  'notice.bulkPartial': '已更新 {n} 条,跳过 {m} 条',
129
130
  'select.clear': '清除',
130
131
  'select.done': '退出选择',
132
+ 'select.clear': '清空',
131
133
  'select.hint': '先勾几条待办,再点「一键分配」:优先回到写下它的那个对话,其次同工作区的对话,都找不到才让你选。',
132
134
  'assign.working': '正在分配…',
133
135
  'assign.summary': '已分配 {n} 条(原对话 {origin} · 同工作区 {workspace} · 你选的 {chosen})',
@@ -153,6 +155,8 @@ window.__ModuleLoader__.load({
153
155
  'notice.autoCollectOff': '已关闭自动获取',
154
156
  'notice.externalChange': '清单在别处被改过,已载入最新内容',
155
157
  'reminder.title': '到点提醒({n})',
158
+ 'reminder.note': '这些是到了设定的提醒时间,和「逾期」不是同一批。',
159
+ 'reminder.dismissAll': '全部知道了',
156
160
  'reminder.dismiss': '知道了',
157
161
  'reminder.enableNotifications': '开启系统通知',
158
162
  'reminder.notifyTitle': '待办到点',
@@ -244,6 +248,8 @@ window.__ModuleLoader__.load({
244
248
  'empty.none': '还没有待办。上面写一句,回车就记下了。',
245
249
  'empty.add': '记第一条',
246
250
  'empty.filtered': '这个筛选下没有待办。',
251
+ 'empty.clearFilter': '清除筛选',
252
+ 'empty.filterHint': '换个筛选,或者清空搜索关键词。',
247
253
  'meta.added': '加入于',
248
254
  'meta.due': '截止',
249
255
  'meta.overdue': '逾期 {n} 天',
@@ -347,6 +353,7 @@ window.__ModuleLoader__.load({
347
353
  'settings.cancel': 'Cancel',
348
354
  'settings.saved': 'Settings saved',
349
355
  'settings.about': 'About',
356
+ 'inbox.explain': 'Written by external sources or the agent. Confirm it before it joins the list.',
350
357
  'notice.exported': 'Exported {n} todos',
351
358
  'notice.imported': 'Imported {n} (merged; {total} in the list now)',
352
359
  'import.badJson': 'That file is not valid JSON',
@@ -368,7 +375,7 @@ window.__ModuleLoader__.load({
368
375
  'notice.bulk': 'Updated {n}',
369
376
  'notice.bulkPartial': 'Updated {n}, skipped {m}',
370
377
  'select.clear': 'Clear',
371
- 'select.done': 'Done',
378
+ 'select.done': 'Exit selection',
372
379
  'select.hint': 'Pick a few todos, then Assign: back to the conversation that wrote each one, else a conversation in the same workspace, else you choose.',
373
380
  'assign.working': 'Assigning…',
374
381
  'assign.summary': 'Assigned {n} ({origin} to origin · {workspace} same workspace · {chosen} chosen)',
@@ -394,6 +401,8 @@ window.__ModuleLoader__.load({
394
401
  'notice.autoCollectOff': 'Auto-collect off',
395
402
  'notice.externalChange': 'The list changed somewhere else; the latest content is loaded',
396
403
  'reminder.title': 'Due now ({n})',
404
+ 'reminder.note': 'These reached their reminder time — a different set from Overdue.',
405
+ 'reminder.dismissAll': 'Dismiss all',
397
406
  'reminder.dismiss': 'Dismiss',
398
407
  'reminder.enableNotifications': 'Enable notifications',
399
408
  'reminder.notifyTitle': 'Todo due',
@@ -486,6 +495,8 @@ window.__ModuleLoader__.load({
486
495
  'empty.none': 'No todos yet. Write one above and press Enter.',
487
496
  'empty.add': 'Add your first todo',
488
497
  'empty.filtered': 'Nothing matches this filter.',
498
+ 'empty.clearFilter': 'Clear filter',
499
+ 'empty.filterHint': 'Try another filter, or clear the search box.',
489
500
  'meta.added': 'added',
490
501
  'meta.due': 'due',
491
502
  'meta.overdue': '{n} d overdue',
@@ -908,7 +919,7 @@ window.__ModuleLoader__.load({
908
919
  // ------------------------------------------------------------------ 样式
909
920
 
910
921
  const CSS = `
911
- .gt-root { display: flex; flex-direction: column; height: 100%; min-height: 0; min-width: 0;
922
+ .gt-root { position: relative; display: flex; flex-direction: column; height: 100%; min-height: 0; min-width: 0;
912
923
  color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 1.5; }
913
924
  .gt-inner { display: flex; flex-direction: column; gap: 14px; width: 100%; max-width: 1040px; min-width: 0;
914
925
  box-sizing: border-box; margin: 0 auto; padding: 20px 24px 32px; flex: 1; }
@@ -1069,8 +1080,11 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1069
1080
  display: inline-flex; align-items: center; justify-content: center; padding: 0; }
1070
1081
  .gt-pick[data-on="1"] { border-color: var(--dsw-alias-brand-primary); background: var(--dsw-alias-brand-primary); color: var(--dsw-alias-bg-base); }
1071
1082
  .gt-item[data-selected="1"] { border-color: var(--dsw-alias-brand-primary); }
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; }
1083
+ .gt-selectbar { position: sticky; top: 0; z-index: 10; display: flex; align-items: center;
1084
+ gap: 8px; flex-wrap: wrap; padding: 0 12px; min-height: 36px; box-sizing: border-box;
1085
+ border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px;
1086
+ background: var(--dsw-alias-bg-layer-1); font-size: 13px; }
1087
+ .gt-selectbar-count { font-weight: 600; white-space: nowrap; }
1074
1088
  .gt-item-body { flex: 1; display: flex; flex-direction: column; gap: 5px; min-width: 0; cursor: pointer; }
1075
1089
  .gt-item-body:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; border-radius: 6px; }
1076
1090
  .gt-item-title { font-weight: 600; font-size: 14px; word-break: break-word; line-height: 22px;
@@ -1091,6 +1105,13 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1091
1105
  .gt-settings { display: flex; flex-direction: column; gap: 12px; padding: 16px; width: 340px;
1092
1106
  box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px;
1093
1107
  background: var(--dsw-alias-bg-overlay); font-size: 13px; line-height: 20px; }
1108
+ .gt-group-select { display: none; min-height: 28px; max-width: 150px; padding: 0 8px; font: inherit;
1109
+ font-size: 12.5px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1);
1110
+ border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; }
1111
+ .gt-root[data-wide="0"] .gt-group-tabs { display: none; }
1112
+ .gt-root[data-wide="0"] .gt-group-select { display: inline-flex; align-items: center; }
1113
+ .gt-inbox-note { padding: 0 2px 2px; color: var(--dsw-alias-label-secondary); font-size: 13px;
1114
+ line-height: 20px; }
1094
1115
  .gt-settings-path { font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1095
1116
  .gt-settings .gt-input { flex: 0 0 auto; width: 100%; min-height: 32px; }
1096
1117
  .gt-settings .gt-check-line .gt-input { width: 64px; }
@@ -1187,7 +1208,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
1187
1208
  .gt-empty-title { font-size: 14px; color: var(--dsw-alias-label-primary); }
1188
1209
  .gt-foot { color: var(--dsw-alias-label-secondary); font-size: 11px; display: flex; gap: 10px; flex-wrap: wrap;
1189
1210
  align-items: center; }
1190
- .gt-toast { position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%); z-index: 5;
1211
+ .gt-toast { position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 50;
1191
1212
  display: flex; align-items: center; gap: 12px; background: var(--dsw-alias-bg-overlay);
1192
1213
  border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px; padding: 9px 13px;
1193
1214
  color: var(--dsw-alias-label-primary); }
@@ -3477,7 +3498,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3477
3498
  h('div', { className: 'gt-spacer' }),
3478
3499
  h(
3479
3500
  'span',
3480
- { className: 'gt-tabs', role: 'group', 'aria-label': t('group.by'), title: t('group.by') },
3501
+ { className: 'gt-tabs gt-group-tabs', role: 'group', 'aria-label': t('group.by'), title: t('group.by') },
3481
3502
  GROUP_MODES.map((mode) =>
3482
3503
  h(
3483
3504
  'button',
@@ -3495,6 +3516,20 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3495
3516
  ),
3496
3517
  ),
3497
3518
  ),
3519
+ h(
3520
+ 'select',
3521
+ {
3522
+ className: 'gt-group-select',
3523
+ value: groupMode,
3524
+ 'aria-label': t('group.by'),
3525
+ title: t('group.by'),
3526
+ onChange: (event) => {
3527
+ setGroupMode(event.target.value)
3528
+ storeStoredGroupMode(event.target.value)
3529
+ },
3530
+ },
3531
+ GROUP_MODES.map((mode) => h('option', { key: mode, value: mode }, t(`group.${mode}`))),
3532
+ ),
3498
3533
  ),
3499
3534
  h('div', { className: 'gt-spacer' }),
3500
3535
  h(
@@ -3669,7 +3704,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3669
3704
  ? h(
3670
3705
  'div',
3671
3706
  { className: 'gt-selectbar' },
3672
- h('span', null, t('select.count', { n: Object.keys(selected).length })),
3707
+ h('span', { className: 'gt-selectbar-count' }, t('select.count', { n: Object.keys(selected).length })),
3673
3708
  h(
3674
3709
  'button',
3675
3710
  {
@@ -3691,11 +3726,17 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3691
3726
  ? t('select.allOn')
3692
3727
  : t('select.all'),
3693
3728
  ),
3729
+ h(
3730
+ 'button',
3731
+ { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => setSelected({}) },
3732
+ t('select.clear'),
3733
+ ),
3734
+ h('span', { className: 'gt-spacer' }),
3694
3735
  h(
3695
3736
  'button',
3696
3737
  {
3697
3738
  type: 'button',
3698
- className: 'gt-btn gt-btn-brand',
3739
+ className: 'gt-btn gt-btn-primary',
3699
3740
  disabled: assignBusy || Object.keys(selected).length === 0,
3700
3741
  onClick: () => void runAssign(Object.keys(selected)),
3701
3742
  },
@@ -3769,22 +3810,19 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3769
3810
  },
3770
3811
  t('select.addTag'),
3771
3812
  ),
3772
- h(
3773
- 'button',
3774
- { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => setSelected({}) },
3775
- t('select.clear'),
3776
- ),
3777
3813
  h(
3778
3814
  'button',
3779
3815
  {
3780
3816
  type: 'button',
3781
- className: 'gt-btn gt-btn-quiet',
3817
+ className: 'gt-iconbtn',
3818
+ title: t('select.done'),
3819
+ 'aria-label': t('select.done'),
3782
3820
  onClick: () => {
3783
3821
  setSelectMode(false)
3784
3822
  setSelected({})
3785
3823
  },
3786
3824
  },
3787
- t('select.done'),
3825
+ h(Icon, { name: 'close', size: 13 }),
3788
3826
  ),
3789
3827
  )
3790
3828
  : null,
@@ -3795,7 +3833,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3795
3833
  ? null
3796
3834
  : h(
3797
3835
  'div',
3798
- { className: 'gt-reminders', role: 'status' },
3836
+ { className: 'gt-reminders', role: 'status', title: t('reminder.note') },
3799
3837
  h('span', { className: 'gt-reminders-title' }, t('reminder.title', { n: remindersDue.length })),
3800
3838
  ...remindersDue.slice(0, 3).map((item) =>
3801
3839
  h(
@@ -3829,7 +3867,7 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3829
3867
  h(
3830
3868
  'button',
3831
3869
  { type: 'button', className: 'gt-btn gt-btn-quiet', onClick: () => void ackReminders(remindersDue.map((item) => item.id)) },
3832
- t('reminder.dismiss'),
3870
+ t('reminder.dismissAll'),
3833
3871
  ),
3834
3872
  ),
3835
3873
 
@@ -3904,12 +3942,23 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3904
3942
  },
3905
3943
  t('empty.add'),
3906
3944
  )
3907
- : null,
3908
- h('div', { className: 'gt-sub' }, t('hint.keys')),
3945
+ : h(
3946
+ 'button',
3947
+ {
3948
+ type: 'button',
3949
+ className: 'gt-btn gt-btn-quiet',
3950
+ onClick: () => {
3951
+ setQuery('')
3952
+ void reload({ filter: 'active', query: '' })
3953
+ },
3954
+ },
3955
+ t('empty.clearFilter'),
3956
+ ),
3909
3957
  )
3910
3958
  : h(
3911
3959
  'div',
3912
3960
  { className: 'gt-groups', role: 'list' },
3961
+ filter === 'inbox' ? h('div', { className: 'gt-inbox-note', role: 'note' }, t('inbox.explain')) : null,
3913
3962
  groups.map((group) => {
3914
3963
  const isCollapsed = collapsed[group.key] ?? group.collapsed === true
3915
3964
  const exemptGroup = typeof group.dir === 'string' && group.dir !== '' && exemptWorkspaces.includes(group.dir)
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.1'
39
+ export const VERSION = '0.6.3'
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.1",
3
+ "version": "0.6.3",
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",