@dennisrongo/dsh-todo 0.2.0 → 0.4.0

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/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # @dennisrongo/dsh-todo
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/@dennisrongo/dsh-todo)](https://www.npmjs.com/package/@dennisrongo/dsh-todo)
4
+
5
+ **npm:** [`@dennisrongo/dsh-todo`](https://www.npmjs.com/package/@dennisrongo/dsh-todo) ·
6
+ **source:** [dennisrongo/dsh-plugins](https://github.com/dennisrongo/dsh-plugins/tree/main/plugins/dsh-todo)
7
+
3
8
  A todo list for the [DeepSeek Harness](https://github.com/deepseek-ai) (dsh) web UI.
4
9
 
5
10
  Registers into the additive `conversation.view` slot — the conversation view
@@ -7,22 +12,51 @@ ring — so it appears as its **own tab beside Chat and Trajectory** (`order: 20
7
12
  after chat at `0` and trajectory at `10`) and fills the session pane when
8
13
  selected.
9
14
 
15
+ A **sprint/roadmap task list**, not just a checklist: each task carries a status,
16
+ a priority, and optional release and sprint labels, so a real backlog can live
17
+ next to the code it describes.
18
+
10
19
  ## Features
11
20
 
12
- - **Persisted on disk by the host** — the list lives in a dsh storage domain at
13
- `~/.dsh/storages/dsh_todo.json`, not in the browser. It survives a restart, a
14
- cleared browser cache, and a different browser entirely.
21
+ - **Persisted on disk by the host** — one SQLite database per project at
22
+ `<workspace>/.dsh/todo.db`, not in the browser. It survives a restart, a
23
+ cleared browser cache, and a different browser entirely, and it travels with
24
+ the project.
15
25
  - **Per-workspace** — each workspace has its own list, keyed by workspace id.
16
26
  - **Safe against races** — every write carries the revision it observed; a
17
27
  losing write is refused and the view adopts the authoritative list, so two
18
28
  open tabs can never silently clobber each other.
19
- - **Own tab** — full-pane view with a progress header and `done/total` score.
20
- - **Filter ring** All · Open · Done · Archive, with live counts.
29
+ - **Real workflow states** — `backlog · todo · in-progress · blocked · done`,
30
+ changed from a pill on the row. A boolean cannot express the two things a
31
+ standup actually asks about: what is moving, and what is stuck.
32
+ - **Priority** — `P0`–`P3`, shown as a chip. Only P0/P1 are coloured, so the
33
+ list flags what is urgent instead of turning into a rainbow.
34
+ - **Release and sprint** — two independent labels: what ships together
35
+ (`v1.2.0`) and when it is worked (`Sprint 24`). A task can be in both.
36
+ - **Group by** — None · Status · Release · Sprint · Priority, with collapsible
37
+ section headers carrying their own `done/total` and progress bar. Grouping by
38
+ status gives you a kanban board without drag-and-drop.
39
+ - **Task detail modal** — click a task's title to open the full dialog: a
40
+ roomy description box plus status, priority, release, sprint and due date.
41
+ Esc or the backdrop closes it, focus is trapped inside and returned to the row
42
+ you came from, and edits commit on close so a stray click never loses them.
43
+ - **Expandable rows** — the chevron still gives a quick in-row peek without
44
+ leaving the list. Double-click a title to rename it inline. One scannable line
45
+ collapsed is what lets a task carry nine fields without becoming a wall of text.
46
+ - **Due dates** — stored as calendar days, so "due the 14th" reads as the 14th
47
+ in every timezone. Overdue tasks are flagged red on the row, due-today amber,
48
+ and a finished task is never overdue.
49
+ - **Own tab** — full-pane view with a progress header and `done/total` score,
50
+ plus live in-progress and blocked counts.
51
+ - **Filter ring** — All · Open · In Progress · Blocked · Backlog · Done ·
52
+ Archive, with live counts; empty states are hidden rather than shown at zero.
21
53
  - **Archive, not delete** — check items off, then "Archive completed" files them
22
54
  away. Archived items leave every active view but stay in the record, and can be
23
55
  restored (↩) or permanently deleted from the Archive view.
24
- - **Full editing** — add, check off, click-to-edit, reorder (▲/▼), archive, and
25
- delete.
56
+ - **Full editing** — add, check off, click-to-edit the title, edit the
57
+ description and labels, reorder (▲/▼), archive, and delete. Release and sprint
58
+ inputs suggest labels already in use, so free text converges on a shared
59
+ vocabulary without a releases table to administer.
26
60
  - **Themed** — colors come only from the shell's `--dsw-*` tokens, so it follows
27
61
  light/dark automatically. Respects `prefers-reduced-motion`.
28
62
 
@@ -32,7 +66,7 @@ This is a **dual-face plugin**. Both halves ship from one package.
32
66
 
33
67
  | Half | File | Role |
34
68
  | --- | --- | --- |
35
- | Host | `src/index.ts` | `TodoService`, a `TypertRemoteService` that owns the storage domain and exports `list` / `replace` as `@Remote` methods. |
69
+ | Host | `src/index.ts` | `TodoService`, a `TypertRemoteService` that owns the per-workspace SQLite database and exports `list` / `replace` as `@Remote` methods. |
36
70
  | Client | `src/client.tsx` | The React tab. Mounts the host contract and calls it as `ctx.remote.dshTodo.*`. |
37
71
  | Bridge | `src/remote.ts` | The Typert Remote descriptor the client mounts. |
38
72
  | Shared | `src/types.ts` | Dependency-free vocabulary used by both halves. |
@@ -68,14 +102,16 @@ pnpm install
68
102
  pnpm run build # node build/build.mjs — emits lib/index.js + lib/client.js
69
103
  pnpm run typecheck # tsc --noEmit
70
104
  pnpm test # node test/smoke.mjs — offline, exercises the BUILT lib/ output
105
+ pnpm run test:icons # headless Chrome: icon sizes and the 40px row budget
106
+ pnpm run test:modal # headless Chrome: the dialog escapes the list's scroll container
71
107
  ```
72
108
 
73
109
  `pnpm test` asserts against `lib/`, so **build before testing**.
74
110
 
75
111
  ## Install into a dsh profile
76
112
 
77
- The profile must already compose the storage rows (`storage`, `storage-json`,
78
- `storage-domain`); `@deepseek-ai/dsh-web-app` does this by default.
113
+ The profile must already compose the workspace registry, which `@deepseek-ai/dsh-web-app`
114
+ does by default.
79
115
 
80
116
  ```bash
81
117
  cd ~/.dsh/profiles/<profile>
@@ -108,7 +144,7 @@ one source of truth and no way to store an archived item without a date.
108
144
  | Archive completed | footer, any active view | Stamps every done item. Recoverable. |
109
145
  | Archive (⌸) | row hover, completed items | Stamps one item. Recoverable. |
110
146
  | Restore (↩) | row hover, Archive view | Clears the stamp, returning it to the list. |
111
- | Delete (✕) | row hover | Removes one item outright. |
147
+ | Delete (✕) | row hover | Removes one item outright, after a confirmation dialog naming the task. |
112
148
  | Delete archived | footer, Archive view | Permanently drops every archived item — the only destructive bulk action, and the only one that asks for confirmation. |
113
149
 
114
150
  Archived items are excluded from the progress bar and the done/total score, so
@@ -120,19 +156,219 @@ swallow a move.
120
156
  `clearCompleted` (hard delete of done items) is still exported for callers that
121
157
  want it, but it is no longer wired to a button.
122
158
 
159
+ ## CLI — for you and for AI agents
160
+
161
+ The same list is reachable from a terminal, so an agent can shell out and manage your tasks
162
+ (and so can you). It targets a workspace **directory** and needs no profile, no session and
163
+ no running server — it works offline and in CI.
164
+
165
+ ### Getting the command
166
+
167
+ The plugin ships a `dsh-todo` binary. If the package is already installed into a dsh
168
+ profile, that profile's `node_modules/.bin` has it; otherwise reach it directly:
169
+
170
+ ```bash
171
+ npx @dennisrongo/dsh-todo list # no install
172
+ pnpm add -g @dennisrongo/dsh-todo # then just: dsh-todo list
173
+ node /path/to/plugins/dsh-todo/lib/bin.js list # from a checkout
174
+ ```
175
+
176
+ Requires **Node 22+** — storage is `node:sqlite`, which is built in (and still prints an
177
+ experimental-feature warning on stderr; it is harmless, and `2>/dev/null` silences it).
178
+
179
+ ### Everyday use
180
+
181
+ ```bash
182
+ cd ~/projects/my-app # the workspace IS the current directory
183
+
184
+ dsh-todo add "Fix token refresh" --priority p0 --release v1.2.0 --due 2026-03-14
185
+ dsh-todo add "Write migration guide" --sprint "Sprint 24"
186
+ dsh-todo list
187
+ ```
188
+
189
+ ```text
190
+ [ ] tmtcfbutukp4j todo p0 Fix token refresh (release=v1.2.0 due=2026-03-14)
191
+ [ ] tmtcfbuxr5y9x todo p2 Write migration guide (sprint=Sprint 24)
192
+ [ ] tmtcfbv071e6w in-progress p2 Ship it
193
+ ```
194
+
195
+ One task per line: checkbox, id, status, priority, title, then any labels in parentheses.
196
+ That is the whole display contract — it stays greppable and diffable.
197
+
198
+ Move work along, then file it away:
199
+
200
+ ```bash
201
+ dsh-todo update tmtcfbut --status in-progress --sprint "Sprint 24"
202
+ dsh-todo show tmtcfbut # everything about one task
203
+ dsh-todo done tmtcfbut
204
+ dsh-todo archive # archive EVERY completed task
205
+ ```
206
+
207
+ Work on a project you are not `cd`'d into with `--workspace`:
208
+
209
+ ```bash
210
+ dsh-todo --workspace ~/projects/other list --open
211
+ ```
212
+
213
+ ### Commands
214
+
215
+ | Command | Does |
216
+ | --- | --- |
217
+ | `list` | Show tasks — active only unless `--archived` |
218
+ | `add <title>` | Create a task; every field flag is accepted |
219
+ | `update <id>` | Change one or more fields; needs at least one |
220
+ | `done` / `reopen` | Flip completion, stamping or clearing `completedAt` to match |
221
+ | `rm <id>` | Delete outright — no confirmation, no archive |
222
+ | `archive [<id>]` | Archive one task, or every completed one when no id is given |
223
+ | `show <id>` | Print one task in full, including description and timestamps |
224
+ | `help` | The same reference, in the terminal |
225
+
226
+ ### Options
227
+
228
+ | Option | Applies to | Notes |
229
+ | --- | --- | --- |
230
+ | `--workspace <dir>` | all | Workspace directory. Defaults to cwd, and is used **as given** — no upward search for a `.dsh`, so an agent in a subdirectory targets the project it was pointed at. |
231
+ | `--json` | all | Machine-readable output, **including on errors** |
232
+ | `--status <s>` | add, update, list | `backlog` · `todo` · `in-progress` · `blocked` · `done` |
233
+ | `--priority <p>` | add, update, list | `p0`–`p3` (default `p2`) |
234
+ | `--release <label>` | add, update, list | e.g. `v1.2.0` |
235
+ | `--sprint <label>` | add, update, list | e.g. `"Sprint 24"` |
236
+ | `--due <YYYY-MM-DD>` | add, update | A calendar day; impossible dates are refused |
237
+ | `--description <text>` | add, update | Body text — acceptance criteria, repro steps |
238
+ | `--title <text>` | update | Rename |
239
+ | `--open` | list | Everything unfinished, whatever stage |
240
+ | `--archived` | list | Show archived tasks *instead of* active ones |
241
+
242
+ Filters combine, so `list --open --priority p0 --release v1.2.0` is an AND across all three.
243
+ `--key value` and `--key=value` are both accepted.
244
+
245
+ ### Driving it from a script or an agent
246
+
247
+ **`--json` is the one to use from a script.** It prints structured output on the error path
248
+ too, so a caller never has to parse a human sentence to find out what went wrong:
249
+
250
+ ```bash
251
+ dsh-todo list --open --json
252
+ ```
253
+
254
+ ```json
255
+ {
256
+ "count": 3,
257
+ "items": [
258
+ {
259
+ "id": "tmtcfbutukp4j",
260
+ "title": "Fix token refresh",
261
+ "status": "todo",
262
+ "priority": "p0",
263
+ "release": "v1.2.0",
264
+ "dueDate": "2026-03-14",
265
+ "createdAt": 1787889639378
266
+ }
267
+ ]
268
+ }
269
+ ```
270
+
271
+ `list` returns `{ count, items }`; `add` / `update` / `done` / `reopen` / `rm` return
272
+ `{ item, revision }`; `archive` returns `{ archived, revision }`; `show` returns the task
273
+ itself. A failure returns `{ error, code }` and exits with that code:
274
+
275
+ ```bash
276
+ $ dsh-todo update nope --status done --json; echo "exit=$?"
277
+ {
278
+ "error": "no task matching \"nope\"",
279
+ "code": 3
280
+ }
281
+ exit=3
282
+ ```
283
+
284
+ Exit codes are distinct so a script can branch on **why** a command failed:
285
+
286
+ | Code | Means |
287
+ | --- | --- |
288
+ | `0` | Success |
289
+ | `2` | Usage — unknown command, bad flag, malformed value |
290
+ | `3` | Not found — a well-formed request that matched no task |
291
+
292
+ Piping into `jq` covers most agent work:
293
+
294
+ ```bash
295
+ # ids of everything blocked
296
+ dsh-todo list --status blocked --json | jq -r '.items[].id'
297
+
298
+ # fail CI if any P0 is still open
299
+ test "$(dsh-todo list --open --priority p0 --json | jq '.count')" -eq 0
300
+ ```
301
+
302
+ Ids accept any **unambiguous prefix** — `dsh-todo done tmtcfbut`. Ids are time-ordered so
303
+ short prefixes collide; an ambiguous one is an **error listing the candidates** rather than a
304
+ guess at which task you meant.
305
+
306
+ An **empty value clears a field**: `--release ""` removes the release. From a shell there is
307
+ no other way to say "unset this" as opposed to "leave it alone".
308
+
309
+ > **PowerShell drops empty arguments.** `--release ""` arrives at Node as a bare `--release`
310
+ > flag, so the field is left untouched instead of cleared. Use the `--release=` form there,
311
+ > which survives intact on every shell.
312
+
313
+ Invalid values are **refused, never dropped** — `--due 2026-02-31` exits `2` instead of
314
+ quietly storing nothing, because an agent would otherwise never learn its date was ignored.
315
+
316
+ ### Is it safe alongside the running app?
317
+
318
+ Yes, and it is tested. SQLite is a multi-process database: the file lock refuses a writer that
319
+ lands inside another process's transaction rather than letting it interleave, and the CLI sets
320
+ a `busy_timeout` so it waits for the harness to commit instead of failing. Verified live — the
321
+ CLI wrote while a running server held its handle, and the API returned the new task with no
322
+ restart.
323
+
324
+ The one visible effect is that an **already-open browser tab** may need a refresh: every write
325
+ bumps a revision token, so the tab's next write is refused and it adopts the authoritative
326
+ list. That is the designed reconciliation, not lost data.
327
+
328
+ ## The task model
329
+
330
+ ```ts
331
+ { id, title, description?, status, priority, release?, sprint?, dueDate?,
332
+ createdAt, completedAt?, archivedAt? }
333
+ ```
334
+
335
+ | Field | Notes |
336
+ | --- | --- |
337
+ | `title` | Short and scannable — the one line a collapsed row shows. Capped at 500. |
338
+ | `description` | The body: acceptance criteria, repro steps, links. Its own 5000 cap, because reusing the title's 500 would silently truncate real notes. |
339
+ | `status` | `backlog \| todo \| in-progress \| blocked \| done`. **The source of truth** — there is no separate `done` flag to fall out of sync. |
340
+ | `priority` | `p0`–`p3`, default `p2` so an unranked task sits mid-pile rather than jumping the queue. |
341
+ | `release` | What ships together, e.g. `v1.2.0`. |
342
+ | `sprint` | When it is worked, e.g. `Sprint 24`. |
343
+ | `dueDate` | `YYYY-MM-DD`. A calendar day, not an instant — an epoch would bind it to a timezone and let one task read as two different days. Impossible dates like `2025-02-31` are rejected rather than rolled forward. |
344
+
345
+ **Release and sprint are separate on purpose.** A task can be worked in Sprint 24
346
+ and ship in v1.3.0; collapsing them into one field loses the ability to answer
347
+ either question. Both are free text rather than entities — grouping and
348
+ filtering work with no releases table, no CRUD, and no migration to rename one.
349
+
350
+ `completedAt` is written only by the status transition, so it can never claim a
351
+ task is finished that isn't. Absent optional fields are absent *keys*, never
352
+ `''`, so "no release" has exactly one representation.
353
+
123
354
  ## Storage
124
355
 
125
356
  | Location | Contents |
126
357
  | --- | --- |
127
- | `~/.dsh/storages/dsh_todo.json` | `tables.workspaces[<workspaceId>]` `{ items, revision, updatedAt }` |
358
+ | `<workspace>/.dsh/todo.db` | SQLite: one `todo` row per task, plus a `meta` table holding `revision` and `updatedAt`. |
128
359
 
129
- Each item is `{ id, text, done, createdAt, completedAt?, archivedAt? }`. Archived
130
- items live in the same `items` array — archiving never moves data between
360
+ Archived items live in the same table archiving never moves data between
131
361
  collections, so nothing can be lost in a partial write.
132
362
 
133
- The file is plain JSON and safe to read. Editing it by hand while dsh is running
134
- is not recommended — the host holds the authoritative copy in memory and will
135
- overwrite the file on its next write.
363
+ ### Upgrading from the checklist version
364
+
365
+ Existing databases are migrated **in place** on first open: the new columns are
366
+ added with `ALTER TABLE`, `title` is backfilled from the old `text`, and
367
+ `done = 1` becomes `status = 'done'`. Nothing is dropped, and a list written by
368
+ the old version keeps its order, its completion stamps and its archive.
369
+
370
+ The old `text`/`done` columns are still written alongside their replacements, so
371
+ downgrading to the previous version still reads a sane list.
136
372
 
137
373
  ### Migration from the old browser-only version
138
374