dsh-global-todo 0.7.1 → 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 +16 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/docs/ROADMAP.md +92 -56
- package/docs/screenshots/panel-detail.png +0 -0
- package/docs/screenshots/panel-inbox.png +0 -0
- package/docs/screenshots/panel-reminders.png +0 -0
- package/docs/screenshots/panel-review.png +0 -0
- package/docs/screenshots/panel-select.png +0 -0
- package/docs/screenshots/panel-settings.png +0 -0
- package/docs/screenshots/panel.png +0 -0
- package/lib/client.js +49 -2
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
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
|
+
|
|
1
17
|
## [0.7.1] — 2026-10-20
|
|
2
18
|
|
|
3
19
|
Documentation for the one piece of layout that cannot be "cleaned up".
|
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
|
|
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
|
|
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** —
|
|
114
|
-
panel's right edge instead of pushing the list down (CSS-only over
|
|
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
|
-
- **
|
|
122
|
-
|
|
123
|
-
`lib/
|
|
124
|
-
|
|
125
|
-
- **
|
|
126
|
-
(`lib/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
- **
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
deployment already has.
|
|
179
|
+
### 3. Markdown / TaskPaper import and export
|
|
162
180
|
|
|
163
|
-
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
multi-select that did not land with batch assignment.
|
|
189
|
+
### 4. The weekly agent report
|
|
173
190
|
|
|
174
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
tool and diffed in a repo.
|
|
199
|
+
### 5. Image attachments
|
|
180
200
|
|
|
181
|
-
|
|
201
|
+
`kind: 'image'` beside `url` / `file`, so a screenshot can be attached instead of a path
|
|
202
|
+
to one.
|
|
182
203
|
|
|
183
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -50,7 +50,7 @@ window.__ModuleLoader__.load({
|
|
|
50
50
|
* different mechanisms (restart vs. hot reload), so "which one am I looking
|
|
51
51
|
* at" needs an answer that the bundle itself carries.
|
|
52
52
|
*/
|
|
53
|
-
const UI_REV = '
|
|
53
|
+
const UI_REV = '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.
|
|
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.
|
|
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",
|