@dennisrongo/dsh-todo 0.1.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 ADDED
@@ -0,0 +1,147 @@
1
+ # @dennisrongo/dsh-todo
2
+
3
+ A todo list for the [DeepSeek Harness](https://github.com/deepseek-ai) (dsh) web UI.
4
+
5
+ Registers into the additive `conversation.view` slot — the conversation view
6
+ ring — so it appears as its **own tab beside Chat and Trajectory** (`order: 20`,
7
+ after chat at `0` and trajectory at `10`) and fills the session pane when
8
+ selected.
9
+
10
+ ## Features
11
+
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.
15
+ - **Per-workspace** — each workspace has its own list, keyed by workspace id.
16
+ - **Safe against races** — every write carries the revision it observed; a
17
+ losing write is refused and the view adopts the authoritative list, so two
18
+ 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.
21
+ - **Archive, not delete** — check items off, then "Archive completed" files them
22
+ away. Archived items leave every active view but stay in the record, and can be
23
+ restored (↩) or permanently deleted from the Archive view.
24
+ - **Full editing** — add, check off, click-to-edit, reorder (▲/▼), archive, and
25
+ delete.
26
+ - **Themed** — colors come only from the shell's `--dsw-*` tokens, so it follows
27
+ light/dark automatically. Respects `prefers-reduced-motion`.
28
+
29
+ ## Architecture
30
+
31
+ This is a **dual-face plugin**. Both halves ship from one package.
32
+
33
+ | Half | File | Role |
34
+ | --- | --- | --- |
35
+ | Host | `src/index.ts` | `TodoService`, a `TypertRemoteService` that owns the storage domain and exports `list` / `replace` as `@Remote` methods. |
36
+ | Client | `src/client.tsx` | The React tab. Mounts the host contract and calls it as `ctx.remote.dshTodo.*`. |
37
+ | Bridge | `src/remote.ts` | The Typert Remote descriptor the client mounts. |
38
+ | Shared | `src/types.ts` | Dependency-free vocabulary used by both halves. |
39
+
40
+ The browser holds no authority over the data: it renders an optimistic echo and
41
+ the committed host revision always wins.
42
+
43
+ ### Two build constraints that are easy to break
44
+
45
+ Both are asserted by the smoke test, because both fail silently at runtime:
46
+
47
+ 1. **The host half must not be minified.** The Typert gateway discovers a
48
+ `@Remote` method's wire fields by reading its *parameter names* out of
49
+ `Function.prototype.toString()`. Minifying `request` to `e` changes the wire
50
+ contract.
51
+ 2. **The host half must target `es2021`.** `@Remote` is a TC39 *standard*
52
+ decorator, and Node 22 cannot yet parse native decorator syntax. esbuild only
53
+ downlevels decorators when the target predates them; at `es2022+` it emits
54
+ them verbatim and the host half fails to load.
55
+
56
+ ### Peer dependencies, not dependencies
57
+
58
+ `@deepseek-ai/cordis`, `dsh-typert-protocol`, and `dsh-storage-domain` are
59
+ declared as **peer** dependencies and marked external in the host build. They
60
+ must resolve to the *running dsh install's* copies — a second cordis instance
61
+ would register into a different registry and the service would never appear.
62
+ dsh profiles set `autoInstallPeers: false`, so this resolves correctly.
63
+
64
+ ## Commands
65
+
66
+ ```bash
67
+ pnpm install
68
+ pnpm run build # node build/build.mjs — emits lib/index.js + lib/client.js
69
+ pnpm run typecheck # tsc --noEmit
70
+ pnpm test # node test/smoke.mjs — offline, exercises the BUILT lib/ output
71
+ ```
72
+
73
+ `pnpm test` asserts against `lib/`, so **build before testing**.
74
+
75
+ ## Install into a dsh profile
76
+
77
+ The profile must already compose the storage rows (`storage`, `storage-json`,
78
+ `storage-domain`); `@deepseek-ai/dsh-web-app` does this by default.
79
+
80
+ ```bash
81
+ cd ~/.dsh/profiles/<profile>
82
+ pnpm add "file:/absolute/path/to/dsh-plugins/plugins/dsh-todo"
83
+ ```
84
+
85
+ then add the row to `cordis.patch.yml`:
86
+
87
+ ```yaml
88
+ - insert:
89
+ - id: dsh-todo
90
+ name: '@dennisrongo/dsh-todo'
91
+ ```
92
+
93
+ and restart the profile. The single row mounts both halves: the host service
94
+ and the browser tab.
95
+
96
+ > The profile installs `file:` dependencies as a **copy**, not a symlink, so
97
+ > after `pnpm run build` you must re-run `pnpm install` in the profile to pick
98
+ > up the new artifacts.
99
+
100
+ ## Archiving
101
+
102
+ Completed work is **archived, not deleted**. An item carries an optional
103
+ `archivedAt` epoch-ms stamp; its *presence* is the archived state, so there is
104
+ one source of truth and no way to store an archived item without a date.
105
+
106
+ | Action | Where | Effect |
107
+ | --- | --- | --- |
108
+ | Archive completed | footer, any active view | Stamps every done item. Recoverable. |
109
+ | Archive (⌸) | row hover, completed items | Stamps one item. Recoverable. |
110
+ | Restore (↩) | row hover, Archive view | Clears the stamp, returning it to the list. |
111
+ | Delete (✕) | row hover | Removes one item outright. |
112
+ | Delete archived | footer, Archive view | Permanently drops every archived item — the only destructive bulk action, and the only one that asks for confirmation. |
113
+
114
+ Archived items are excluded from the progress bar and the done/total score, so
115
+ tidying up never makes progress appear to regress. They sort newest-archived
116
+ first, so the Archive view reads as a log. Reordering is computed in
117
+ active-list space, so a hidden archived entry between two visible rows cannot
118
+ swallow a move.
119
+
120
+ `clearCompleted` (hard delete of done items) is still exported for callers that
121
+ want it, but it is no longer wired to a button.
122
+
123
+ ## Storage
124
+
125
+ | Location | Contents |
126
+ | --- | --- |
127
+ | `~/.dsh/storages/dsh_todo.json` | `tables.workspaces[<workspaceId>]` → `{ items, revision, updatedAt }` |
128
+
129
+ Each item is `{ id, text, done, createdAt, completedAt?, archivedAt? }`. Archived
130
+ items live in the same `items` array — archiving never moves data between
131
+ collections, so nothing can be lost in a partial write.
132
+
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.
136
+
137
+ ### Migration from the old browser-only version
138
+
139
+ Earlier versions stored todos in `localStorage` under `dsh-todo:items`. On first
140
+ run, that list is imported **once** into the first workspace that opens with an
141
+ empty stored list, and the import is then marked with `dsh-todo:migrated`. The
142
+ original key is deliberately left in place rather than deleted.
143
+
144
+ ## Notes
145
+
146
+ - The host half publishes a service, so it belongs to the profile's host
147
+ composition — not to an agent preset.
@@ -0,0 +1,9 @@
1
+ # dsh bundle patch: inserts this plugin into a profile's layer stack, so
2
+ # `dsh plugin add` mounts it without hand-editing the profile's own
3
+ # cordis.patch.yml. Both id: and name: are required — a bare id: is an
4
+ # id-targeted override of an existing row and silently no-ops.
5
+ #
6
+ # One row mounts BOTH halves: the host service (dshTodo) and the browser tab.
7
+ - insert:
8
+ - id: dsh-todo
9
+ name: '@dennisrongo/dsh-todo'