# Changelog

All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). GitHub Releases use the same bilingual layout as [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.6-alpha.1).

## [0.4.13] - 2026-11-04

### Changed

- Declare DeepSeek Harness `0.2.2-rc.1` compatible while preserving `>=0.1.7-rc.1 <0.3.0` and all earlier verified releases. No notification source changes are needed.
- Synchronize both READMEs, the package and lockfile versions, compatibility matrix, DSH badge, channel information and release commands. Clarify that current DSH requires Node.js `>=24.9.0`, while the plugin's own `>=20` requirement is unchanged.

### Verified

- Compared 19 consumed DSH package build trees with published `0.2.2-alpha.3`: only the general-settings display version and client-modules build commit changed; all other compared build files are byte-identical. Current Cordis `4.1.0` passes Host and Client activation and cleanup tests.
- Static checks, **118 automated tests**, build, package checks and dependency audit pass. Live Client inspection confirms `dsh-notify-toast` active in `shell.overlay`; registration is not a fresh visual, audio or end-to-end test.
- Release acceptance downloads the npm tarball and checks both file-set differences and each file's bytes against the release checkout, plus npm `latest`, GitHub Release notes, tarball and checksum. No profile or other plugin is modified.

## [0.4.12] - 2026-11-03

### Fixed

- Make the Client plugin entry non-constructible and return an idempotent disposer instead of a plain runtime object. Cordis treats ordinary functions as constructors and can ignore their cleanup return; passing the old object through its effect path instead throws `Invalid effect`. The prior pack check called `apply` directly and missed these lifecycle errors. Settings and overlay registrations now clean up through the accepted effect contract.
- Synchronize the lockfile's DSH peer ranges with the manifest (`>=0.1.7-rc.1 <0.3.0`); its root still carried the obsolete `<0.2.0` ceiling. Update locked transitive Zod `4.6.5` → `4.6.7` to resolve the high-severity audit advisory without modifying the host installation.

### Changed

- Declare the running DSH `0.2.2-alpha.3` compatible. Consumed renderer, session UI, connection, webserver, jobs, session, agent, agent-loop and workflow builds are unchanged from alpha.2; additive settings/layout changes and module-loader reconciliation changes preserve the consumed contracts.
- Add regression checks for bilingual README versions, DSH badges, compatibility rows, release commands, changelog and Release headings, and root lockfile metadata. Refresh both READMEs and clearly separate historical screenshots from this check.

### Verified

- Static checks, **118 automated tests**, build and package checks, including real Cordis client load/unload and release-metadata regressions.
- Host API inspection and live Client inspection (`dsh-notify-toast` active in `shell.overlay`) on DSH `0.2.2-alpha.3`. The existing profile was not upgraded or changed.
- Browser automation returned HTTP 401: no new authenticated visual, mobile, audio or end-to-end notification verification is claimed. DSH npm `latest` is `0.2.1-rc.5`; that unbooted RC is not added to the explicitly verified list.
- Post-publication acceptance compares the npm tarball's documents, manifest, patch and every build file with the release checkout and requires npm `latest`, GitHub Release and tag to match.

## [0.4.11] - 2026-10-28

### Changed

- Declare DeepSeek Harness `0.2.2-alpha.2` compatible. The `>=0.1.7-rc.1 <0.3.0` requirement already admitted it, so no range had to widen; no notification source change is needed.
- **Of every package this plugin binds, the ones that own its interfaces are byte-identical across `0.2.1-rc.4` → `0.2.2-alpha.2`; the six that changed did so additively, off this plugin's call path.** `dsh-client-ui-renderer` — which owns the whole `slots` service, `shell.overlay` and `settings.section` — is byte-identical, as are `dsh-client-ui-settings`, `dsh-jobs`, `dsh-client-ui-slots`, `dsh-tool-workflow` and `dsh-client-connection`'s `lib/index.js`, so the seats, the jobs `settled` contract and `requestRejection` cannot have moved. `dsh-host-webserver` adds a second `resource` route style while `register(route)` stays byte-identical and the plugin's `{kind:'exact',path,handler}` route still lands on `responder.handler(req, res)`; `dsh-client-ui-session` adds one `whenAccess()` method; `dsh-client-ui-layout` adds six locale dictionaries; `dsh-client-modules` adds `require.asset` and a `/plugins/<pkg>/assets` route without touching the `dsh.client` validator or the `__ModuleLoader__.load({id, factory})` shape; `dsh-client-ui-settings-general` restyles the host settings panel into a CSS grid (the seat's `{close}` props and `{id,order,label}` row derivation are unchanged); `dsh-client-connection`'s only other change is an optional browser-transport type. The host's `cordis` and `schemastery` moved to `4.0.6-alpha.1` / `3.18.6-alpha.1` — the former changes only the fiber config lifecycle, the latter's code is byte-identical — and the plugin passed DSH's own gate on both releases with no exemption needed or granted.
- Recheck both READMEs for matching current plugin and DSH versions, badges, compatibility rows, version examples and release commands, and synchronize the lockfile's root version with the release.

### Verified

- The running `0.2.2-alpha.2` host already delivered this plugin's `0.4.10` build: `/plugins/dsh-notify/health` answered `guiAvailable: true` with `services.jobs: "reflected"` and `eventErrors: 0`, and `/config`, `/sounds` and `/update` all answered 200, the update check reading the real npm registry. The profile holds no exact-version exemption for this plugin.
- Client half on `0.2.2-alpha.2`, inspected in a real browser against the running page: the boot graph lists `@idoall/dsh-notify/client.js` and its served entry answered 200; Settings → Notifications rendered the 通知显示 / 提示音 / 任务提醒 / 预览与自测 sections, the preview sent a real card into `.dsh-notify-frame`, and the console stayed clean. At a 390px viewport the section fits with no horizontal overflow and the toast is full-width.
- Full verification: static checks, **112 automated tests**, build, and package checks.
- Release acceptance downloads the published npm tarball, compares its documents and manifest with the release tag, and checks every distributed build file against a fresh build; npm `latest` and the GitHub Release must agree.

## [0.4.10] - 2026-10-27

### Changed

- Declare DeepSeek Harness `0.2.1-rc.4` compatible. The `>=0.1.7-rc.1 <0.3.0` requirement already admitted it, so no range had to widen; no notification source change is needed.
- **Of every package this plugin binds, every one is byte-identical across `0.2.1-rc.3` → `0.2.1-rc.4` except `dsh-client-ui-settings-general`, whose bundled client only substitutes the host version string (`general.currentVersion`) it displays.** `dsh-host-webserver`, `dsh-client-connection`, `dsh-client-ui-renderer`, `dsh-client-ui-layout`, `dsh-client-ui-settings`, `dsh-client-ui-session`, `dsh-jobs`, `dsh-app-boot`, `dsh-base` and `dsh-api-remotes` differ only in their `package.json` version and devDependency pins, and `dsh-client-modules` — the package that composes the page's client entry graph — changes only the commit hash it reports in release metadata. `cordis` and `schemastery` are unchanged at `4.0.5` / `3.18.5`.
- Recheck both READMEs for matching current plugin and DSH versions, badges, compatibility rows, version examples and release commands, and synchronize the lockfile's root version with the release.

### Verified

- The running `0.2.1-rc.4` profile already delivered this plugin's `0.4.9` build: `/plugins/dsh-notify/health` answered `guiAvailable: true` with `services.jobs: "reflected"` and `eventErrors: 0`, and `/config`, `/sounds` and `/update` all answered 200, the update check reading the real npm registry.
- Client half on `0.2.1-rc.4`: the page's boot graph lists `@idoall/dsh-notify/client.js` among 85 client entries, and its served entry answered 200 carrying the `settings.section` and `shell.overlay` registrations plus the `dsh-notify-*` markup.
- Full verification: static checks, **112 automated tests**, build, and package checks.
- Release acceptance downloads the published npm tarball, compares its documents and manifest with the release tag, and checks every distributed build file against a fresh build; npm `latest` and the GitHub Release must agree.

## [0.4.9] - 2026-10-26

### Added

- **One-click clear-all above the notification pile.** With two or more live cards, a **全部清除** strip sits above the pile and retires every card on the page at once. Clearing stays page-local: nothing is acknowledged, a pending question or approval is still waiting in its session, and a cleared record is not presented again by the polls that re-deliver it. The strip is part of the frame's height (a 20px pill plus a 6px gap) rather than an overlay on the newest card, so card content keeps every line; the `+N` queue counter moves down by the same amount instead of drawing under it, and the empty part of the strip stays click-through. A single card shows no strip — its own × is the whole story.

### Verified

- New jsdom coverage: a single card offers no strip, one click clears the pile and the next poll does not resurrect it, and clearing a pending question never answers it on the user's behalf.
- Full verification: static checks, **112 automated tests**, build, and package checks — the pack check materializes the shipped client bundle and requires both the `settings.section` and `shell.overlay` seats to activate.
- Previewed in a real DeepSeek Harness `0.2.1-rc.3` profile with the built client bundle; the clear-all strip, the settings section, and the toast stack were confirmed in the running page. Compatibility declarations, the `>=0.1.7-rc.1 <0.3.0` host requirement, and `dshReleases` are unchanged.

## [0.4.8] - 2026-10-23

### Changed

- Declare DeepSeek Harness `0.2.1-rc.3` compatible. The existing `>=0.1.7-rc.1 <0.3.0` requirement remains unchanged; no notification source changes are needed.
- Recheck both READMEs for matching current plugin and DSH versions, badges, compatibility rows, version examples, and release commands. Correct the older statement calling `0.2.1-rc.2` the first RC.
- Synchronize the lockfile's root version with the release without changing dependency resolutions.

### Verified

- The consumed core Host and Client APIs remain unchanged from `0.2.1-rc.2`; changes to general settings and workspace blank-session handling do not affect the notification entry points.
- The running `0.2.1-rc.3` host serves the notification routes successfully with `eventErrors: 0`; live Client inspection confirms the notification overlay registration is active. Registration is not a visual screenshot test.
- Full verification: static checks, 110 automated tests, build, and package checks.
- Release acceptance includes downloading the published npm tarball, comparing documents and manifest with the release tag, and verifying each distributed build file against a fresh build. The npm `latest` tag and GitHub Release versions must agree.

## [0.4.7] - 2026-10-17

### Changed

- **Declared DeepSeek Harness `0.2.1-rc.2` compatible, the first release candidate of the `0.2.1` line.** `dsh.compatibility.dshReleases` gains `0.2.1-rc.2` after `0.2.1-alpha.5`. The requirement itself stays `>=0.1.7-rc.1 <0.3.0`, which already admitted it — that is why `dsh-notify` was not among the rows the loader refused at startup on `0.2.1-rc.2`, and no range widening, exemption or code change was needed. `0.2.1-rc.1` is deliberately **not** declared: this machine never booted it, and each entry in that list names a real boot rather than a semver guess.

### Verified

- **Of every package this plugin binds, exactly one differs across `0.2.1-alpha.5` → `0.2.1-rc.2`, and it differs only in a displayed string.** `dsh-host-webserver`, `dsh-client-connection`, `dsh-jobs`, `dsh-client-ui-renderer`, `dsh-client-ui-layout`, `dsh-client-ui-settings`, `dsh-client-ui-session`, `dsh-session`, `dsh-llm`, `dsh-tool-workflow`, `dsh-workflow`, `dsh-agent-loop`, `dsh-agent`, `dsh-user-questions` and `dsh-tools` are byte-identical. The exception is `dsh-client-ui-settings-general`, whose bundled client only substitutes the host version string (`general.currentVersion`) it renders; the `settings.section` and `shell.overlay` seats this plugin mounts into are untouched.
- **The host's `cordis` and `schemastery` promoted from `4.0.5-alpha.2` / `3.18.5-alpha.2` to stable `4.0.5` / `3.18.5`, with no code difference at all** — the published stable tarballs are byte-identical to the alphas apart from their `package.json`. Both remain inside this plugin's `^4.0.4` / `^3.18.4` peer ranges.
- **A real profile on `0.2.1-rc.2` admitted this plugin.** The loader disabled the `browser-use` and `browser-use-playwright-mcp` rows and no others, and no exact-version exemption was needed or granted for `@idoall/dsh-notify`.
- **Host half live on `0.2.1-rc.2`:** `/plugins/dsh-notify/health` answered `guiAvailable: true` with `services.jobs: "reflected"` and `eventErrors: 0`; `/config`, `/sounds` and `/update` (real npm registry: running `0.4.6`, newest `0.4.6`) all answered 200.
- **Client half live on `0.2.1-rc.2`:** the page lists `@idoall/dsh-notify/client.js` in its plugin bundle, and the served combo bundle answered `200` carrying the `settings.section` and `shell.overlay` registrations and the `dsh-notify-*` markup.
- Full verification passed: static source checks, **110 automated tests**, build, and package precondition checks.

## [0.4.6] - 2026-10-16

### Changed

- **Declared DeepSeek Harness `0.2.1-alpha.5` compatible.** `dsh.compatibility.dshReleases` gains `0.2.1-alpha.5` after `0.2.1-alpha.4`. The requirement itself stays `>=0.1.7-rc.1 <0.3.0`, which already admitted it — that is why `dsh-notify` was not among the rows the loader refused at startup on `0.2.1-alpha.5`, and no range widening, exemption or code change was needed.

### Fixed

- **`README.zh.md`'s DSH badge said `0.2.1-alpha.3`, two releases behind.** The English badge, the Requirements line, the Compatibility heading and the release table had all moved on, so the Chinese README advertised a host version the plugin had long since moved past — and a badge is the first thing a reader sees. The badge now tracks the same version as every other declaration, and this release was audited for stale version strings across both READMEs rather than only in the lines being edited.

### Verified

- **Every package this plugin binds is byte-identical across `0.2.1-alpha.4` → `0.2.1-alpha.5`, with two changes off this plugin's paths.** `dsh-host-webserver`, `dsh-client-connection`, `dsh-jobs`, `dsh-client-ui-renderer`, `dsh-client-ui-layout`, `dsh-client-ui-settings`, `dsh-client-ui-session`, `dsh-session`, `dsh-llm`, `dsh-tool-workflow`, `dsh-workflow`, `dsh-agent-loop`, `dsh-agent` and `dsh-user-questions` carry no source change at all. `dsh-tools` changes only the `run_code` tool's flavour descriptions (TypeScript function-body flavour, argument wording) and never touches `toolCallId`, `isError` or the `tool/result` session payload this plugin reads; `dsh-client-ui-settings-general` only substitutes the displayed host version string in its bundled client. The `settings.section` and `shell.overlay` seats this plugin mounts into are untouched.
- **A real profile on `0.2.1-alpha.5` admitted this plugin.** The loader disabled the `browser-use` and `browser-use-playwright-mcp` rows and no others, and no exact-version exemption was needed or granted for `@idoall/dsh-notify`.
- **Host half live on `0.2.1-alpha.5`:** `/plugins/dsh-notify/health` answered `guiAvailable: true` with `services.jobs: "reflected"` and `eventErrors: 0`; `/config`, `/sounds` and `/update` (real npm registry: running `0.4.5`, newest `0.4.5`) all answered 200.
- **Client half live on `0.2.1-alpha.5`:** the page lists `@idoall/dsh-notify/client.js` in its plugin bundle, and the served combo bundle answered `200` (4.4 MB) carrying the `settings.section` and `shell.overlay` registrations and the `dsh-notify-*` markup.
- Full verification passed: static source checks, **110 automated tests**, build, and package precondition checks.

## [0.4.5] - 2026-10-16

### Changed

- **Declared DeepSeek Harness `0.2.1-alpha.4` compatible.** `dsh.compatibility.dshReleases` gains `0.2.1-alpha.4` after `0.2.1-alpha.3`. The requirement itself stays `>=0.1.7-rc.1 <0.3.0`, which already admitted it — that is why `dsh-notify` was not among the rows the loader refused at startup on `0.2.1-alpha.4`, and no range widening, exemption or code change was needed.

### Verified

- **Every package this plugin binds is byte-identical across `0.2.1-alpha.3` → `0.2.1-alpha.4`, with one additive exception.** Comparing the published packages field by field, `dsh-host-webserver`, `dsh-client-connection`, `dsh-jobs`, `dsh-client-ui-renderer`, `dsh-client-ui-layout`, `dsh-client-ui-settings`, `dsh-client-ui-session`, `dsh-session`, `dsh-llm`, `dsh-tool-workflow`, `dsh-workflow`, `dsh-agent-loop`, `dsh-agent`, `dsh-user-questions` and `dsh-tools` carry no source change at all. Only `dsh-client-ui-settings-general` differs, and what it adds is off this plugin's paths: a Host release change prompt (`HostReleasePrompt`) with its `hostRelease.*` locale keys, plus a cosmetic renumbering of the bundled CSS variables. The `settings.section` and `shell.overlay` seats this plugin mounts into are untouched.
- **A real profile on `0.2.1-alpha.4` admitted this plugin.** The loader disabled the `browser-use` and `browser-use-playwright-mcp` rows and no others, and no exact-version exemption was needed or granted for `@idoall/dsh-notify`.
- **Host half live on `0.2.1-alpha.4`:** `/plugins/dsh-notify/health` answered `guiAvailable: true` with `services.jobs: "reflected"` and `eventErrors: 0`; `/config`, `/sounds` and `/update` (real npm registry: running `0.4.4`, newest `0.4.4`) all answered 200.
- **Client half live on `0.2.1-alpha.4`:** the page lists `@idoall/dsh-notify/client.js` in its plugin bundle, and the served combo bundle answered `200` (4.4 MB) carrying the `settings.section` and `shell.overlay` registrations and the `dsh-notify-*` markup.
- Full verification passed: static source checks, **110 automated tests**, build, and package precondition checks.

## [0.4.4] - 2026-10-15

### Changed

- **Declared DeepSeek Harness `0.2.1-alpha.3` compatible.** `dsh.compatibility.dshReleases` gains `0.2.1-alpha.3` after `0.2.1-alpha.1`. The requirement itself stays `>=0.1.7-rc.1 <0.3.0`, which already admitted it — that is why `dsh-notify` was not among the rows the loader refused at startup on `0.2.1-alpha.3`, and no range widening was needed. **No source change.**

### Verified

- **No interface this plugin binds changed in `0.2.1-alpha.2` or `0.2.1-alpha.3`.** Comparing the published packages of `0.2.1-alpha.1` and `0.2.1-alpha.3` field by field, the seams this plugin binds are identical: `connection.requestRejection`, `webServer.register`/`tapIndex`, the `settings.section` and `shell.overlay` seat contracts, `slots.inject`, `uiSession.sessionStatus`, the `session/event` payloads it reads (`tool/call`, `tool/result` with `message.toolCallId`/`message.isError`, `turn/*`), `jobs.events`, `workflow/end` and `tool-workflow/run-start`. What the range added is additive only: the `plugin:` record namespace, `job/registered`, `job/settled`, `working-directory/change`, and the `connection.operator` → `connection.browserPeer` rename, a member this plugin never reads.
- **A real profile on `0.2.1-alpha.3` admitted this plugin.** The loader disabled the `browser-use` and `browser-use-playwright-mcp` rows and no others, and no exact-version exemption was granted or needed for `@idoall/dsh-notify`.
- **Host half live on `0.2.1-alpha.3`:** `/plugins/dsh-notify/health` answered `guiAvailable: true` with `services.jobs: "reflected"` and `eventErrors: 0`; `/config`, `/sounds` and `/update` (real npm registry: running `0.4.3`, newest `0.4.3`) all answered 200; `/pull` returned six real records from that same day, spanning a completion, a failure and a question still awaiting an answer.
- **Client half live on `0.2.1-alpha.3`:** the page lists `@idoall/dsh-notify/client.js` in its plugin bundle, and the served bundle carries the `settings.section` and `shell.overlay` registrations and the `dsh-notify-*` markup.
- Full verification passed: static source checks, **110 automated tests**, build, and package precondition checks.

## [0.4.3] - 2026-10-06

### Fixed

- **The two settings dropdowns no longer render larger than the labels beside them.** 「通知位置」and「提示音」were the only controls in the section that did not pin their own type size: `.dsh-notify-select` used a bare `font: inherit`, so they followed the host's content font size — `--dsh-content-font-size`, 14px by default and user-settable from 10px to 22px — while every label, hint, toggle, segmented button and action button around them is pinned (13px/12px). The select now states `font-size: 13px` after `font: inherit`, matching its own card, and still inherits the family from the host. No other control was affected: every other `select` in the plugin already sat inside a container that pinned 13px.

### Verified

- `npm run typecheck` gained a static guard: `.dsh-notify-select` must declare its own `font-size`, so the control cannot silently go back to scaling with the host's content font size.
- Full verification passed: static source checks, **110 automated tests**, build, and package precondition checks.
- Counter-proof: removing `font-size: 13px` from the rule fails `npm run typecheck` with `the settings select must pin its own font-size, not inherit the host content font size`; restoring it passes.

## [0.4.2] - 2026-10-06

### Fixed

- **A task that asked for your approval is announced again when it finishes.** Since `0.3.2` a turn that asked an execution approval or a plan review had its completion withheld forever. That suppression assumed the interactive pause ended the turn and the work resumed in a *new* one. DSH never does that: `approval.request()` requires an open turn and awaits its outcome inside it, and `exit_plan_mode` returns its verdict to the same turn, so the turn that resumes is the turn that asked. Replaying every session log on this machine (362 files, 3830 notifiable turns, 81 of them containing an approval or plan review) shows **8 of those 81 turns announced** before this release and **81 of 81** after it, with no extra record anywhere — the suppression removed no duplicate, it only swallowed real completions. A gate now withholds its turn's completion **only while the human still owes an answer**, which is also the safe reading if a turn ever does end on an open gate.

### Changed

- **Declared DeepSeek Harness `0.2.0-rc.2` and `0.2.1-alpha.1` compatible.** `dsh.compatibility.dshReleases` gains `0.2.1-alpha.1` alongside `0.2.0-rc.2`, `0.2.0-rc.1`, `0.1.7-rc.2` and `0.1.7-rc.1`. The requirement itself stays `>=0.1.7-rc.1 <0.3.0`, which already admitted both under DSH's `includePrerelease` evaluation — that is why `0.4.1` was not among the bundles refused at startup, and no range widening was needed.

### Verified

- **No interface this plugin binds changed in `0.2.0-rc.2`.** The Host events (`agent/status`, `session/event`, `approval/request`, `user-questions/request`, `goal/activation-changed`, `agent/error`, `workflow/end`), the `webServer`/`connection` route and authorization contract, the `jobs` event stream, the `slots` seats (`settings.section`, `shell.overlay`), and the `--dsw-alias-*` theme tokens are unchanged; across the packages it binds (`dsh-client-connection`, `dsh-client-ui-session`, `dsh-host-webserver`, `dsh-jobs`, `dsh-client-ui-renderer`, `dsh-client-ui-layout`, `dsh-client-ui-settings`, `dsh-client-ui-settings-general`) the `dsh-v0.2.0-rc.1..dsh-v0.2.0-rc.2` diff is version bumps plus one internal one-line `scoped-slots` change.
- DSH's own loader check (`evaluatePluginCompatibility` from `dsh-app-boot`) accepts this manifest on `0.2.0-rc.2` and still refuses the unchanged `@idoall/dsh-session-colors@0.1.6`, reproducing the startup warning exactly.
- The new `0.2.0-rc.2` reading of `ask_user_question` (an opt-in `mode: 'timed'` row whose foreground wait returns a **pending** result and leaves the question answerable) needs no host change: the tool result settles the record while the turn continues, and the turn's own end is the completion. The default remains the blocking `legacy` tool, which this plugin already handled.
- **No interface this plugin binds changed in `0.2.1-alpha.1` either.** Across `dsh-v0.2.0-rc.2..dsh-v0.2.1-alpha.1` (266 commits) the bound packages differ only by version bumps and the repository-wide removal of the `invariant` companion modules, which no runtime path loads. The one real source change is `ui-layout` adding the **new** `shell.bottom` seat and a `.frame` grid row for it; the `shell.overlay` seat this plugin mounts into, its `.overlayLayer`, and the `[data-conversation-scroll]` anchor the toast measures are untouched.
- **Real-machine verification on `0.2.1-alpha.1`** in an isolated `DSH_HOME` under `/tmp` (the operator's profile is never touched): a profile built from the shipped `web` template with this plugin installed as `link:` booted with **no compatibility refusal**, `/plugins/dsh-notify/health` answered `guiAvailable: true` with `eventErrors: 0`, `/config`, `/pull`, `/sounds` and `/update` (real npm registry) all answered 200, the client entry was composed into `window.__DSH_BOOT__` with its full `inject` list, and its bundle served `200` as a `@idoall/dsh-notify` `__ModuleLoader__.load` registration.
- Full verification passed: static source checks, **110 automated tests**, build, and package precondition checks.

## [0.4.1] - 2026-09-29

### Changed

- **Declared DeepSeek Harness `0.2.0-rc.1` compatible.** `dsh.engines.dsh` and every `@deepseek-ai/dsh-*` peer move from `>=0.1.7-rc.1 <0.2.0` to `>=0.1.7-rc.1 <0.3.0`, and `dsh.compatibility.dshReleases` gains `0.2.0-rc.1` while keeping `0.1.7-rc.1` and `0.1.7-rc.2`. DSH refuses an incompatible bundle at profile load by peer range, so the old ceiling would have dropped the plugin as soon as `0.2.0` shipped — `0.2.0-rc.1` only loads today because a prerelease still sorts below `0.2.0`.

### Verified

- **No source change was needed.** The Host events (`agent/status`, `session/event`, `approval/request`, `user-questions/request`, `goal/activation-changed`, `agent/error`, `workflow/end`), the `webServer`/`connection` route and authorization contract, the `jobs` event stream, the two client `slots` seats, and the `--dsw-alias-*` theme tokens are unchanged in `0.2.0-rc.1`; the packages the plugin binds are byte-identical between `dsh-v0.1.7-rc.2` and `dsh-v0.2.0-rc.1`, and the approval-resolves-inline flow it already handles is the same in both.
- Full verification passed: static source checks, **110 automated tests**, build, and package precondition checks.
- **Real-machine verification** in an isolated `DSH_HOME` under `/tmp` (the user's profile is never touched) with the plugin installed as `link:` and DSH `0.2.0-rc.1` booted: the composed profile mounted the `dsh-notify` row without a compatibility refusal, `/plugins/dsh-notify/health` answered `guiAvailable: true` with `eventErrors: 0`, `/config`, `/pull`, `/sounds` and `/update` (real npm registry) all answered 200, and the browser rendered **设置 → 通知** with a working self-test toast stack and no page errors.
- Counter-proof: under DSH's real semver evaluation the old ranges do **not** satisfy `0.2.0` or `0.2.1`, so the empty-lookup risk on the `0.2.0` release is real rather than assumed.

## [0.4.0] - 2026-09-25

### Added

- **Online version detection in 设置 → 通知.** The settings page now shows the running version against the newest one published on npm, next to **检查更新**, GitHub, 更新日志 and 反馈 Issue — the same row the reference settings page offers. The host reads the registry through the authenticated, read-only `GET /plugins/dsh-notify/update` (six-hour cache; `?force=1` is what the button uses), compares versions with its own ~30 lines of semver arithmetic instead of adding a dependency, and never installs anything or restarts DSH: when a newer version exists the page reveals a copyable `dsh plugin --profile web add @idoall/dsh-notify@<version>` command and states that the restart is the operator's to do. An unreachable or malformed registry renders as `v<current> · 检查失败` — it never presents the running version as the newest, and a failed lookup is cached so an offline host is not asked on every page load.

### Verified

- Version ordering is covered for releases, prereleases (`rc.1` < `rc.2` < `rc.10` < release), build metadata, and an unparsable string that must compare as equal rather than claim an update.
- The route reports the version read from the shipped manifest, reuses the cache on a plain reload, bypasses it on `force=1`, stays behind the auth gate (a refused request never reaches the registry), and answers `registry_unavailable` instead of failing when the registry is down.
- The settings row is covered in jsdom: the chip wording and tone for newest/available/failed, the three repository links, the upgrade panel appearing only when there is something to install, and a clipboard-less browser reporting 复制失败 rather than claiming 已复制.
- Full verification passed: static source checks, **108 automated tests**, build, and package precondition checks.
- **Real-machine verification** in an isolated `DSH_HOME` under `/tmp` (the user's profile is never touched), with the plugin installed as `link:` and DSH `0.1.7-rc.2` booted: `/plugins/dsh-notify/health` answered `guiAvailable: true`, `/plugins/dsh-notify/update` returned `{"current":"0.4.0","latest":"0.3.4","hasUpdate":false,"error":null}` from the real npm registry — an older publish is correctly *not* an update, and `?force=1` produced a second real lookup — and the settings page rendered `v0.4.0 ✓ 最新` with the three repository links and no upgrade panel.

## [0.3.4] - 2026-09-24

### Fixed

- **A failure is announced when DSH reports it.** `agent/error` used to only remember the message for the `turn/end` that was expected to follow, so a failure whose turn never ended was never announced at all. The failure now becomes a card at the error itself, and the `turn/end` that does follow derives a record with the same `fail:<session>:<turn>` identity, so it updates that card rather than adding a second one. A subagent failure stays as quiet as a subagent completion.
- **Work that resumes in the same stack is no longer announced as a finished task.** DSH publishes `agent/status: idle` and resumes queued work in the same synchronous stack. Because every `session/event` is also handled one microtask later, the idle flush found the candidate the preceding `turn/end` had just registered and announced a task that never stopped — the `running` that cancelled it had already run. The flush now re-checks that the session is still `idle` when it runs, so the cancellation wins with no debounce window and no added latency.
- **A card no longer waits for the next tick when the page already knows better.** The client nudges an immediate `/pull` when `sessions.list` or the Session status snapshot changes (leading-edge throttled to one nudge per 200 ms), and catches up on the spot when a hidden tab becomes visible again; the 1.5 s interval stays as the fallback. A late response can no longer move the delivery cursor backwards.

### Verified

- A same-stack `idle -> running` flap emits no completion card, and the turn that really ends last is announced exactly once.
- A failure with no following `turn/end` is delivered on the error itself; a failure whose turn does end stays one card with one identity; a subagent failure stays silent.
- Client coverage counts `/pull` requests: both session-store edges and the visible-again edge pull without the interval running.
- All five behaviour tests were confirmed to fail against the previous implementation before the fix was restored.
- Full verification passed: static source checks, **92 automated tests**, build, and package precondition checks.

## [0.3.3] - 2026-09-24

### Changed

- **Adapted to DeepSeek Harness `0.1.7-rc.1`.** Background-job completions now subscribe to the consolidated `jobs.events` stream (`settled`, unawaited only) instead of the removed `onJobDone` listener. Tool-result settlement reads `message.toolCallId` / `message.isError` on the flattened tool-role message. In-toast answers read the unified `uiSession.sessionStatus` snapshot, where the answerable interaction is nested under `pendingInteraction`.
- **Declared DSH compatibility as a load-time requirement.** `dsh.manifestVersion`, `dsh.engines.dsh`, and every `@deepseek-ai/dsh-*` peer now state `>=0.1.7-rc.1 <0.2.0`, so DSH 0.1.7-rc.1 can refuse an incompatible bundle at profile load. `@deepseek-ai/schemastery` moves from a plain dependency to a peer (with a matching devDependency) so a `link:` install resolves it from the running installation.

### Verified

- Host jobs coverage confirms the 0.1.7 event stream, awaited settlements stay silent, and the pre-0.1.7 `onJobDone` listener remains a fallback.
- Flattened `tool/result` messages close the matching interaction; a nested pre-0.1.7 `tool-result` block still does.
- Client pending-interaction coverage reads both the 0.1.7 Session status snapshot and the older map.
- Live `link:` install on DSH `0.1.7-rc.1` hot-mounted without restarting: Host routes answered, the client bundle registered, **Settings → Notifications** rendered, and a four-tone self-test produced the collapsed overlay stack above the settings modal.
- Full verification passed: static source checks, **86 automated tests**, build, and package precondition checks.

## [0.3.2] - 2026-09-20

### Fixed

- **Completion now follows DSH's native idle state.** A completed Toast is not emitted when a response first appears or after an arbitrary timer. `turn/end` only creates a candidate; the card is delivered when the owning Agent reports `agent/status: idle`, the same state that stops the native session-tree spinner. A new `running` transition cancels an earlier candidate, so goal continuation, queued work, and tool follow-ups cannot announce completion too early or strand a missing notification.
- **One continuous task receives one green completion card.** Plan reviews and execution approvals are control gates, not finished tasks. Their transient idle pause no longer adds a green Toast before the user decides and the task resumes; the later non-interactive work turn is the sole completion delivery. This removes the duplicate same-session “任务完成” cards that could appear seconds apart after approval or plan review.
- **A direct answer no longer loses completion.** Ordinary `ask_user_question` is intentionally distinguished from a plan review or execution approval. When the user answer itself finishes a task and no follow-up turn starts, the native-idle transition still sends exactly one green completion Toast. If work resumes, the pending candidate is cancelled and the final work turn remains the only notification.

### Verified

- Host lifecycle regression coverage confirms a completion is withheld while DSH is native `running` and emitted exactly once only after native `idle`.
- A plan-review pause followed by its approved work emits one completion card, not two.
- An ordinary question whose answer directly finishes work emits one completion card rather than none.
- Full verification passed: static source checks, **84 automated tests**, build, and package precondition checks.

## [0.3.1] - 2026-09-20

### Fixed

- **Sound now matches visible delivery.** Automatic audio plays only when a notification is admitted to an enabled visual Toast queue. A poll batch produces one cue rather than one cue per record, and a rejected card remains eligible for a later delivery attempt.
- **Open interaction recovery.** After the Toast overlay remounts, still-open approvals, questions, and plan reviews are restored silently. Settled history stays suppressed, preventing completed messages from replaying.
- **Approval-colour consistency.** Plan-review notifications now use the same amber approval colour as execution approvals. Ordinary questions remain blue.

### Verified

- A live pending-interaction acceptance confirmed the native pending marker, Toast, sound, Toast navigation, and automatic left-sidebar reveal together; clearing the interaction cleared its pending delivery state.

## [0.3.0] - 2026-09-20

### Added

- **Presentation preferences.** Persisted Enhanced (default) and Soft notification styles, plus a persisted collapsed-stack preference.
- **A preview-first notification self-test.** Send one local Completed, Confirm, Failed, or Info example; replay all four, fold the active examples, or clear them. Tests never affect a real task or send a system notification.
- **Storefront screenshot manifest.** `screenshots.json` explicitly declares the current product screenshots for plugin directories and marketplaces.

### Changed

- **The default multi-toast experience is now a collapsed pile.** The newest card remains fully readable on top; older complete cards expose equal 18px lower edges beneath it. Pointer entry or keyboard focus expands the pile without discarding cards.
- **The notification settings interface was redesigned** around display preferences, sound, task reminders, and visual preview controls. Desktop self-test state buttons are compact in one row; narrow screens reflow them to two columns.
- **README and README.zh are rewritten** to describe the current notification behavior, controls, queue limit, and mobile layout. All screenshots now show the current UI.

### Fixed

- Clicking a notification that opens a session now also reveals the selected session row in the left sidebar, including when it belongs to an off-screen workspace.
- Enhanced local preview examples carry their real state tone rather than rendering as neutral gray.

## [0.2.3] - 2026-09-19 (tagged, not published)

`v0.2.3` was tagged with release-workflow and packaging-maintenance changes, but its npm OIDC trusted-publisher step did not publish `@idoall/dsh-notify@0.2.3`. The latest published npm version remains `0.2.2`; do not treat this tag as an npm release.

### Changed

- `assets/` no longer ships inside the npm tarball; npm renders relative README images from this repository's raw GitHub URLs instead.
- CI moves to `actions/checkout@v7` and `actions/setup-node@v7`.

## [0.2.2] - 2026-09-19

Published to npm as **`@idoall/dsh-notify`**, with a tag-driven release pipeline. Nothing about the plugin's behaviour changes: the cordis name, the mount id, the `/plugins/dsh-notify/*` routes and every CSS class keep their short spelling, so profile config and the test suite are untouched.

### Added

- **npm releases.** The package publishes as `@idoall/dsh-notify`. The unscoped `dsh-notify` name belongs to another author's Windows tray plugin, so the scope is what keeps `dsh plugin add dsh-notify` from pulling the wrong package. `package.json` drops `private` and declares `publishConfig.access: public`.
- **`.github/workflows/release.yml`.** A `v*` tag gates on the `package.json` version, runs `npm run verify`, packs the plugin, publishes through GitHub Actions OIDC (no long-lived token; a version that is already on npm is skipped instead of failing), and creates the GitHub Release with the tarball and its sha256. `release-notes/v<version>.md` supplies the bilingual notes whenever it exists.
- **`.github/dependabot.yml`**: monthly grouped npm maintenance plus GitHub Actions updates.

### Changed

- **`cordis.patch.yml` mounts `@idoall/dsh-notify`** — the Node-resolvable package name — while the mount `id` stays `dsh-notify`.
- **The client bundle registers under `@idoall/dsh-notify`.** `scripts/build.mjs` derives the `__ModuleLoader__.load({id})` id from `package.json` instead of hardcoding it, because DSH identifies a browser module by its manifest package name.
- **`scripts/pack-check.mjs` also asserts the publish preconditions**: `private` unset, public scoped access, `repository.url` matching the GitHub repository, every relative README image present in the repository, and both `cordis.patch.yml` and the client registration id tracking `package.json`'s name.
- Installing from a local clone no longer needs a manual `npm run build`: `npm install` builds through `prepare`, and `prepack` rebuilds before packing or publishing.
- README and README.zh document the scoped install name, the npm badge and the release flow.

## [0.2.1] - 2026-09-18

### Fixed

- One plan review (or question) no longer becomes two toasts. A profile plugin sees both the live `tool/call` (`exit_plan_mode` / `ask_user_question`) and the `user-questions/request` waterfall for the same interaction; those used to be two `mergeKey`s, so the corner showed two 「计划待审」 cards for one session — one with the real question (`Approve this plan and leave plan mode?`) and one with the generic `计划待审：请在页面里查看并批准或拒绝`. Complementary identities now collapse onto the callId, the waterfall body wins over the fallback, and a rekey drops the old buffer copy so a page asking from 0 still sees one card.
- Clicking a card can jump to its session again. Current DSH moved view selection to `uiWorkspace.openSession` and dropped `sessions.open` / `list.current`; `sessions.binding()` is only live after the main view has already retained the session, so requiring it first made every card report 没能打开这个会话. The toast now selects through `openSession` and treats `retainedBy.mainView` as landed (the older `open` + `current` path still works).
- The toast follows the conversation's top-right when a sidebar opens or closes. AppFrame animates `grid-template-columns` on a frame that does not itself resize, so a ResizeObserver on the first `[data-conversation-scroll]` node (or on `body`) never saw the toggle: the stack stayed at the old `inset-inline-end` while the chat expanded. It now re-queries the conversation, watches the grid frame's style and collapsed attributes, and tracks the slow column transition.

## [0.2.0] - 2026-09-18

Notifications are delivered live and nothing is stored: the stack in the top-right corner is the whole feature, so the sidebar bell, the history list and the store behind them are gone.

### Added

- **A toast stack.** Up to five notifications live in the top-right corner, newest on top; each new one takes the first slot and pushes the older ones down, animated by a measured `transform` rather than a re-flow. Cards enter with a spring scale + fade and leave by sliding out to the right.
- **Every card carries its own time.** The arrival time leads the card, to the second (`00:33:08`), because parallel tasks finish seconds apart and "which one was this" is exactly the question a stack of similar cards raises. It shares the metadata row the session name already used, so it costs the card no height and five cards still fit the same window; a record that is not from today falls back to `02-14 09:05`, and the full local date and time always sits in the tooltip. The clock leads rather than trails that row because the `+N` count is pinned to the corner and would otherwise sit on top of it; fixed-width digits keep the clocks in one column either way. The self-test group is spread over the previous half-minute so a burst shows distinct moments instead of eight identical ones.
- **A window, not a pile.** The corner is exactly five cards tall — three on a narrow, touch viewport, where screen space is the scarce thing. A sixth notification takes its place below them, where the window scrolls to it (the wheel, a swipe, or the count button, which jumps between the two ends of the queue); a sliver of the next card under the window edge is what makes the rest look reachable. The count is the whole queue — eight notifications show `+8`, whether five of them are on screen or not — and it is pinned to the corner outside the scroll box, so it neither scrolls away nor means something different once you have scrolled.
- **Action state on the card.** Answering a question or a plan review from a card reports itself: the icon spins while the answer is in flight, the buttons disable, and the card settles on a green check plus 已完成 (then retires on its own) or on a red error with the host message and a 重试 button.
- The settings self-test can fire a window-sized group (**测试 5 条**) and an overflowing one (**测试 8 条**), so what happens past the window — the count, the scroll and the card that waits below the fold — can be looked at without waiting for eight real notifications.
- **`src/buffer.js`**: a bounded in-memory delivery buffer, plus the one thing that does persist — the user own preferences. A page asks `/pull?since=<seq>` for whatever it has not seen, and the first poll of a page is treated as history it was not there for: it is never replayed as a pile of old cards.

### Changed

- **The corner is a window onto a page-scoped queue.** Five cards fit — always including anything waiting on the user, which is ordered first — and everything older keeps its slot below the fold instead of being dropped, one wheel-scroll away. Nothing about the stack depends on the pointer any more, and a card on its way out no longer counts towards the window while it slides away. A refresh still clears everything: nothing is stored, here or on the host.

- **A card leaves when the user says so.** No countdown bar and no clock: a notification stays until it is closed with **×**, opened by clicking the body, or answered successfully from the card itself. A record that arrives again because the host settled it (an approval, asked and then decided) updates its card in place instead of stacking a second one, and retires it.
- **Nothing is stored.** The profile keeps `settings.json` (sound, toast position, subtask noise) and nothing else. Records do not survive a restart, there is no read state anywhere, and no page is brought up to date on work it was not there for.

### Removed

- The sidebar bell with its badge, the pending/history panel, paging, mark-all-read, 已处理 and the batch delete behind **Select…** — along with `/ack`, `/settle`, `/clear`, `/delete`, `/open` and the host self-test suite that existed to exercise them. The plugin registers two seats instead of three.
- `src/storage.js` (394 lines): the epoch/cursor protocol, the change log, capacity eviction, the JSON documents and their schema validation and recovery. Every bug fixed in this release lived in that bookkeeping, and it was the only reason a full store could go silent.

### Fixed

- The corner could paint as a sliver of overlapping card edges before the cards appeared. A batch is created in a single commit, and the cards are measured in that commit's ref callbacks — while the frame is still `display:none`, because a `popover` that has not been shown yet is not laid out. Every card honestly reported a height of 0, and the window was built out of those zeros: five slots twelve pixels apart, clipped to one card-tall sliver. It then stayed that way until an unrelated re-render (the next poll, a resize) measured it again, which is the one-to-two seconds of "top borders with no content". The frame is now promoted to the top layer and re-measured in a layout effect, so the first painted frame is the finished one; the suite fakes the hidden-frame condition (a card height of 0 until the popover is shown) and asserts a measured window in the same flush, and both new tests fail against the old code.
- A burst of notifications trickled in one card per poll. The toast effect served only the *next* un-toasted record, which was right when a poll carried a single event; now that a poll returns everything after a sequence, several tasks finishing together appeared one card every 1.5 seconds. Every record delivered since the last poll is now presented in one pass, oldest first so the newest still lands on top.
- A click that could not go anywhere looked exactly like one that worked: the card slid away and nothing said so, because the card was dismissed *before* the jump was even attempted. It is now dismissed only once the host has really selected the session, and the three ways a click can fail are told apart: a session that is gone (deleted, archived, or absent from this page's list) leaves the card in place reading 这个会话已经不在了，无法打开; a jump that merely failed — no sessions service, a race with a deletion — says 没能打开这个会话，再点一次试试, and clicking the card again retries; and a record with no session of its own says so rather than blaming the navigation. A self-test card still disappears silently, because having no session is what it is for. Verified on the live page by handing it a record whose session does not exist: the card stayed, turned red, and named the reason.
- 工作流结束 notifications can jump to their session. `workflow/end` cannot say which session a run belongs to — the engine emits it from its own unscoped context and the payload is `{id, meta}` — so that kind shipped with no session at all and was the one card that could never be clicked. The owning session is learned from `session/event` instead: `dsh-tool-workflow` appends `tool-workflow/run-start {runId, name}` to the parent Session before the run begins, and every append is delivered there. The lookup is dropped as it is consumed and capped, and nothing depends on which of the two events lands first.
- In a window narrower than 768px the host's own mobile stylesheet (`bridge-mobile-styles`) stretches every input and select inside the settings pane to `width:100%`. That is right for our select and wrong for the checkbox: it took the whole row and pushed its label out of the card. The host's selector (`div[class*=…] input`) outranks a bare `.dsh-notify-toggle input`, so the control's sizing is scoped to the settings section now and pinned three ways (width, max-width and a flex basis) — the tap target still grows to 22px for a coarse pointer, and the label may shrink so a cramped pane wraps instead of overflowing. Measured on the live page at 375px with a fine pointer (18px box, nothing overflows) and on a touch preset (22px box, 44px select, nothing overflows); jsdom resolves stylesheets by source order and ignores rules inside `@media`, so this one has no unit test that could honestly fail.
- The notification settings read as a wall of text, because a card's vertical rhythm never reached its paragraphs. The rhythm was one rule — `.dsh-notify-card>*+*{margin-top:10px}` — which has the same specificity as the `.dsh-notify-hint{margin:0}` reset further down the same stylesheet, and the reset comes later, so it won: labels, selects and button rows kept their 10px while every hint sat flush against the control above it. Measured inside 提示通道, the gaps were 10, 0, 10, 0, 0. The rhythm is now scoped to the settings section so no reset can reach it, a note sits 6px under the control it explains rather than 10px from it, two separate notes and a card's lead sentence keep the full 10px, and the cards use the host's own metrics — `16px 18px` padding, a 12px radius, a 36px select. Five rules that no markup uses any more (the old self-test card strip, the details/summary block and the confirm block) went with it.
- The stack flickered when the pointer crossed the gap between two cards. Hovering used to resize it — a card listening for its own pointer left the stack at the 12px gap, which collapsed it, which slid the cards back under the cursor and expanded it again. The window is now a fixed size and the queue is below the fold, so the pointer cannot move anything; the suite fires every boundary event a browser sends while the pointer crosses the stack and asserts that nothing — height, content, transforms, order — has changed.
- A card could flash and vanish a moment after it appeared. A record already on screen was treated as "the host has settled it" whenever it arrived again, so a host that re-sends what it has already delivered — an older host answering the old cursor protocol, or any duplicate delivery — closed every card on the next poll. Only a record that was **waiting on the user** and has since been settled retires its card now; every other repeat is the same notification and is ignored. The suite covers both a legacy host and a host whose sequence never advances.
- A notification that arrived once the store held its 300-record cap was delivered as a full-snapshot `reset`, and a reset pull is history rather than news — so it never raised a toast, while the background tab kept flashing about it. With no store there is no cap to reach, no epoch to advance and no reset to swallow.

## [0.1.2] - 2026-09-16

The sidebar badge could pin itself at 1 and refuse to go away. A notification left pending by an interrupted interaction is now resolved where it is created, when its session is rebuilt, and when the host starts.

### Fixed

- An interrupted approval (“需要审批” whose turn was stopped before any `approval/decided`) came back to the sidebar badge on every session reload and could not be dismissed: 已处理 cleared it, reopening the session put it straight back. DSH’s `approval/asked` payload carries no `turn`, so restart healing — which only expires a record from a turn that already ended — left every approval alone, and the history replay re-applied `phase: 'open'` on top of a record the user had already settled. An approval now records the turn its `tool/call` belonged to (live and during the rebuild), and a replayed ask never reopens a record that is already settled or expired.
- A pending record inherited from a previous host process is expired at boot, so a leftover in a session that is never reopened can no longer pin the badge across restarts: per-session healing only runs when that session is created again. A cordis re-activation inside a running host is not a boot and never clears a genuinely pending interaction.

## [0.1.1] - 2026-09-15

Notify when the **task** is done, not when a **turn** is done. The sidebar badge now counts only work that still waits on you.

### Added

- Quiet **已处理** action on pending rows, plus batch **标记已处理**. Host `POST /settle` ends pending state (idempotent, same-origin + auth). Distinct from `/ack`, which only means “seen”.
- Plugin config `completionGraceMs` (default `8000`). Set to `0` to restore “notify on every finished turn”.

### Changed

- Sidebar badge and aria-label count `phase: open` only. Finished work never inflates the number.
- History panel tabs are **待处理 / 历史**, matching the badge. Rows still dim once seen; read retention trims the history tab.
- Toast **×** marks every kind as read, including an open question (the composer still holds the answer).
- Tab flash fires for pending work, or for unread that arrived while the tab was hidden — never for a stale backlog.
- A notifiable `turn/end` is deferred: a `turn/start` or `tool/call` inside the grace window cancels it; an engaged goal holds it until `goal/activation-changed` reports the goal is gone.

### Fixed

- One unanswered `open` record no longer starves later toasts.
- Answering a question in the composer (or elsewhere) closes the live toast and marks it read.
- `/ack` is idempotent; a duplicate ack no longer skips the next one.
- Listeners are bounded so a stuck write cannot wedge DSH HTTP, session creation, or other plugins.
- Startup rebuild expires leftovers whose turn already ended; a live question in a turn that has not ended is left alone.
- A settle the host did not accept is reported as a failure, not as success.

## [0.1.0] - 2026-09-14

First public release. Verified against DeepSeek Harness `0.1.5-rc.1`.

### Added

- In-page toast for completed turns, failures, questions, approvals, plan reviews and job/workflow ends: title, body, session name, kind tone, close button, countdown bar.
- Answering questions and approvals straight from the toast, sharing the same pending interaction as the composer.
- Sidebar bell with unread/read history, paging, "mark all read", and batch delete behind a **Select…** trigger.
- Jump to the session **and the exact turn** (`data-chat-turn`), with a brief highlight.
- WebAudio notification sound: synthesised built-ins (chime / ping / alert / silent), uploadable custom sounds with validation, and a preview button that also unlocks the audio context. Plays while the page is hidden.
- Background tab attention indicator: 🔔 title prefix and an alternating favicon dot, stopping when the tab is looked at or nothing is unread.
- Settings section: read retention, toast position, opt-in subtask/background-job notifications, sound, self-test.
- Self-test card plus advanced checks (persisted history, navigation/unread, storage round-trip, deduplication).
- Profile-owned persistence with recovery on corrupt documents and an epoch/cursor protocol for clients.

### Changed

- Read state is immediate in the bell count while the row fades in place; it only moves to the read list when the list is reopened.
- The narrow-layout history panel is capped to its flex container instead of a `dvh` guess, so it no longer overflows the top on phones.
- The history panel is promoted to the browser top layer so a fixed mobile sidebar cannot cover it.

### Removed

- Browser system notifications (channel B), host OS notifications (channel C) and web push / service worker (channel D), along with the `web-push` and `ipaddr.js` dependencies. Those channels failed invisibly (submitted but never seen) and could not be made reliable across browsers and operating systems.

[0.4.1]: https://github.com/idoall/dsh-notify/releases/tag/v0.4.1
[0.4.0]: https://github.com/idoall/dsh-notify/releases/tag/v0.4.0
[0.3.4]: https://github.com/idoall/dsh-notify/releases/tag/v0.3.4
[0.3.3]: https://github.com/idoall/dsh-notify/releases/tag/v0.3.3
[0.3.2]: https://github.com/idoall/dsh-notify/releases/tag/v0.3.2
[0.3.1]: https://github.com/idoall/dsh-notify/releases/tag/v0.3.1
[0.3.0]: https://github.com/idoall/dsh-notify/releases/tag/v0.3.0
[0.2.3]: https://github.com/idoall/dsh-notify/releases/tag/v0.2.3
[0.2.2]: https://github.com/idoall/dsh-notify/releases/tag/v0.2.2
[0.2.1]: https://github.com/idoall/dsh-notify/releases/tag/v0.2.1
[0.2.0]: https://github.com/idoall/dsh-notify/releases/tag/v0.2.0
[0.1.2]: https://github.com/idoall/dsh-notify/releases/tag/v0.1.2
[0.1.1]: https://github.com/idoall/dsh-notify/releases/tag/v0.1.1
[0.1.0]: https://github.com/idoall/dsh-notify/releases/tag/v0.1.0
