@nolit/dsh-notify 0.0.0-stage → 0.0.1-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +388 -0
- package/LICENSE +21 -0
- package/PROVENANCE.md +96 -0
- package/README.md +298 -2
- package/cordis.patch.yml +19 -0
- package/docs/2026-10-20-dsh-notify-design.md +335 -0
- package/docs/2026-10-20-final-status.md +125 -0
- package/docs/2026-10-23-asset-policy.md +170 -0
- package/docs/2026-10-23-deployment-strategy-review.md +530 -0
- package/docs/2026-10-23-headless-nonintrusive-ui-architecture.md +579 -0
- package/docs/2026-10-23-hybrid-dual-mode-design.md +955 -0
- package/docs/2026-10-23-timezone-global-report.md +204 -0
- package/docs/2026-10-23-web-notify-plugin-spec.md +454 -0
- package/docs/2026-10-23-web-plugin-mount.md +111 -0
- package/docs/2026-10-23-windows-widget-poc-report.md +312 -0
- package/docs/2026-10-24-linux-desktop-verification.md +134 -0
- package/docs/BOOT-CARD.md +176 -0
- package/docs/RECOVERY-NOTES.md +111 -0
- package/docs/WORK-PROTOCOL.md +155 -0
- package/docs/deepseek-peak-spec.md +370 -0
- package/docs/dsh-harness-notify-spec.md +149 -0
- package/icon.svg +6 -0
- package/lib/client.js +2172 -0
- package/lib/client.src.js +1963 -0
- package/lib/index.js +13 -0
- package/lib/locale-table.js +215 -0
- package/locale/en.json +104 -0
- package/locale/ko.json +104 -0
- package/market/icons/dsh-notify.svg +6 -0
- package/market/index.json +77 -0
- package/package.json +80 -4
- package/scripts/build-client.mjs +41 -0
- package/scripts/gen-locale.mjs +36 -0
- package/test/client.test.mjs +601 -0
- package/test/locale.test.mjs +114 -0
- package/test/manifest.test.mjs +115 -0
- package/test/peak.test.mjs +333 -0
- package/test/typography.test.mjs +85 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# Changelog — Notify for Chrome (`@nolit/dsh-notify`)
|
|
2
|
+
|
|
3
|
+
<!-- skeleton:
|
|
4
|
+
version: 2.0
|
|
5
|
+
kind: report
|
|
6
|
+
status: active
|
|
7
|
+
updated: 2026-11-06
|
|
8
|
+
summary: Engineering history for Notify for Chrome (formerly DSH Notify Web). Seeded from git history on 2026-11-04; entries after that date record what changed, why, and the measured before/after, per docs/RULES.md 6.1.
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
> History lives here (owner instruction 2026-11-04). Boot cards and `AGENTS.md` are narrowed to
|
|
12
|
+
> the owner's unfinished asks; they are not a changelog. See `docs/RULES.md` §6.1 in the `dsh-plugins` workspace.
|
|
13
|
+
|
|
14
|
+
Current version: `0.0.1-rc.3`
|
|
15
|
+
|
|
16
|
+
## Seeded history (from git, before this file existed)
|
|
17
|
+
|
|
18
|
+
The commits below predate this file. They are the record of what shipped, quoted from the
|
|
19
|
+
repository history rather than rewritten:
|
|
20
|
+
|
|
21
|
+
- `50f50d8` harness bootstrap (timestamped boot cards, policy management)
|
|
22
|
+
- `56300b7` feat(settings): sorted model checklist, persisted sweep, locale-aware tab labels
|
|
23
|
+
- `61c44d8` fix(plugins): native menu typography, grouping and settings panel
|
|
24
|
+
|
|
25
|
+
## Entries after 2026-11-04
|
|
26
|
+
|
|
27
|
+
Add newest first. Each entry: what changed, why, and the measured before/after. A change with no
|
|
28
|
+
entry here is not complete.
|
|
29
|
+
|
|
30
|
+
### 2026-11-06 — `0.0.1-rc.3` — package renamed to `@nolit/dsh-notify`
|
|
31
|
+
|
|
32
|
+
Owner instruction: the npm package name must be `@nolit/dsh-notify`. The `-web`
|
|
33
|
+
suffix was never the owner's choice and the owner was never told the package
|
|
34
|
+
carried it.
|
|
35
|
+
|
|
36
|
+
What the measurement showed, and what had been misreported:
|
|
37
|
+
|
|
38
|
+
| Claim | Reality |
|
|
39
|
+
| :--- | :--- |
|
|
40
|
+
| `CHANGELOG.md` entry for `0.0.1-rc.2` said the package name was **"Kept `@nolit/dsh-notify`"** | The name on disk was `@nolit/dsh-notify-web` in **every commit** since the repository's first commit `50f50d8` (2026-10-29, v0.1.6). `@nolit/dsh-notify` had **never existed** here: `git log -S'@nolit/dsh-notify"'` returns 0 commits. The "Kept" entry was a false record and is corrected in place. |
|
|
41
|
+
| `PROVENANCE.md` documents the package as `@nolit/dsh-notify` | Also never matched disk. The recovered tree was created as `-web`, so the two disagreed from 2026-10-24 onward and nobody reconciled them. |
|
|
42
|
+
| Release artifacts | `nolit-dsh-notify-web-0.1.6/7/8.tgz`, and the `name` inside each tarball was `@nolit/dsh-notify-web`. |
|
|
43
|
+
|
|
44
|
+
Renamed surfaces (before → after):
|
|
45
|
+
|
|
46
|
+
| Surface | Before | After |
|
|
47
|
+
| :--- | :--- | :--- |
|
|
48
|
+
| `package.json` `name` | `@nolit/dsh-notify-web` | **`@nolit/dsh-notify`** |
|
|
49
|
+
| `market/index.json` `plugins[0].name` | `@nolit/dsh-notify-web` | **`@nolit/dsh-notify`** |
|
|
50
|
+
| `cordis.patch.yml` row `name` | `@nolit/dsh-notify-web` | **`@nolit/dsh-notify`** |
|
|
51
|
+
| `lib/client.src.js` module `id` | `@nolit/dsh-notify-web` | **`@nolit/dsh-notify`** |
|
|
52
|
+
| `test/manifest.test.mjs` assertions | `@nolit/dsh-notify-web` | **`@nolit/dsh-notify`** |
|
|
53
|
+
| `scripts/install-notify-live.sh`, `scripts/publish-npm.sh` | `@nolit/dsh-notify-web` | **`@nolit/dsh-notify`** |
|
|
54
|
+
| Docs (track + workspace, 11 files) | `@nolit/dsh-notify-web` | **`@nolit/dsh-notify`** |
|
|
55
|
+
|
|
56
|
+
Deliberately **not** changed: the `cordis.patch.yml` row **`id: dsh-notify-web`**.
|
|
57
|
+
It is a plugin-row identifier, not the package name, and the owner did not ask
|
|
58
|
+
for it. An earlier attempt in this session changed it to `notify`; that was
|
|
59
|
+
outside the instruction and was reverted before committing.
|
|
60
|
+
|
|
61
|
+
Version is unchanged at `0.0.1-rc.3`. Version numbers belong to the owner.
|
|
62
|
+
|
|
63
|
+
Gates after the rename: `npm test` 69/69; `scripts/check-npm-publishable.mjs`
|
|
64
|
+
exit 0; `npm run docs:check` 1,242 links OK.
|
|
65
|
+
|
|
66
|
+
### 2026-11-06 — `0.0.1-rc.3` — published to GitHub (`Nolitor/dsh-notify`)
|
|
67
|
+
|
|
68
|
+
The track had no repository of its own; it lived only as a directory inside the
|
|
69
|
+
`dsh-plugins` superproject, which has no remote. `Nolitor/dsh-notify` existed
|
|
70
|
+
but held the **Windows prototype** with the web plugin nested under `plugin/`
|
|
71
|
+
and an unrelated history (root commit `6f63d06`, no shared objects).
|
|
72
|
+
|
|
73
|
+
Owner decision: **option C** — replace `main` with this plugin, keeping the
|
|
74
|
+
Windows prototype recoverable.
|
|
75
|
+
|
|
76
|
+
Measured before → after:
|
|
77
|
+
|
|
78
|
+
| Surface | Before | After |
|
|
79
|
+
| :--- | :--- | :--- |
|
|
80
|
+
| `Nolitor/dsh-notify` `main` | `aa4dd9c` — Windows prototype at root, plugin in `plugin/` @ `0.2.1` | **`9641ff5`** — this plugin at root @ `0.0.1-rc.3` |
|
|
81
|
+
| History | unrelated to the superproject | 7 commits split from `dsh-notify/` via `git subtree split` |
|
|
82
|
+
| Root tree | `app/ bridge/ build/ core.ps1 ... plugin/` | `CHANGELOG.md LICENSE PROVENANCE.md README.md cordis.patch.yml docs icon.svg lib locale market package.json prototype-windows scripts test` |
|
|
83
|
+
| Windows prototype | at root | preserved under `prototype-windows/` **and** in tag `archive/windows-prototype-v0.2.1` |
|
|
84
|
+
|
|
85
|
+
Safety net: tag `archive/windows-prototype-v0.2.1` was pushed **before** the
|
|
86
|
+
force-push, so every pre-existing file — including `ui/icons/*.ico`, which
|
|
87
|
+
`docs/RECOVERY-NOTES.md` §3 records as unrecoverable from the session logs —
|
|
88
|
+
stays retrievable. The force-push used `--force-with-lease` against the exact
|
|
89
|
+
commit measured (`aa4dd9c`).
|
|
90
|
+
|
|
91
|
+
The split was verified faithful: the split branch tree
|
|
92
|
+
(`391c367f235c11e61badb297e8223cb6c1fe4a3f`) is byte-identical to
|
|
93
|
+
`HEAD:dsh-notify`.
|
|
94
|
+
|
|
95
|
+
Not changed, and why: `docs/` still contains absolute paths (`/home/minami` ×2,
|
|
96
|
+
`C:\Users\nolit` ×16). They sit in measurement and design documents as evidence
|
|
97
|
+
of the defects being reported, so removing them would destroy the record. Runtime
|
|
98
|
+
code (`lib/`, `scripts/`, `locale/`, `market/`) has **zero** absolute paths.
|
|
99
|
+
|
|
100
|
+
### 2026-11-06 — `0.0.1-rc.3` — 7-segment peak countdown chip
|
|
101
|
+
|
|
102
|
+
Owner instruction (translated): *"If the window cannot be force-promoted, strengthen the in-app UI.
|
|
103
|
+
Implement a 7-segment digital countdown floating widget (or fixed chip) inside the DSH screen
|
|
104
|
+
showing DeepSeek off-peak/peak time remaining, reusing the Windows prototype's 7-segment style.
|
|
105
|
+
Green off-peak countdown by default, colour/state change at peak. Background tabs announce the flip
|
|
106
|
+
through the already-verified Chrome notification plus `setAppBadge`."*
|
|
107
|
+
|
|
108
|
+
Owner decisions in the same turn:
|
|
109
|
+
|
|
110
|
+
| # | Decision | Applied as |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| 1 | Makeup workdays are **off-peak** | `treatMakeupAsPeak` does not exist in the browser half; a `isOffDay: false` date returns `{ isPeak: false, reason: 'makeup-workday' }`. The Windows prototype's `true` default is **not** carried over. |
|
|
113
|
+
| 2 | Version `0.0.1.rc3` | `package.json` / `market/index.json` = **`0.0.1-rc.3`** (semver form of the owner's `0.0.1.rc3`). |
|
|
114
|
+
|
|
115
|
+
#### What was added
|
|
116
|
+
|
|
117
|
+
- **Peak engine (pure, testable).** `peakDecision`, `peakNextChange`, `peakTimerText`,
|
|
118
|
+
`peakHolidayIndex`, `beijingDateKey` — a port of the measured Windows provider. Peak windows are
|
|
119
|
+
the fixed UTC definition (01:00–04:00, 06:00–10:00, Mon–Fri, excluding Chinese public holidays);
|
|
120
|
+
holiday matching uses the **Beijing calendar day (UTC+8)** because the published dates are State
|
|
121
|
+
Council local dates.
|
|
122
|
+
- **7-segment chip.** Inline SVG traced from the prototype's geometry (10×18 unit box, 1.9 unit
|
|
123
|
+
round-cap stroke). No font, no `font-family`, no stylesheet; every stroke is a `--dsw-*` token.
|
|
124
|
+
Colour is `--dsw-alias-state-success-primary` off-peak and `--dsw-alias-state-error-primary` at
|
|
125
|
+
peak.
|
|
126
|
+
- **Mount seat.** Registered into `conversation.session.header.utilities` — measured 2026-11-06 as a
|
|
127
|
+
`list`-kind, `replaceRisk: none` seat already rendering three entries, so an additive cell needs no
|
|
128
|
+
priority trick. The chip is `order: 10`.
|
|
129
|
+
- **Transition announcement.** On a state flip it reuses the single notification channel (Chrome
|
|
130
|
+
first, native toast fallback) and sets/clears `setAppBadge` plus a `●` tab-title marker.
|
|
131
|
+
- **Two settings rows**, both defaulted on: *Show peak countdown chip* and *Announce peak changes*.
|
|
132
|
+
They are declared in `cordis.patch.yml` as well, because the manifest contract requires the patch
|
|
133
|
+
to configure exactly what the client normalizes.
|
|
134
|
+
|
|
135
|
+
#### Measured before → after
|
|
136
|
+
|
|
137
|
+
| Surface | Before (rc.2) | After (rc.3) | Evidence |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| Peak awareness | none (0 references) | decision + countdown + announcement | `lib/client.js`, `test/peak.test.mjs` |
|
|
140
|
+
| Destination slot | none | `conversation.session.header.utilities` (list) | `cordis_inspect_query` live occupancy |
|
|
141
|
+
| Settings keys | 8 | 11 | `DEFAULT_SETTINGS`, `cordis.patch.yml` |
|
|
142
|
+
| Locale keys | 74 per language | 99 per language | `locale/en.json`, `locale/ko.json` |
|
|
143
|
+
| Tests | 51 | 69 | `npm test` |
|
|
144
|
+
| Version | `0.0.1-rc.2` | `0.0.1-rc.3` | `package.json`, `market/index.json` |
|
|
145
|
+
|
|
146
|
+
#### Bug fix — route gate (owner report, same session)
|
|
147
|
+
|
|
148
|
+
The chip shipped in this version initially rendered for **every** model route. The owner reported it
|
|
149
|
+
while the session was on `opencode-http-agent`:
|
|
150
|
+
|
|
151
|
+
> "지금 다른 같은 딥시크이지만 공식이 아닌 딥시크 모델인데 저게 뜨는데? … 공식 모델 쓸때만 보여야
|
|
152
|
+
> 할텐데?"
|
|
153
|
+
|
|
154
|
+
The peak/off-peak clock is a property of **DeepSeek's own billing**. A gateway, reseller or proxy
|
|
155
|
+
route has its own price sheet, so the chip was stating a price the user was not paying.
|
|
156
|
+
|
|
157
|
+
**Fix.** The chip, the transition announcement, the tab badge and the title marker now all require an
|
|
158
|
+
official route:
|
|
159
|
+
|
|
160
|
+
| Surface | Gate |
|
|
161
|
+
|---|---|
|
|
162
|
+
| Chip render | `if (!onOfficialRoute) return null` |
|
|
163
|
+
| `setAppBadge` / tab title `●` | only while an official route is active |
|
|
164
|
+
| Transition notification | `if (!isOfficialDeepSeekProvider(...)) return` |
|
|
165
|
+
|
|
166
|
+
Official ids, read from the packages that register them (measured 2026-11-06):
|
|
167
|
+
`dsh-llm-deepseek-api-key` -> **`deepseek-official`**, `dsh-llm-deepseek-account` ->
|
|
168
|
+
**`deepseek-account`**.
|
|
169
|
+
|
|
170
|
+
The live selection is read from `[data-provider-id][data-model-id]` — the attribute pair the model
|
|
171
|
+
selector stamps on its trigger (`dsh-client-ui-model-selection`, `renderModelLabel`, placement
|
|
172
|
+
`trigger`), which is the same read path DeepInfra's cost chip uses. An attribute-filtered
|
|
173
|
+
`MutationObserver` follows route changes; no polling.
|
|
174
|
+
|
|
175
|
+
Live profile note: `llm-deepseek` is `disabled: true` and the default model is
|
|
176
|
+
`opencode-http-agent / opencode-go/deepseek-v4.1-flash`, so **the chip is correctly hidden in the
|
|
177
|
+
owner's current setup** and appears only after switching to an official route.
|
|
178
|
+
|
|
179
|
+
#### Settings restructure + opt-in toggle (owner decision, same session)
|
|
180
|
+
|
|
181
|
+
Owner instruction (translated): *"Add a 'show even when not an official route' toggle as a setting.
|
|
182
|
+
In the Notify for Chrome settings tab, show it as two categories — the DeepSeek peak timer, and
|
|
183
|
+
notifications. Only `deepseek-official` / `deepseek-account` are checked by default; other users'
|
|
184
|
+
ids may differ, so when the option is on, show every other route too; when off, only those two.
|
|
185
|
+
Stay within DSH conventions, and state in the install docs that DeepSeek's discount is declared for
|
|
186
|
+
its own service and other services may differ even for the same model."*
|
|
187
|
+
|
|
188
|
+
| # | Decision | Applied as |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| 1 | Two setting categories | The page now renders four cards: **Chrome status**, **알림 조건**, **딥시크 피크 타이머**, **환경**. The peak rows moved out of the notification card into their own. |
|
|
191
|
+
| 2 | Opt-in toggle for other routes | New setting `peakOnAnyProvider`, **default `false`**, row label *Show timer on other providers*. |
|
|
192
|
+
| 3 | Only official ids by default | `peakAppliesToRoute(providerId, settings)` returns the official check unless the toggle is `true`. Used by the chip, the announcement, and the badge/title marker alike. |
|
|
193
|
+
| 4 | State the caveat in the install docs | README §4 gains *The peak timer only trusts official DeepSeek routes by default*, and the settings card carries the same warning inline. |
|
|
194
|
+
|
|
195
|
+
The caveat as shipped: DeepSeek publishes the off-peak discount for **its own API**; a different
|
|
196
|
+
service serving the same model may bill differently, so the timer is a reference there, not a fact.
|
|
197
|
+
|
|
198
|
+
`peakAppliesToRoute` accepts only a literal `true` for the opt-in, so a stray truthy string from
|
|
199
|
+
`localStorage` cannot silently widen the gate (asserted in `test/peak.test.mjs`).
|
|
200
|
+
|
|
201
|
+
#### Copy + order correction (owner report, same session)
|
|
202
|
+
|
|
203
|
+
Owner instruction (translated): *"Replace it with this wording in settings, and you know it shows
|
|
204
|
+
differently for English and Korean. And swap the peak-timer and notification order."*
|
|
205
|
+
|
|
206
|
+
| # | Change | Applied as |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| 1 | Caveat text replaced with the owner's exact sentence | `locale/ko.json` carries the Korean verbatim; `locale/en.json` carries the English translation. Both are baked by `gen-locale.mjs`, so the two languages stay in sync by construction. |
|
|
209
|
+
| 2 | Peak timer card moved **above** notifications | Card order is now: Chrome status → **DeepSeek peak timer** → notifications → environment. |
|
|
210
|
+
|
|
211
|
+
A defect found while doing this: the **completion-cooldown row** had been left inside the peak card
|
|
212
|
+
when the categories were first split. It belongs to notifications and has been moved back, so the
|
|
213
|
+
peak card now holds only the three peak rows and its caveat.
|
|
214
|
+
|
|
215
|
+
Owner wording, as shipped (ko):
|
|
216
|
+
|
|
217
|
+
> 딥시크는 공식 모델 API에 대해서만 할인을 명시합니다. 같은 모델이라도 다른 제공자는 서비스 요금이
|
|
218
|
+
> 다를 수 있으며 보장하지 않습니다. 그 경우 이 타이머는 참고 사항일 뿐입니다.
|
|
219
|
+
|
|
220
|
+
Verified by rendering the real settings component in Chromium for both locales:
|
|
221
|
+
`[크롬 알림, 딥시크 피크 타이머, 알림 조건, 환경]` and
|
|
222
|
+
`[Chrome notifications, DeepSeek peak timer, When to notify, Environment]`.
|
|
223
|
+
|
|
224
|
+
#### Label clarification — "Platform" was ambiguous (owner question, same session)
|
|
225
|
+
|
|
226
|
+
Owner asked what the environment card's **Platform** row means — *"is it the remote platform or the
|
|
227
|
+
local platform that is currently running?"* — because it showed **Windows** while the DSH host runs
|
|
228
|
+
on Linux.
|
|
229
|
+
|
|
230
|
+
**Measured answer:** it is the **browser's** OS, never the host's.
|
|
231
|
+
|
|
232
|
+
| Evidence | Result |
|
|
233
|
+
|---|---|
|
|
234
|
+
| `uname -srm` on the host | `Linux 7.1.5-... x86_64` (Pop!_OS 24.04) |
|
|
235
|
+
| `detectPlatform` inputs | `documentElement.dataset.platform`, `navigator.userAgentData.platform`, `navigator.platform`, `navigator.userAgent` — all browser-side |
|
|
236
|
+
| Is the host OS exposed to the client? | **No.** No client Service or `@Remote` method returns the host platform (`terminalController.environment` returns terminal geometry only) |
|
|
237
|
+
| Feeding it a real Linux Chrome UA | `{"platform":"linux","installState":"browser"}` — the detector is correct |
|
|
238
|
+
| So "Windows" on a Linux host means | the Chrome tab itself runs on Windows |
|
|
239
|
+
|
|
240
|
+
That is the correct value, because every rule the platform feeds is a **browser** property:
|
|
241
|
+
`requireInteraction` support, Action Center persistence, and the installed-app branch.
|
|
242
|
+
|
|
243
|
+
**Fix (label, not logic).** The row is renamed `Platform` -> **Browser OS** (브라우저 OS) and now
|
|
244
|
+
carries an inline hint: *"The operating system of the machine running this browser tab — not the
|
|
245
|
+
machine running the DSH host. Notification behaviour depends on this one."* The detection code is
|
|
246
|
+
unchanged; it was already right.
|
|
247
|
+
|
|
248
|
+
Also corrected: an earlier `docs/` note implied the platform was the host's. The README now states
|
|
249
|
+
the browser/host split with the measured per-OS behaviour table.
|
|
250
|
+
|
|
251
|
+
#### Browser support + security disclosure (owner instruction, same session)
|
|
252
|
+
|
|
253
|
+
Owner challenge (translated): *"The plugin is Chrome-based, so why care about the OS platform?
|
|
254
|
+
Unless you check the Chrome version... but you should state that operation is not guaranteed outside
|
|
255
|
+
Chrome. And you should document the Chrome security issues too — a popup may not appear because of
|
|
256
|
+
security."*
|
|
257
|
+
|
|
258
|
+
Correct on both counts.
|
|
259
|
+
|
|
260
|
+
**A. OS platform was not added by this session.** `detectPlatform` predates it — `git log -S
|
|
261
|
+
"function detectPlatform"` pins it to **00e4eb9 (2026-11-05)**, and the earlier rename was a label
|
|
262
|
+
fix only. It stays because it is load-bearing: four `notificationOptionsFor` branches key on it
|
|
263
|
+
(Windows installed app vs browser tab, `requireInteraction` support, macOS ignoring it). It is not
|
|
264
|
+
decoration.
|
|
265
|
+
|
|
266
|
+
**B. There was no browser check at all.** Measured: zero `Chrome/`, `Edg/`, `Firefox/`, `Safari/`
|
|
267
|
+
matchers in the shipped code. Feature detection existed (`Notification`, `isSecureContext`,
|
|
268
|
+
`navigator.locks`, `setAppBadge`) but nothing told the user *"this browser is not the target one"*. A
|
|
269
|
+
Firefox tab reported `permission: 'default'` and looked healthy while silently ignoring
|
|
270
|
+
`requireInteraction`.
|
|
271
|
+
|
|
272
|
+
**Added:** `detectBrowser()` → `chromium | firefox | safari | other`, surfaced in a new **Browser
|
|
273
|
+
support** card in settings with a native `Tag` (Chromium = `success`, anything else = `warning`).
|
|
274
|
+
Edge, Opera and Brave are recognised as Chromium because their UAs keep the `Chrome/` token.
|
|
275
|
+
|
|
276
|
+
Note the exact-version check the owner floated is deliberately **not** done: the plugin's real
|
|
277
|
+
minimum is `setAppBadge` (Chrome 81), and a version gate that hides the plugin on a working older
|
|
278
|
+
Chrome would be worse than a documented recommendation.
|
|
279
|
+
|
|
280
|
+
**C. Security constraints documented** (README §6, measured in Chromium 156):
|
|
281
|
+
|
|
282
|
+
| Constraint | Measured behaviour |
|
|
283
|
+
|---|---|
|
|
284
|
+
| permission `default` | `new Notification(...)` **does not throw** — it looks successful and shows nothing. The plugin checks `permission === 'granted'` *before* constructing, so it routes to the toast. |
|
|
285
|
+
| permission `denied` | `requestPermission()` resolves `'denied'` immediately; Chrome never prompts that origin again. Enable button hidden, because a control that cannot work is not rendered as broken. |
|
|
286
|
+
| insecure origin | no OS notifications on `http://<LAN-IP>`; toast fallback. |
|
|
287
|
+
| no user gesture | `requestPermission()` auto-denied; driven from the Enable button only. |
|
|
288
|
+
| OS Do Not Disturb / Focus Assist, site mute | page cannot override or detect. |
|
|
289
|
+
| tab closed | no service-worker backend, no delivery. |
|
|
290
|
+
|
|
291
|
+
**D. Four stale README claims fixed** while checking the above:
|
|
292
|
+
|
|
293
|
+
| Claim | Reality |
|
|
294
|
+
|---|---|
|
|
295
|
+
| "No `fetch`" | the peak timer fetches the holiday list — now disclosed with exactly what is sent (nothing but the request) and how to disable it |
|
|
296
|
+
| privacy list omitted the holiday cache | now lists all three `localStorage` keys |
|
|
297
|
+
| limitations implied notifications degrade transparently | now names the silent-drop cases |
|
|
298
|
+
|
|
299
|
+
#### Verification
|
|
300
|
+
|
|
301
|
+
`npm test` → **69/69 pass**. The new suite re-runs the published spec §5 table and asserts case #7
|
|
302
|
+
now expects `false` (makeup workday off-peak), plus the fixed UTC windows, the Beijing-day boundary,
|
|
303
|
+
malformed-payload rejection, the 8-day next-change scan, `HH:MM:SS` clamping, and that the rendered
|
|
304
|
+
SVG carries token colours only.
|
|
305
|
+
|
|
306
|
+
**Not verified:** a live pixel render inside the DSH GUI. The GUI requires the `dsh web` auth token,
|
|
307
|
+
which is not readable from this session, so the chip has been verified by unit test and by the live
|
|
308
|
+
Slot/Service inspections only.
|
|
309
|
+
|
|
310
|
+
### 2026-11-05 — `0.0.1-rc.2` — Chrome-only rewrite, renamed "Notify for Chrome"
|
|
311
|
+
|
|
312
|
+
Owner request (translated): *"Notify version management also to 0.0.1.rc2, while removing legacy
|
|
313
|
+
things except Chrome notifications; make it follow DSH style and fonts so it does not use anything
|
|
314
|
+
of its own; rename to 'Notify for Chrome' (English default, Korean 알림 for 크롬 when the
|
|
315
|
+
multilingual locale is Korean); the Chrome notification used on Windows should change according to
|
|
316
|
+
the install environment; remove all other legacy system call paths."*
|
|
317
|
+
|
|
318
|
+
Owner decisions applied (given during the session):
|
|
319
|
+
|
|
320
|
+
| # | Decision | Applied as |
|
|
321
|
+
|---|---|---|
|
|
322
|
+
| 1 | Version `0.0.1.rc2` (owner form) | `package.json` version **`0.0.1-rc.2`** — the semver-valid equivalent of the owner-stated `0.0.1.rc2`. The owner form is recorded here verbatim; the semantic version must use the hyphen because npm rejects `0.0.1.rc2`. |
|
|
323
|
+
| 2 | Keep the in-app toast as fallback; remove sound engine / BYO audio / self-authored CSS | Chrome notification is primary; the **native DSH toast** is the silent fallback. Web Audio, the inline beep data URI, `<audio>`, BYO audio upload, IndexedDB audio storage and every sound setting are removed. |
|
|
324
|
+
| 3 | Windows platform detection (option 1): branch only the Chrome-notification parameters, keep the toast | `detectPlatform()` + `detectInstallState()`; `requireInteraction`/`silent`/tag and the permission guidance branch on platform + install mode. Nothing else is Windows-specific. |
|
|
325
|
+
| 4 | Everything visual uses DSH tokens and native settings rows; no own stylesheet | `settings.section` renders native `Switch`/`Button`/`Tag`/`Input`; the fallback renders the native `Toast`; the section icon is the DSH `IconAlarmClockOutlineRegular`; sizes use `--dsh-content-font-*`, colours `--dsw-*`. Zero injected CSS. |
|
|
326
|
+
| 5 | Package name | Recorded at the time as "Kept **`@nolit/dsh-notify`**". **This was wrong and is corrected on 2026-11-06:** the name on disk was `@nolit/dsh-notify-web` throughout, and `@nolit/dsh-notify` had never existed in this repository's history (`git log -S'@nolit/dsh-notify"'` → 0 commits). Only display identity changed: English **Notify for Chrome**, Korean **알림 for 크롬**. The package was renamed to `@nolit/dsh-notify` by owner instruction on 2026-11-06. |
|
|
327
|
+
| 6 | rc.1 compliance (lead) | `engines.node` `>=24` → **`>=24.9`**; cordis peer **`~4.0.6-alpha.1` → `^4.1.0`** (the old range excluded the installed Cordis 4.1.0). |
|
|
328
|
+
|
|
329
|
+
#### Measured before → after
|
|
330
|
+
|
|
331
|
+
Measured with `git show HEAD:dsh-notify/...` for before and the working tree for after
|
|
332
|
+
(2026-11-05, this workspace).
|
|
333
|
+
|
|
334
|
+
| Surface | Before (HEAD, 0.1.8) | After (0.0.1-rc.2) | Evidence |
|
|
335
|
+
|---|---|---|---|
|
|
336
|
+
| Display identity | "DSH Notify Web" / "DSH 알림 (웹)" | "Notify for Chrome" / "알림 for 크롬" | `locale/*.json` `meta.title`, `market/index.json` `title` |
|
|
337
|
+
| Version | `0.1.8` | `0.0.1-rc.2` (owner: `0.0.1.rc2`) | `package.json`, `market/index.json` `versions[0]` |
|
|
338
|
+
| Notification channels | Chrome notification + Web Audio chime + inline WAV beep + in-app toast queue + tab-title marker | Chrome notification **primary**; native DSH toast **silent fallback**; no audio, no title marker | `lib/client.js` |
|
|
339
|
+
| `AudioContext` references | 3 | 0 | `grep -c AudioContext` |
|
|
340
|
+
| `indexedDB` references | 4 | 0 | `grep -c indexedDB` |
|
|
341
|
+
| Sound settings | `soundEnabled`, `soundSource`, `volume` + BYO audio file | removed (7 boolean rows + cooldown) | `cordis.patch.yml`, client settings |
|
|
342
|
+
| Self-authored stylesheet | `NOTIFY_CSS`, 26 `.dshNbx*` rules, injected into `document.head` | 0 bytes of CSS; native primitives + token inline styles | `grep -c 'dshNbx.*{'`, no `document.head` |
|
|
343
|
+
| Hardcoded `font-family` | 1 (`var(--ds-font-family, "Segoe UI", …)`) | 0 | `grep -c fontFamily` |
|
|
344
|
+
| Hardcoded pixel font sizes | 5 (`fontSize: 12/13/14`) | 0; every size is `var(--dsh-content-font-*, Npx)` and every line height `calc(Npx + var(--dsh-content-font-delta*, 0px))` | `test/typography.test.mjs` |
|
|
345
|
+
| Language in code paths | hardcoded Korean (1,009 Hangul chars in `client.js`) | English default; Korean only from the baked dictionary (0 Hangul outside it) | `test/locale.test.mjs` |
|
|
346
|
+
| Windows / install environment | not detected; fixed parameters with hand comments | platform + install-mode detection; parameters branch per environment | `notificationOptionsFor()`, `test/client.test.mjs` |
|
|
347
|
+
| Cordis peer | `~4.0.6-alpha.1` (excludes installed 4.1.0) | `^4.1.0` | `package.json` |
|
|
348
|
+
| `engines.node` | `>=24` | `>=24.9` | `package.json` |
|
|
349
|
+
| Browser-half lines | 1,774 | 1,321 generated (`lib/client.src.js` 1,162 handwritten) | `wc -l` |
|
|
350
|
+
| Tests | 25 (`client` 21 + `typography` 4), no locale/manifest contract | 51 across four files (`client` 25, `typography` 7, `locale` 7, `manifest` 12) | `npm test` |
|
|
351
|
+
|
|
352
|
+
Legacy paths removed from the shipped client: Web Audio synthesis, inline WAV data URI, `<audio>`
|
|
353
|
+
element playback, BYO audio upload/decode/IndexedDB store, volume/source/sound toggles, the
|
|
354
|
+
`<style>` injection, the first-use consent card, the tab-title alert marker, and the hardcoded
|
|
355
|
+
Korean UI strings. The historical `prototype-windows/` tree and `docs/` files are not shipped in the
|
|
356
|
+
package (`package.json` `files`) and are not referenced by the notification code path; they remain
|
|
357
|
+
as project history only.
|
|
358
|
+
|
|
359
|
+
#### Verification
|
|
360
|
+
|
|
361
|
+
Isolated-`DSH_HOME` boot recipe (run 2026-11-05, DSH `0.2.2-rc.1`, Node v24.22.1, Chromium
|
|
362
|
+
1248 headless, Playwright core 1.64.1): a copy of `~/.dsh/profiles/web` under the workspace, with
|
|
363
|
+
the copied patch's session-search `path:` pointed into the copy and a disabled
|
|
364
|
+
`tokeneconomizer-dsh` row; temp home removed afterwards. Live profile untouched.
|
|
365
|
+
|
|
366
|
+
| Check | Measured result |
|
|
367
|
+
|---|---|
|
|
368
|
+
| `npm test` (pretest regenerates locale + client) | **51/51 pass, 0 fail** |
|
|
369
|
+
| `node --check` on `lib/client.js`, `lib/client.src.js`, `scripts/*.mjs` | pass |
|
|
370
|
+
| `lib/client.js` regenerated from `lib/client.src.js` + `locale/*.json` | build fails if the placeholder survives; a test asserts the baked table equals the JSON |
|
|
371
|
+
| Boot, English (locale preference `en`) | **0 page errors**, no `did not activate`; nav label **Notify for Chrome**; section heading **Notify for Chrome**; **7 native `[role="switch"]`** rows with English labels; **0** plugin `<style>` tags |
|
|
372
|
+
| Boot, Korean (locale preference `ko`) | **0 page errors, 0 console errors**; nav label **알림 for 크롬**; **7 native switches** with Korean labels; section text Korean; **0** plugin `<style>` tags |
|
|
373
|
+
| Typography in the real boot | section root computed `font-size: 12px` = `--dsh-content-font-size` (profile sets 12); `font-family` is the host/Korean-pack stack (inherited), never a plugin stack |
|
|
374
|
+
| Chrome-notification path with granted permission | `Notification.permission === 'granted'`; test action returns channel `chrome`, `lastDelivery.osShown === true`, no toast |
|
|
375
|
+
| Fallback path when Chrome cannot show it (permission `default`/blocked) | test action returns channel `fallback`, `osShown === false`, and the **native DSH toast** renders with the localized text (measured `알림 테스트 — 크롬 알림이 정상 동작합니다.` in the Korean boot) |
|
|
376
|
+
| Plugin activation | `window.__dshNotifyChrome` present in both boots (the browser half ran `apply()`) |
|
|
377
|
+
| Live profile | untouched; change is **applied pending restart** (the owner restarts) |
|
|
378
|
+
|
|
379
|
+
#### Follow-ups / notes
|
|
380
|
+
|
|
381
|
+
- Browser notification preferences remain in `localStorage` (version 2), not the Host settings
|
|
382
|
+
service. The plugin's Host half is intentionally a no-op and exposes no settings namespace; the
|
|
383
|
+
preferences are browser-side (permission, tab leadership, toast fallback) and must work before
|
|
384
|
+
any Host form round-trip. Recorded as an assumption; moving them to a Host `Config` + `configForms`
|
|
385
|
+
namespace would add a Host-side schema dependency and a new required client service.
|
|
386
|
+
- The owner's "remove all other legacy system call paths" is satisfied for the shipped plugin; the
|
|
387
|
+
historical Windows prototype is not a runtime path and was left in place after the lead narrowed
|
|
388
|
+
the legacy-removal scope to sound/BYO/self-CSS.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nolit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/PROVENANCE.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# PROVENANCE — dsh-notify track
|
|
2
|
+
|
|
3
|
+
<!-- skeleton:
|
|
4
|
+
version: 1.0
|
|
5
|
+
kind: reference
|
|
6
|
+
status: reference
|
|
7
|
+
updated: 2026-10-24
|
|
8
|
+
summary: Where the dsh-notify source tree came from: recovery path out of historical DSH session logs and what could not be recovered.
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
> Workspace track: `dsh-plugins/dsh-notify` · origin: **DSH Notify** (Windows project) · recovery date: 2026-10-24
|
|
13
|
+
|
|
14
|
+
This directory is **not** a from-scratch reimplementation. It is a physical promotion of a
|
|
15
|
+
Windows-era DSH project that survived only as agent session logs. The original working tree
|
|
16
|
+
(`C:\Users\nolit\Workspace\projects\DSH Notify`) no longer exists; the eight session logs
|
|
17
|
+
below are the only surviving copy, and every file here was reconstructed from those logs (plus
|
|
18
|
+
the staging tree `/tmp/reconstruct/notify`, which was itself replayed from the same logs).
|
|
19
|
+
|
|
20
|
+
## Source sessions (only surviving copy)
|
|
21
|
+
|
|
22
|
+
All eight decoded session logs were read from `/tmp/notify-decoded/*.jsonl` (zstd originals under
|
|
23
|
+
`~/.dsh/sessions/--home-minami-Workspace-projects-DSH~0020Notify--/`):
|
|
24
|
+
|
|
25
|
+
| # | Session id | Wall-clock range (UTC) | Events | Role |
|
|
26
|
+
|---|---|---|---|---|
|
|
27
|
+
| 1 | `session-f9c95dba-191d-4096-94c6-be35c1f8ef4b` | 2026-10-19 16:24 | 5 | empty (header only) |
|
|
28
|
+
| 2 | `session-a3b2bf75-8959-4a8e-8b53-9d4394b898e3` | 2026-10-19 16:31 → 2026-10-22 11:20 | 5,854 | **main build session** (PowerShell prototype) |
|
|
29
|
+
| 3 | `session-58a2dc78-0e98-49ac-8f3a-beec8a2997c6` | 2026-10-19 16:30 → 2026-10-20 03:44 | 13 | empty (no tool calls) |
|
|
30
|
+
| 4 | `session-c7a8072b-507d-4679-833c-3952f3b041c6` | 2026-10-20 04:08 | 5 | empty (header only) |
|
|
31
|
+
| 5 | `session-318f99bb-f0dd-462a-8540-3cb7d3694e2d` | 2026-10-20 04:08 | 5 | empty (header only) |
|
|
32
|
+
| 6 | `session-21197fd9-a032-4cbd-b5b5-cd0882ce3fe9` | 2026-10-20 04:09 | 5 | empty (header only) |
|
|
33
|
+
| 7 | `session-95a9341d-a091-4a4b-879d-899cb432b9cc` | 2026-10-22 18:23 → 2026-10-23 00:49 | 12 | empty (no tool calls) |
|
|
34
|
+
| 8 | `session-b53400d8-41db-4a3b-b611-a14f43e62916` | 2026-10-22 18:25 → 2026-10-24 01:28 | 6,044 | **final session** (DSH web plugin half) |
|
|
35
|
+
|
|
36
|
+
Only sessions 2 and 8 contain recoverable work; the other six carry session headers and commands
|
|
37
|
+
but no file-mutating tool calls.
|
|
38
|
+
|
|
39
|
+
## Recovery method
|
|
40
|
+
|
|
41
|
+
1. **Decode** all eight `.jsonl` session logs (one event per line, DSH session format v4).
|
|
42
|
+
2. **Replay** every `write`/`edit` tool call in wall-clock order onto a staging tree
|
|
43
|
+
(`/tmp/reconstruct/notify`), which is the baseline copied here.
|
|
44
|
+
3. **Fill gaps** that replay cannot produce — files created before logging began or mutated
|
|
45
|
+
through shell here-strings rather than `write`/`edit`:
|
|
46
|
+
- `docs/BOOT-CARD.md`, `docs/2026-10-20-dsh-notify-design.md`,
|
|
47
|
+
`docs/deepseek-peak-spec.md`, `README.md`, `.gitignore` — restored from full-file
|
|
48
|
+
`read` tool results, then replayed through every later logged `edit` and every
|
|
49
|
+
verifiable in-place PowerShell mutation.
|
|
50
|
+
- `market/icons/dsh-notify.svg` — the session log shows it was created with
|
|
51
|
+
`Copy-Item plugin/icon.svg → plugin/market/icons/dsh-notify.svg`; recreated as that copy.
|
|
52
|
+
- `LICENSE` — captured verbatim; the `plugin/LICENSE` copy is the same MIT text.
|
|
53
|
+
- `ui/assets/digital-7-license.txt` — recovered from a full read; the font binary itself was
|
|
54
|
+
never in the logs (see `docs/RECOVERY-NOTES.md`).
|
|
55
|
+
4. **Verify**: every reconstructed doc was checked against every later `read` snapshot in the
|
|
56
|
+
sessions (0 line mismatches); all JSON/JS files are syntax-checked (see validation in
|
|
57
|
+
`docs/RECOVERY-NOTES.md`).
|
|
58
|
+
5. **Promote** the plugin half to this track root; the Windows half is preserved under
|
|
59
|
+
`prototype-windows/` with its original relative structure.
|
|
60
|
+
|
|
61
|
+
## What is Windows-only vs platform-neutral
|
|
62
|
+
|
|
63
|
+
**Windows-only (prototype-windows/)** — a PowerShell 7 + WPF resident desktop prototype:
|
|
64
|
+
- Requires Windows 10/11, `pwsh.exe`, .NET/WPF (`PresentationFramework`), Windows Forms tray,
|
|
65
|
+
`wscript.exe`/JScript, `start.cmd`, registry-free Startup shortcut. It **cannot run on Linux**
|
|
66
|
+
and no PowerShell/JScript execution was attempted here.
|
|
67
|
+
- It also shipped a DSH *host plugin* bridge (`bridge/index.js`, a Node ESM cordis plugin) that
|
|
68
|
+
forwarded DSH events to the Windows popup app via a JSON file. That bridge is part of the
|
|
69
|
+
Windows half because its consumer is the PowerShell app.
|
|
70
|
+
- Binary assets that were generated or fetched at runtime are not recoverable from logs
|
|
71
|
+
(fonts, ICOs, PNG previews, ding.wav); see `docs/RECOVERY-NOTES.md`.
|
|
72
|
+
|
|
73
|
+
**Platform-neutral (this package root)** — `@nolit/dsh-notify` is a browser-only DSH web
|
|
74
|
+
plugin: `lib/index.js` is a no-op Host half, all behavior is in `lib/client.js` (Web
|
|
75
|
+
Notification + Web Audio + `localStorage`/`IndexedDB`, no ports, no IPC, no filesystem, no
|
|
76
|
+
bundled audio/fonts/icons). This half is usable on Linux/macOS/Windows in any Chromium-class
|
|
77
|
+
browser and was validated here with Node.js (JSON/ESM parse + `node --test`).
|
|
78
|
+
|
|
79
|
+
## Layout of this track
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
dsh-notify/
|
|
83
|
+
package.json # @nolit/dsh-notify (dsh.manifestVersion 1, dsh.client.platform web)
|
|
84
|
+
cordis.patch.yml # bundle layer that activates the no-op host half
|
|
85
|
+
lib/index.js # no-op host half
|
|
86
|
+
lib/client.js # browser half (notifications, settings section, overlay)
|
|
87
|
+
locale/{en,ko}.json # localization metadata
|
|
88
|
+
market/index.json # market catalog entry (+ market/icons/dsh-notify.svg)
|
|
89
|
+
icon.svg # neutral clock icon (no trademark)
|
|
90
|
+
test/client.test.mjs # browser-API mock tests (node --test)
|
|
91
|
+
README.md # plugin docs (recovered verbatim)
|
|
92
|
+
LICENSE # MIT, Copyright (c) 2026 nolit
|
|
93
|
+
prototype-windows/ # recovered Windows PowerShell/WPF prototype (not runnable on Linux)
|
|
94
|
+
docs/ # recovered project docs (promoted to track root per workspace layout)
|
|
95
|
+
PROVENANCE.md # this file
|
|
96
|
+
```
|