Repository navigation
Releases: LUgABeEEE/dsh-global-todo
Release list
0.22.0 — the list gets a gate, and the phone's targets are measured
One rule for what reaches the list: no deadline, no list.
- An agent record without a deadline waits in the inbox. Measured before this change: 354 of
357 records were written by an agent (~16 a day), and 40 of the 86 open ones said 高 with 34 of
those having no deadline at all. Three doors were writing with one wide threshold — the per-turn
guidance had no gate, and the unattended question reused the manual 「抓一下待办」 wording. Now an
agent-written record withoutdueAtlands in the inbox (收下 / 丢弃, seven-day sweep) with the
reason on the record; a person's own entry is never gated. - Eight a day, then they wait — nothing is dropped. A per-day allowance on agent-written records
reaching the list, derived from the records themselves rather than a counter, so a restart, a
second Host or a sync cannot disagree with it. - The level comes from the deadline. No date = P3, within 7 days = P2, within 48 hours = P1,
overdue = P0. What the model asked for is kept assuggestedPriority; a person's edit locks the
record and the rule never overrides it again. - The inbox is outside the list's numbers, so 全部 / 未完成 mean the list itself.
- A turn that already recorded something is not asked again — the unattended question is skipped
when the agent wrote a todo during that turn, and its prompt is now its own, stricter text. - The panel explains itself: an inbox row names its reason, and the detail says whether the
level is derived from the deadline, locked by you, or what the agent suggested. - Upgrading does one sweep at first start: unfinished agent records with no deadline that nobody
touched move to the inbox, and rule-governed levels are recomputed. On this machine that was 68
records into the inbox, 6 levels changed, the list from 87 to 19 — and every record a person had
edited stayed exactly where it was. - The phone's touch targets are measured now. A probe that walks outward from each control until
the point stops resolving to it found four the stylesheet had been confidently describing as
fine: the search field was a 20px box inside a 44px frame, the attachment chips 22px, the
detail's segmented control 29px, and the bare text buttons 20px. Fixed;npm run check:mobile
now does that measurement in a real browser over the three phone views. - The screenshot baseline is a fixed instant, so the committed images stop drifting every day
while the CSS stands still. - New:
docs/RELEASING.md
— the release procedure with the incident each guard prevents.
Tests: 393, contract checks 101/101, mobile contract green on all three phone views.
Full changelog: CHANGELOG.md
0.21.0 — one list on two machines, and a phone page that folds
One list on two machines, and a phone page that folds.
- Each machine keeps its own list; they meet in a directory. Pointing
storePathat a NAS makes every panel read a network round trip and leaves an offline laptop unable to open its own todos. Instead both machines fold throughtodos.hub.jsonin a shared directory (syncDirin 设置 → 多端同步,0= manual only). LaterupdatedAtwins per record,seqis renumbered from the record set, the fold is commutative and idempotent, and a sync that changes nothing writes nothing. - A deletion leaves a tombstone.
removeused to drop the record, so the next copy from the other machine put it back. Documents now carrytrash: [{ id, at, by }]; undo revokes the tombstone instead of letting it delete the restored copy again. Reading the list never touches the share — a stale mount cannot hang the panel, and/health.syncsays what the last attempt did. - The phone page folds, and the fold is a gesture. At 600px or less the panel measures its own width: a 44px header line, a three-row toolbar (filters / search / grouping), 66px rows, 44px targets, safe areas respected. Scrolling down past 80px folds the search line away — after clearing the 110px it gives back, or the content jumps out from under the thumb — and a 48px flick up unfolds it; either direction is deaf for 300ms after a flip. Folded rows keep their title plus the signals that change what to do next; the open row stays whole. The dispatch summary and auto-collect switch left the phone toolbar for the settings sheet, and
dsh-mobileneeds no configuration. - Grouping by workspace is real. The first mode is the directory the work was written in (
originDir, falling back to the session's directory), headed by its last segment with the full path as the tooltip; a workspace group never auto-collapses, and the per-group exemption keys on exactly what the group is. Labels: 工作区 / 时间 / 待办等级. - Two silent failures fixed on the way.
/syncwas registered twice per path; the Host keys exact routes by path alone and throws on the second, and the bundle registers its whole list in one pass — so every route after/syncdisappeared while reads kept answering and writes returned a bare 404. And the primary button painted its label with the canvas token (--dsw-alias-bg-base), which a wallpaper theme sets transparent: a white pill with no word on it. Five places fixed, plus a test that forbids surface tokens incolor/fill/stroke. - Tests: 381, contract matrix 85/85, both screenshot sets regenerated. Schema 2 — every machine sharing a hub needs 0.21.0 or newer, and a 0.20.0 host refuses a version-2 file loudly rather than dropping tombstones.
Full changelog: CHANGELOG.md
0.20.0 — the list and the memory store split the job
The list stops competing with the memory store for the same sentence.
- A bridge with the memory plugin, as a sentence rather than a wire. When dsh-mnemon is
installed, the prompt gains one extra section — actions belong to this list, facts belong to
memory, do not store the same thing twice — and a completed todo's tool result says to
mnemon_rememberthe conclusion with the todo id in the body. Both exist only when
mnemonMemorydoes:ctx.inject(['systemPrompt','mnemonMemory'], …)gates the section, and
createTools({ hasMemory })asks per call. Naming a tool the deployment lacks is the failure the
tool-group gate already prevents. - Two refusals, recorded in
docs/DESIGN.md. No reading or writing of its SQLite store (a
private schema, and its single service is a definition registry, not a "remember" call), and no
mnemon-source-global-todopackage in this repository (a second bundle with a peer dependency;
this one has to keep working alone). The extension SDK it publishes is the documented path if
someone wants recall to see open items. - Tests:
memoryGuidanceForgating, the completion line with and without a memory plugin, and
three contract checks that keep the bridge from naming a tool acoredeployment does not have. - Checked against DSH 0.2.1-rc.3. It adds More menus (browse other plugins, create a plugin) to
the left sidebar, the conversation view tabs and the right-sidebar guide; fixes New Session losing
an unsent draft; and changes how the DeepSeek API-key adapter refreshes its model list. None of it
changes a plugin declaration:packages/boot/hmr/src/package-manifest.tsand
packages/client/modules/src/client/manifest.tsare byte-identical between rc.2 and rc.3, and the
sidebar entry keeps registering the same way — the new menu lists entries, it does not change how
one is declared. - The declared range already admits the new line, and now says why.
engines.dshstays
>=0.2.0-rc.1 <0.3.0; the Host matches withincludePrerelease: true(dsh-app-boot's
satisfies), which is why0.2.1-rc.3is inside a range whose lower bound names the 0.2.0
prerelease.scripts/matrix.mjsgained that case and a warning: "cleaning up" the range into
^0.2.1would refuse both0.2.1-rc.2and0.2.1-rc.3. Both READMEs and the quickstart now
name the versions that were checked instead of implying one.
0.19.0 — a phone layout, chosen by the panel's measured width
- A phone layout, chosen by the panel's measured width. At 600px or less the panel is a touch
surface: 44px controls and inputs, filter chips that scroll sideways instead of losing the tail
that only appears when it has counts, the row's hand-off collapsed to its icon so meta packs two
or three per line (the creator attribution moves to the detail), the keyboard hints gone, the
bottom safe area respected, and type following the shell's--dsh-content-font-size. The
desktop page is unchanged, and DSH Mobile's phone page needs no configuration — the panel was
already using measured width for the 960px two-column breakpoint, and this is the same signal
one step down. Three stable hooks carry it (data-mobile,.gt-btn-label,.gt-meta-by); no
second render path. - The fixture can answer the measurement, which it never could before.
tools/preview.mjs
gives every ref a layout stub sized to--width(and--mobilewrites the 390px set into
preview/mobile/), so the narrow branch is rendered by a script instead of only by a device.
npm run check:renderasserts it on every verify, andnpm run screenshots:mobilephotographs
the 390×844 baselines intodocs/screenshots/mobile/. - A dead writer on the same host is reaped immediately, not after 30 s. 0.18.0 checked the
lock's age before the holder's pid, so a crashed writer left the list refusing writes until the
file turned 30 s old — while waiters give up at 5 s. Age now only decides the case with no pid to
ask (another machine, or an unreadable claim); on this host a dead pid is enough. The test that
covers it writes a fresh lock file on purpose, because a stale one would pass either way. docs/WINDOWS.mdgained the matching three-point checklist for the machine that can actually
see the difference: exclusive create, no.lockleft on disk, and a kill -9 followed by a write.- The lock is now findable from wherever somebody would look. Both READMEs name the lock file in
the data section and say onlytodos.jsonis worth backing up;docs/QUICKSTART.mdadds it to
the backup table;docs/ROADMAP.mdrecords it under "landed"; the FAQ gained question 16,
「一直报 store-busy 怎么办」, with the five checks in the order a person should make them; and
AGENTS.md says the store must never be written outsidestore.update, because that is how the
bug can come back.
0.18.0 — a lock file, so two writers cannot overwrite each other
Two processes writing the same list no longer means one of them silently loses a change.
- A lock file, not just a promise chain.
store.updateserialized writers inside one process
only; two DSH processes (the Web panel plus a desktop or CLI session, or two machines on a synced
store) each read the list, and the slower rename erased the faster change while both calls returned
success — the report that prompted this. Every mutation now takes<store>.lockwith
open(…, 'wx')before reading and releases it after the rename (lib/lockfile.js). - A crashed writer cannot lock the list forever. The claim records
pid+host+ a random
token: on the same host takeover requires a dead pid, across hosts it requires 30 s of age, and a
reaped-then-retaken lock is never deleted by its previous owner. Takeovers log a warning; waiters
retry with jittered backoff and give up after five seconds. - New code
store-busy(HTTP 503; a tool returns{ ok: false, reason: "store-busy" }): a write
that could not happen says so instead of returning success, and the message names the holder when
it can be read. - What it does not cover is now said out loud in
docs/FAQ.mdanddocs/DESIGN.md: an older
plugin version that does not take the lock, a hand edit, and a sync client writing at an arbitrary
moment are still last-writer-wins; the panel keeps reporting external changes. - Tests:
test/lock.test.mjs, including two real child processes adding ten todos each to one file
(all twenty survive). Checked to fail with the lock disabled, so it pins the bug, not the mechanism.
0.17.1 — the READMEs are one language each
Two READMEs instead of one: Chinese in README.md, English in README.en.md, and the switch to
the other language on the first line of each.
- Two READMEs, one language each.
README.mdis the Chinese document — the name GitHub's
repository page shows and the only one npm renders — andREADME.en.mdis the English one; each
opens with a one-line switch to the other. The 0.17.0 npm page had an English document with the
Chinese only reachable through a relative link, and relative links are exactly what npm rewrites
or drops, so the switch is an absolute GitHub URL (a CJK heading slug is not stable across
renderers either).README.zh.mdbecomes a two-line landing page so old links still resolve;
README.en.mdjoinedfiles, andtest/manifest.test.mjsnow reads the Node and DSH floors out
of the two real documents. AGENTS.md records the convention.
0.17.0 — the confirmation gate is the headline
Documentation and prompt text: the confirmation gate is the headline now, not the third bullet.
Nothing in the write path changed — no field, no route, no validation rule — so upgrading is safe
for existing stores.
-
A release at all, because npm freezes documentation at publish time. The README,
docs/and
the plugin-page locales in the tarball are a snapshot; leading with the gate only reaches the npm
page and the installed docs through a version. -
Both READMEs lead with the gate. It is claim 01 (was 03) and it names both doors: an
agent'sglobal_todo_done(or a status change todone), and the host closing a dispatched todo
after a clean turn. A new early section — 「完成确认:没有一条记录能自己变成「完成」」 / Confirmation:
no record finishes itself — carries the two-door table, the two answers and the clean-turn rule;
the old 0.7 subsection and its row are gone rather than duplicated, and the comparison table gained
a Finishing row. -
The landing page, the npm description and the plugin-page locales open with the same sentence:
page title,meta/og/twitterdescriptions, hero lede, claim 01 (was 03), and the compare
table.package.jsongainedhuman-in-the-loop,confirmationandapproval, which is how
someone looking for this actually searches. -
docs/DESIGN.mddocumentsreviewfor the first time. It lived inlib/model.jsand in no
design doc: there is now a Done is a claim, from either door section, areviewfield row, and
thelinksrow finally admitskind: 'image'(0.15 shipped it). -
The model-facing half says it too. The
global_todo_doneandglobal_todo_update
descriptions call a completion a claim; the done result prints 「这条挂在「待确认」…」;todoLine()
marks pending records in every rendering, so the model cannot read one as settled; and the injected
guidance tells the model to reply 「已标记完成,等你确认」 instead of 「已经结束了」. The stale
「插件也会在你安静下来后自动划掉」 line is gone — that idle-window heuristic was removed in 0.3.0
and the sentence outlived it by thirteen minor versions, telling the model the opposite of the rule.
0.16.3 — the docs reach npm, and the release order is fixed
[0.16.3] — 2026-10-23
Documentation release: the npm page finally shows the current install instructions.
- npm's README is frozen at publish time, so the install/per-profile rewrite and the
「哪些功能还没有 / Not there yet」 sections (README.zh.md, FAQ, ROADMAP) reach GitHub but not the
npm page until a new version ships. Nothing inlib/changes. - The market index no longer blocks publishing. Its test required the index's newest version
to equalpackage.json's — which cannot hold between a version bump and its publish, and
prepackruns the suite, so the first release after the market index landed could not be
tagged. The offline rule is now one-directional (the index may not offer a version newer than
the tree), and staleness moved tonpm run market:fresh, which asks the registry. AGENTS.md
documents the two-step order: publish, then regenerate the index and commit it.
0.16.2 — a date-only deadline is local midnight
[0.16.2] — 2026-10-22
A date-only deadline is local midnight, so a date bound stops swallowing part of its own day.
before:/after:with a bare date were anchored at UTC midnight.Date.parse('2026-11-01')
is UTC midnight by the specification; on +08 that is 08:00 local, sobefore:2026-11-01included
everything due 00:00–08:00 on the bound day, and anafter:bound lost the same window in the
west. Date-only values are now built with the local constructor, matching howdueAtis authored
(issue #6).- This also changes what a bare
dueAt: "2026-11-01"stores: it used to become UTC midnight
and now becomes local midnight — the day the person means, on the machine they are on. Existing
records are untouched; only new writes differ. - The timezone case is pinned in
test/tz.test.mjs, which runs the parser in a child process
withTZ=Asia/Shanghai: CI is UTC, where both anchors are the same number, so the regression
would otherwise be invisible there. On a platform that ignoresTZthe test skips rather than
claiming to have checked. docs/WINDOWS.mdgained two answers from the same round: the date-bound row now states the
local-midnight contract, and a standing three-question checklist (two-layer semantics, timezone,
path four states) sits beside the per-feature one, plus the note that ARIA states must be
verified by attribute, not by searching for the words a screen reader speaks.
0.16.1 — a tag whose CI is green
[0.16.1] — 2026-10-22
Same code as 0.16.0; the tag now points at a commit whose CI is green.
- 0.16.0's tag commit had all three CI jobs red. The new
before:/after:test asserted the
order of two todos created in the same test, and CI creates them in the same millisecond, so the
order it asserted was not the order the default sort produces. The behaviour under test was
correct; the assertion was wrong. Fixed in 8024a48 — the run after it is green on both platforms. - Why a release rather than a moved tag:
v0.16.0is published on npm, and a tag that no
longer matches what was published is worse than a version bump. 0.16.1 is 0.16.0 plus the
assertion, so its tag has a green check.