dsh-global-todo 0.7.0 → 0.7.2

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,30 @@
1
+ ## [0.7.2] — 2026-10-20
2
+
3
+ The roadmap audited against the tree, and the review feature brought up to the same standard
4
+ as the rest of the panel.
5
+
6
+ - **`docs/ROADMAP.md` no longer lies.** Six entries claimed things that already exist (due
7
+ reminders, batch completion/tagging, archive, cross-machine notices, external ingest) — they
8
+ moved to *Landed* with the function that proves each one, and *Next* is now ordered by value
9
+ against effort with a "today" line per item.
10
+ - **`/review` has route tests**: a rejected claim reopens and drops its completion record,
11
+ answering twice is a 400 rather than a silent success, an unknown id is a 404.
12
+ - **The review state is photographed** — a fifth scenario (`panel-review.png`) in the same
13
+ harness as the others, asserted by `check:render` and indexed in both READMEs.
14
+ - **Bulk confirm in the review view**: selecting several finished-by-agent rows and confirming
15
+ them in one go, since assigning is not what that view is for.
16
+
17
+ ## [0.7.1] — 2026-10-20
18
+
19
+ Documentation for the one piece of layout that cannot be "cleaned up".
20
+
21
+ - **`docs/DESIGN.md` now records why the wide-screen detail card is `position: fixed`**, with the
22
+ measurements from the failed attempts (an absolute card came out 348×3051 at 1440; container
23
+ queries make the panel the containing block for fixed descendants), the real cause (the panel
24
+ scrolls with the host page, not `.gt-groups`), and the rules for changing it safely.
25
+ - `/api/global-todo/review` added to both README route tables (it shipped in 0.7.0 without its
26
+ row).
27
+
1
28
  ## [0.7.0] — 2026-10-20
2
29
 
3
30
  An agent's "done" becomes a claim a person confirms, instead of a fact.
package/README.md CHANGED
@@ -43,6 +43,7 @@ preview and photographed by the same tool:
43
43
  | Empty list | [`panel-empty.png`](docs/screenshots/panel-empty.png) |
44
44
  | Filter with no match | [`panel-filtered.png`](docs/screenshots/panel-filtered.png) |
45
45
  | Inbox (awaiting confirmation) | [`panel-inbox.png`](docs/screenshots/panel-inbox.png) |
46
+ | Awaiting review (agent finished) | [`panel-review.png`](docs/screenshots/panel-review.png) |
46
47
  | Due reminder | [`panel-reminders.png`](docs/screenshots/panel-reminders.png) |
47
48
  | Selection mode | [`panel-select.png`](docs/screenshots/panel-select.png) |
48
49
 
@@ -268,6 +269,7 @@ fence when that service is present, and by a strict loopback check otherwise.
268
269
  | POST | `/api/global-todo/status` | `{ id, status, by?: human\|agent }` |
269
270
  | POST | `/api/global-todo/remove` | `{ id }` |
270
271
  | POST | `/api/global-todo/restore` | `{ todo }` — put a removed record back verbatim (undo) |
272
+ | POST | `/api/global-todo/review` | `{ id, decision: confirm\|reject }` — answer an agent's completion |
271
273
  | POST | `/api/global-todo/link` | `{ id, add?: [...], remove?: [value \| index] }` |
272
274
  | POST | `/api/global-todo/dispatch` | `{ id, mode?: auto\|bound\|new\|current, sessionId?, dir? }` |
273
275
  | POST | `/api/global-todo/assign` | `{ ids, items?, target?, dir? }` — hand several todos over in one call; an id that matches nothing comes back instead of being sent elsewhere |
package/README.zh.md CHANGED
@@ -39,6 +39,7 @@ DSH 自带的 `todo_write` 属于**某一个 agent 会话**:整表替换、三
39
39
  | 清单为空 | [`panel-empty.png`](docs/screenshots/panel-empty.png) |
40
40
  | 筛选无结果 | [`panel-filtered.png`](docs/screenshots/panel-filtered.png) |
41
41
  | 收件箱(待确认) | [`panel-inbox.png`](docs/screenshots/panel-inbox.png) |
42
+ | 待确认(Agent 完成) | [`panel-review.png`](docs/screenshots/panel-review.png) |
42
43
  | 到点提醒 | [`panel-reminders.png`](docs/screenshots/panel-reminders.png) |
43
44
  | 批量选择 | [`panel-select.png`](docs/screenshots/panel-select.png) |
44
45
 
@@ -164,6 +165,7 @@ dsh plugin --profile web remove dsh-global-todo
164
165
  | POST | `/api/global-todo/status` | `{ id, status, by?: human\|agent }` |
165
166
  | POST | `/api/global-todo/remove` | `{ id }` |
166
167
  | POST | `/api/global-todo/restore` | `{ todo }` —— 原样还原被删除的记录(撤销) |
168
+ | POST | `/api/global-todo/review` | `{ id, decision: confirm\|reject }` —— 回答 Agent 的完成声明 |
167
169
  | POST | `/api/global-todo/link` | `{ id, add?: [...], remove?: [值 或 下标] }` |
168
170
  | POST | `/api/global-todo/dispatch` | `{ id, mode?: auto\|bound\|new\|current, sessionId?, dir? }` |
169
171
  | POST | `/api/global-todo/assign` | `{ ids, items?, target?, dir? }`——一次把多条交给 Agent;配不上的 id 原样报回,不擅自送到别处 |
package/docs/DESIGN.md CHANGED
@@ -60,15 +60,51 @@ $DSH_HOME/global-todo/todos.json ← { version: 1, todos: [ … ] }
60
60
  ## On a wide window, the detail stands beside the list
61
61
 
62
62
  Expanding a row inline is right on a narrow window and wrong on a wide one: the list jumps, the
63
- row you were reading slides away, and the rest of the screen sits empty. Above 1100px the open
64
- row's detail is lifted out of the flow into a card bracketed between the toolbar and the footer
65
- at the panel's right edge; only the list gives way (the toolbar keeps full width), and the list gets
66
- padding equal to that card. The card's offsets are measured from the panel's own column on mount
67
- and on resize — the slot does not always fill the window, so a viewport-derived offset put the card
68
- over the list inside the real GUI. This is a CSS-only override over unchanged markup
69
- (`.gt-root[data-wide-detail]` plus one media query) precisely so there is still exactly one
70
- detail implementation — a second render path for wide screens would drift from the inline one at
71
- the first field change.
63
+ row you were reading slides away, and the rest of the screen sits empty. When the panel itself is
64
+ at least 960px wide, the open row's detail is lifted out of the flow into a card at the panel's
65
+ right edge; only the list gives way (the header and toolbar keep full width), and the list gets
66
+ 360px of right padding equal to the card. The breakpoint follows the **panel's** measured width,
67
+ not the viewport: the slot is ~265px narrower than the window, so a viewport query opened the
68
+ two-column layout while the panel still had no room for it. This is a CSS-only override over
69
+ unchanged markup (`.gt-root[data-wide]` / `[data-wide-detail]`) precisely so there is still
70
+ exactly one detail implementation — a second render path for wide screens would drift from the
71
+ inline one at the first field change.
72
+
73
+ ### Why the card is `position: fixed` and not `sticky`
74
+
75
+ Three ways to do this were tried; only one survives the host's layout.
76
+
77
+ 1. **`position: sticky`** — the card is a child of the row, so sticky is bounded by the row's
78
+ box: it never leaves the 72–94px tall item. Moving the card out of the row would mean a second
79
+ detail render path.
80
+ 2. **`position: absolute` inside a positioned wrapper around the list** — measured on
81
+ 2026-10-20: at 1440 the card came out **348 × 3051px**, at 1280 **348 × 3203px**. The card
82
+ stretched to the full height of the list's *content* and scrolled away with the page. Reason:
83
+ **the scrolling ancestor of this panel is the host page, not `.gt-groups`** — the panel is a
84
+ column in someone else's scroller, so `.gt-groups` never scrolls and an absolutely positioned
85
+ box is bracketed by the whole content column instead of the visible area.
86
+ 3. **`container-type: inline-size` + container queries** — wanted, because the breakpoint would
87
+ then be CSS. But inline-size containment applies `contain: layout`, which makes the panel the
88
+ containing block for `position: fixed` descendants. The card (and the settings popover) would
89
+ silently switch from viewport-anchored to panel-anchored and land in the wrong place.
90
+
91
+ So: **`position: fixed` plus one measurement of the panel and list boxes**, taken when a row
92
+ opens and again on resize (and on a `ResizeObserver` of the column, because collapsing the host
93
+ sidebar changes the panel's width without a window resize). The measured values land in
94
+ `--gt-detail-top/right/bottom`; the CSS fallbacks assume the centred 1040px column, which is what
95
+ the static previews render.
96
+
97
+ ### Changing this safely
98
+
99
+ - Keep **one** detail implementation: if a change needs different markup for wide screens, it is
100
+ the wrong change.
101
+ - Keep the breakpoint on the **panel** width (960px). A viewport media query was already shipped
102
+ once and put a 360px card over a 416px list at 1100.
103
+ - Never let the card cover the list: it is `.gt-groups` padding that reserves the room, and the
104
+ two numbers (360 padding / 348 card + 12 gap) have to move together.
105
+ - Verify in the real GUI, not the preview, at **1440 / 1280 / 1100**: the card must be ≈348px wide,
106
+ the list must end before it, and the open row must stay visible next to it. The preview cannot
107
+ catch this class of bug — it is server-rendered, so no measurement effect ever runs there.
72
108
  ## A shared file changes without asking
73
109
 
74
110
  The list is deliberately portable — a notes repo, a synced folder, a NAS — so another machine
package/docs/ROADMAP.md CHANGED
@@ -110,95 +110,131 @@ in `skipped[]`, and archived/cancelled rows deliberately do not block a new one.
110
110
  counts a disagreement once (`externalChanges`, `lastExternalAt` in `lib/store.js`; surfaced by
111
111
  `/health.store`). The panel shows a one-line notice instead of letting a synced folder change the
112
112
  list silently.
113
- **Side detail on wide windows** — above 1100px the open row's detail becomes a card at the
114
- panel's right edge instead of pushing the list down (CSS-only over the same markup; one detail
115
- implementation), while narrow windows keep the inline expansion.
113
+ **Side detail on wide windows** — when the panel itself is at least 960px wide, the open row's
114
+ detail becomes a card at the panel's right edge instead of pushing the list down (CSS-only over
115
+ the same markup; one detail implementation), while narrow windows keep the inline expansion. The
116
+ breakpoint follows the panel's measured width, and the card is `position: fixed` with a measured
117
+ anchor — `DESIGN.md` records the two alternatives that were measured and rejected, so this is not
118
+ re-litigated every time someone tries to "clean it up".
116
119
  **External ingest with an inbox** — `POST /ingest` takes source-tagged, confidence-scored items
117
120
  behind a Bearer token; low-confidence records wait in an `inbox` tag view with 确认 / 丢弃 and a
118
121
  seven-day sweep, so an external source cannot silently fill the list.
122
+ **An agent's "done" is a claim** — finishing as an agent sets `review = { pending }`; a person
123
+ confirms or sends it back through `POST /review` (`reviewTodo()` in `lib/model.js`, `review()` in
124
+ `lib/service.js`). Confirming keeps the record finished; rejecting reopens it and drops the
125
+ completion record, because "not actually done" is the only honest reading. The panel shows a
126
+ 待确认 chip, a 待确认 filter and 确认完成 / 退回清单; bulk confirm exists in that view.
127
+ `counts.review` drives the chip. Tests: `test/model.test.mjs`, `test/routes.test.mjs`.
128
+ **A design system with guards** — five type sizes (20/14/13/12/11), four control heights, one
129
+ settings inspector, and no JavaScript layout measurement left in the panel. Three guards keep it
130
+ that way: `test/locale.test.mjs` (duplicate keys, zh/en parity, full-width punctuation in English,
131
+ missing `t()` keys), the scenario table in `tools/preview.mjs` (empty, filtered, inbox, review,
132
+ reminder) photographed into `docs/screenshots/` by `npm run screenshots`, and
133
+ `npm run check:render` asserting each state's marker.
119
134
  ### Partly landed
120
135
 
121
- - **Batch and multi-select** — the selection mode, its per-row markers and the batch
122
- 一键分配 are in (`lib/client.js`, `POST /assign` at
123
- `lib/routes.js`, `assignMany()` at `lib/dispatch.js`; tests
124
- `test/dispatch.test.mjs`). Batch 完成 / 打标签 across a selection do not exist.
125
- - **Session read-back** — `sessionAlive` is on every listed todo
126
- (`lib/routes.js`, `lib/dispatch.js`). Reading what that session last said,
127
- and linking to it, is not: `lib/completion.js` reads `assistant/message` only to
128
- close a todo this plugin dispatched, and stores one summary in its completion record.
129
- - **What the agents produced** — the per-todo record with a summary and the changed
130
- files landed (`completeTodo()` at `lib/model.js`, 「完成记录」 at
131
- `lib/client.js`, the 已完成 filter and group at `lib/client.js`). The
132
- cross-todo "what did the sessions finish this week, and what did they touch" view did
133
- not.
136
+ - **Session read-back** — `sessionAlive` is on every listed todo (`sessionAlive` in
137
+ `lib/dispatch.js`, surfaced by `GET /list`). Reading what that session last said, and
138
+ linking to it, is not: `lib/completion.js` reads `assistant/message` only to close a todo
139
+ this plugin dispatched, and stores one summary in its completion record.
140
+ - **What the agents produced** — the per-todo record with a summary and the changed files landed
141
+ (`completeTodo()` in `lib/model.js`, 「完成记录」 in `lib/client.js`, the 已完成 filter and
142
+ group). The cross-todo "what did the sessions finish this week, and what did they touch" view
143
+ did not.
144
+ - **Attachment health** — a dead file link is flagged on read, but there is no 「重新挂载」 next
145
+ to it (`relink` does not exist anywhere in `lib/`), and `annotateLinks()` has no unit test:
146
+ today the render assertion in `tools/preview.mjs` is the only coverage.
147
+ - **Batch** — selection, 一键分配, 完成 / 归档 / 打标签 and 批量确认(待确认视图)are all in.
148
+ Nothing here is missing; it is listed because the panel is where the batch surface lives.
134
149
 
135
150
  ## Next
136
151
 
137
- ### 1. Checklist inside a todo
152
+ Ordered by value against effort, and audited against the tree on 2026-10-20 — "today"
153
+ means what the code actually does, not what the last plan hoped.
138
154
 
139
- Large items ("ship the plugin") are really a list of steps. Proposed: an optional
140
- `steps[]` on a todo, rendered as checkboxes, and shipped to the agent as a numbered
141
- plan in the dispatch prompt.
155
+ ### 1. Checklist inside a todo — the highest-value gap
142
156
 
143
- - Effort: medium (model + tools + panel + prompt builder).
144
- - Today: nothing named `steps` exists anywhere in `lib/`, so this is untouched.
145
- - Why it matters: it removes the need for a separate "plan" plugin, and the dispatch
146
- prompt gets materially better.
157
+ Large items ("ship the plugin") are a list of steps wearing a title. Proposed: an optional
158
+ `steps[]` (text + done) per todo, rendered as checkboxes in the row and the detail, and
159
+ shipped to the agent as a numbered plan in the dispatch prompt.
147
160
 
148
- ### 2. Session read-back
161
+ - Today: nothing named `steps` exists in `lib/`; `grep -rn steps lib` is empty.
162
+ - Effort: medium — model (create / `applyPatch` / `normalizeStored`), a tool field, the
163
+ panel, and the prompt builder that already assembles the dispatch text.
164
+ - Why first: it improves the *quality of every hand-off*, not just one screen. A dispatch
165
+ that arrives as "step 2 of 5" is worth more than one that arrives as a paragraph.
149
166
 
150
- The panel shows whether the bound session is alive (`sessionAlive`). Next: show what
151
- that session last said — one line of its latest assistant message, and a link to open it
152
- — so a dispatched todo reports back where the work is.
167
+ ### 2. Session read-back — makes hand-off stop being fire-and-forget
153
168
 
154
- - Effort: medium. Needs a host-side read of the bound session (the `sessions` service
155
- exists, but this must stay cheap and failure-tolerant).
156
- - Why it matters: hand-off stops being fire-and-forget.
169
+ `sessionAlive` says a bound conversation still exists. Next: one line of its latest
170
+ assistant message plus a link to open it.
157
171
 
158
- ### 3. Due reminders
172
+ - Today: `lib/completion.js` reads `assistant/message` only to close a todo this plugin
173
+ dispatched; nothing exposes it to the panel.
174
+ - Effort: medium, and it must stay cheap and failure-tolerant: a missing or huge message is
175
+ a normal case, not an error.
176
+ - Why second: the panel currently cannot answer "did the thing I handed over get anywhere",
177
+ which is the question people actually ask.
159
178
 
160
- A reminder when a due time passes, in the GUI and optionally to the IM channel this
161
- deployment already has.
179
+ ### 3. Markdown / TaskPaper import and export
162
180
 
163
- - Effort: medium. The plugin has no scheduler today (nothing in `lib/` matches
164
- `remind`); the Host's timer service plus a config flag is the smallest version.
165
- - Careful: **remind only, never auto-dispatch.** The list belongs to the human; starting
166
- work stays an explicit act. (Auto-dispatch was rejected in `DESIGN.md` for this
167
- reason.)
181
+ JSON round-trips byte for byte; the text formats are what let the list live in a repo and
182
+ diff.
168
183
 
169
- ### 4. Batch completion and tagging
184
+ - Today: only `GET /export` / `POST /import` over JSON.
185
+ - Effort: medium — a serializer plus a tolerant parser, both with round-trip tests.
186
+ - Why third: cheap portability, and it is the only way to compare this list with another
187
+ tool's.
170
188
 
171
- Shift-click to select, then 完成 / 打标签 across the selection — the half of
172
- multi-select that did not land with batch assignment.
189
+ ### 4. The weekly agent report
173
190
 
174
- - Effort: small (one bulk route beside `/assign`, one button beside 一键分配).
191
+ Per-todo completion records already carry a summary and the changed files. A cross-todo
192
+ view — "what did the sessions finish this week, and what did they touch" — turns that into
193
+ something reviewable.
175
194
 
176
- ### 5. Import and export beyond JSON
195
+ - Today: the data exists (`completion.artifacts`), the aggregate does not.
196
+ - Effort: small-medium; the honest version is a date-bounded list filtered on
197
+ `completion.by === 'agent'`, with `review` state beside each row.
177
198
 
178
- Markdown / TaskPaper in and out, so the list can be migrated from (and to) any other
179
- tool and diffed in a repo.
199
+ ### 5. Image attachments
180
200
 
181
- - Effort: medium. JSON is done; the text formats are the remaining work.
201
+ `kind: 'image'` beside `url` / `file`, so a screenshot can be attached instead of a path
202
+ to one.
182
203
 
183
- ### 6. What the agents produced
204
+ - Today: `LINK_KINDS = ['url', 'file']` (`lib/links.js`); the link shape is already prepared.
205
+ - Effort: medium — the store must not inline bytes, so this is a path with a preview and a
206
+ clear "the file is gone" state, which #3-adjacent work already models.
184
207
 
185
- Completions already record a summary and the changed files per todo. A cross-todo view —
186
- "what did the sessions finish this week, and what did they touch" — would turn that into
187
- something you can review, and would be the honest way to show that a hand-off did work.
208
+ ### 6. Source adapters for the ingest door
209
+
210
+ `POST /ingest` exists and is fenced by a token; nothing pushes to it.
211
+
212
+ - Today: the door, the inbox, the confidence threshold and the seven-day sweep are done.
213
+ There is no mail/notification reader anywhere in the repo.
214
+ - Effort: large, and the mode of the work is different: every adapter is an external
215
+ dependency with its own failure modes. Do it when a real source exists, not before.
216
+
217
+ ### 7. Small, well-understood items
218
+
219
+ - `before:` / `after:` date bounds in the search syntax (the parser already walks tokens).
220
+ - 「重新挂载」 next to a dead attachment, plus the `annotateLinks()` unit test it owes.
221
+ - A screen-reader pass over the panel and the group headers; the keyboard path is close.
222
+
223
+ ### Landed after this list was written
224
+
225
+ Due reminders (0.5.2), batch completion and tagging (`/bulk`), archive instead of
226
+ delete, cross-machine change notices, external ingest with an inbox — all four used to be
227
+ items here. See `Landed since 0.1` above; the entries were removed rather than left to
228
+ mislead.
188
229
 
189
230
  ## Later
190
231
 
191
232
  | Idea | Note |
192
233
  |---|---|
193
- | Image attachments | `kind: 'image'` next to `url` / `file`. The shape is already prepared for it, but `LINK_KINDS` is still `['url', 'file']` (`lib/links.js`). |
194
234
  | Official `storageDomain` backend | A second store backend behind the same interface; the file stays the default because it is the feature. Today there is one backend and it says so (`kind: 'json'`, `lib/store.js`). |
195
235
  | Virtualised list | Only needed past a few hundred rows; measure first. Nothing in `lib/client.js` virtualises today. |
196
236
  | Drag to reorder | Manual order is meaningful to some people; it also needs a stable `position` field and a decision about how it interacts with due-date grouping. |
197
237
  | Multi-list / projects | A `list` field with one default list; deferred because "global single list" is the current identity. |
198
- | Archive instead of delete | Keeps history without cluttering. `POST /remove` deletes for real (`lib/routes.js`); only the panel's short undo brings a record back. |
199
- | Cross-machine conflict notice | The file is portable (NAS, sync folder). Detect "changed underneath me" and offer a reload instead of a silent overwrite — `store.read` still trusts the file whenever it parses. |
200
- | Capture from outside the chat | Notifications, mail, announcements — the sources `dsh-todo-list` reads. Needs an ingest path and a policy for deduplication; `POST /capture` only asks a conversation to collect (`lib/routes.js`). |
201
- | Accessible-only sweep | Full keyboard path is close; a screen-reader pass over the panel and the group headers is still owed. |
202
238
 
203
239
  ## Explicitly not planned
204
240
 
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 = 'ui45'
53
+ const UI_REV = 'ui46'
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
 
@@ -3320,6 +3320,39 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
3320
3320
  }
3321
3321
  }
3322
3322
 
3323
+ // Reviewing is a per-record decision, so it cannot ride on /bulk: answering is not a
3324
+ // status change, and a rejected claim has to clean up its completion record.
3325
+ const runBulkReview = async () => {
3326
+ const ids = Object.keys(selected)
3327
+ if (ids.length === 0) return
3328
+ try {
3329
+ const results = await Promise.all(
3330
+ ids.map((id) =>
3331
+ call('review', { method: 'POST', body: { id, decision: 'confirm' } }).then(
3332
+ () => true,
3333
+ () => false,
3334
+ ),
3335
+ ),
3336
+ )
3337
+ const done = results.filter(Boolean).length
3338
+ setNotice({
3339
+ text:
3340
+ done === ids.length
3341
+ ? t('notice.bulk', { n: done })
3342
+ : t('notice.bulkPartial', { n: done, m: ids.length - done }),
3343
+ sessionId: '',
3344
+ })
3345
+ setTimeout(() => setNotice(null), 6000)
3346
+ setSelected({})
3347
+ setError(null)
3348
+ void reload()
3349
+ void refreshHealth()
3350
+ } catch (failure) {
3351
+ reportFailure('bulk-review', { message: failure?.message })
3352
+ setError(failure)
3353
+ }
3354
+ }
3355
+
3323
3356
  const counts = state.counts ?? { total: 0, open: 0, doing: 0, done: 0, cancelled: 0, archived: 0, overdue: 0, inbox: 0 }
3324
3357
  const remindersDue = Array.isArray(state.health?.reminders?.due) ? state.health.reminders.due : []
3325
3358
  const dueKey = remindersDue.map((item) => `${item.id}:${item.dueAt}`).join('|')
@@ -4062,11 +4095,25 @@ button.gt-chip[data-on="1"] { color: var(--dsw-alias-label-primary); border-colo
4062
4095
  t('select.clearAll'),
4063
4096
  ),
4064
4097
  h('span', { className: 'gt-spacer' }),
4098
+ // In the review view the batch action is confirming, not assigning: every
4099
+ // record on screen is finished and waiting for a word.
4100
+ filter === 'review'
4101
+ ? h(
4102
+ 'button',
4103
+ {
4104
+ type: 'button',
4105
+ className: 'gt-btn gt-btn-primary',
4106
+ disabled: Object.keys(selected).length === 0,
4107
+ onClick: () => void runBulkReview(),
4108
+ },
4109
+ t('review.confirm'),
4110
+ )
4111
+ : null,
4065
4112
  h(
4066
4113
  'button',
4067
4114
  {
4068
4115
  type: 'button',
4069
- className: 'gt-btn gt-btn-primary',
4116
+ className: filter === 'review' ? 'gt-btn gt-btn-quiet' : 'gt-btn gt-btn-primary',
4070
4117
  disabled: assignBusy || Object.keys(selected).length === 0,
4071
4118
  onClick: () => void runAssign(Object.keys(selected)),
4072
4119
  },
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.7.0'
39
+ export const VERSION = '0.7.2'
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.7.0",
3
+ "version": "0.7.2",
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",