@nolit/dsh-notify 0.0.1-rc.3 → 0.0.1-rc.4

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 CHANGED
@@ -11,7 +11,7 @@ summary: Engineering history for Notify for Chrome (formerly DSH Notify Web). Se
11
11
  > History lives here (owner instruction 2026-11-04). Boot cards and `AGENTS.md` are narrowed to
12
12
  > the owner's unfinished asks; they are not a changelog. See `docs/RULES.md` §6.1 in the `dsh-plugins` workspace.
13
13
 
14
- Current version: `0.0.1-rc.3`
14
+ Current version: `0.0.1-rc.4`
15
15
 
16
16
  ## Seeded history (from git, before this file existed)
17
17
 
@@ -97,6 +97,32 @@ Not changed, and why: `docs/` still contains absolute paths (`/home/minami` ×2,
97
97
  of the defects being reported, so removing them would destroy the record. Runtime
98
98
  code (`lib/`, `scripts/`, `locale/`, `market/`) has **zero** absolute paths.
99
99
 
100
+ ### 2026-11-07 — `0.0.1-rc.4` — the chip actually renders
101
+
102
+ Owner decision (translated): *"The rename back to the original name is settled, and so is the
103
+ version. Stop treating the older versions as live: the current state is canonical and the path
104
+ changed. The only part you may change is the number after `rc`, so make it `rc.4` and publish
105
+ through the deploy harness."*
106
+
107
+ `0.0.1-rc.3` was published to npm and GitHub with a defect that made the peak chip dead. This
108
+ release is the corrected build.
109
+
110
+ | Surface | `0.0.1-rc.3` (published) | `0.0.1-rc.4` |
111
+ | :--- | :--- | :--- |
112
+ | Peak chip | slot registered, **never painted** (`ReferenceError: doc is not defined` on every render) | renders |
113
+ | Engine id | `0.0.1-rc.3` | `0.0.1-rc.4` |
114
+
115
+ **Root cause.** `makePeakChip` is a UI adapter outside `createNotifyCore`, so the core's `doc`
116
+ binding was out of scope. Registration succeeded, which is why the failure was invisible: the slot
117
+ simply rendered nothing. Fixed by resolving the document from the browser global inside the
118
+ component. Full detail is in the 2026-11-07 entry below.
119
+
120
+ **Verification.** `npm test` 70/70, including a new regression test that renders the registered
121
+ component and asserts it does not throw. `0.0.1-rc.3` is superseded; it is not a valid install
122
+ target.
123
+
124
+ Owner-stated version form: **`0.0.1.rc4`**.
125
+
100
126
  ### 2026-11-06 — `0.0.1-rc.3` — 7-segment peak countdown chip
101
127
 
102
128
  Owner instruction (translated): *"If the window cannot be force-promoted, strengthen the in-app UI.
@@ -140,7 +166,7 @@ Owner decisions in the same turn:
140
166
  | Destination slot | none | `conversation.session.header.utilities` (list) | `cordis_inspect_query` live occupancy |
141
167
  | Settings keys | 8 | 11 | `DEFAULT_SETTINGS`, `cordis.patch.yml` |
142
168
  | Locale keys | 74 per language | 99 per language | `locale/en.json`, `locale/ko.json` |
143
- | Tests | 51 | 69 | `npm test` |
169
+ | Tests | 51 | 70 | `npm test` |
144
170
  | Version | `0.0.1-rc.2` | `0.0.1-rc.3` | `package.json`, `market/index.json` |
145
171
 
146
172
  #### Bug fix — route gate (owner report, same session)
@@ -296,9 +322,40 @@ Chrome would be worse than a documented recommendation.
296
322
  | privacy list omitted the holiday cache | now lists all three `localStorage` keys |
297
323
  | limitations implied notifications degrade transparently | now names the silent-drop cases |
298
324
 
325
+ #### Bug fix — the chip never rendered (`ReferenceError: doc is not defined`)
326
+
327
+ Owner report: *"why is your plugin not working?"* Measured after the DeepSeek sign-in was repaired.
328
+
329
+ The plugin was mounted correctly (`--dump-config` shows the `notify` row with every setting), the
330
+ build was current, and the slot registered. The chip still never appeared.
331
+
332
+ **Cause.** `makePeakChip` is a UI adapter that lives *outside* `createNotifyCore`. The `doc`
333
+ binding it referenced belongs to the core closure (line 456), so it was out of scope in the
334
+ component. Every render threw:
335
+
336
+ ```
337
+ ReferenceError: doc is not defined
338
+ ```
339
+
340
+ Registration succeeded, so nothing looked broken from the outside — the slot simply painted
341
+ nothing. **Registering is not rendering**, and the post-rc.3 rename (`@nolit/dsh-notify-web` ->
342
+ `@nolit/dsh-notify`, commit `eabc41e`) carried the defect forward unchanged.
343
+
344
+ **Fix.** The component resolves the document from the browser global itself
345
+ (`typeof document === 'undefined' ? undefined : document`), which is correct for a UI adapter, and
346
+ passes it to `readActiveProvider` / `watchActiveProvider`. The three core-side call sites
347
+ (`peakAnnounce`, `syncBadge`) were already inside the closure and are unchanged.
348
+
349
+ **Regression test added** so this cannot recur silently: it runs `apply()`, takes the component out
350
+ of the registered slot, calls it, and asserts it does not throw. The previous suite asserted *which
351
+ slot registered*, never that the component survives a render — which is exactly why the bug passed
352
+ 69 tests.
353
+
354
+ `npm test` -> **70/70 pass**.
355
+
299
356
  #### Verification
300
357
 
301
- `npm test` → **69/69 pass**. The new suite re-runs the published spec §5 table and asserts case #7
358
+ `npm test` → **70/70 pass**. The new suite re-runs the published spec §5 table and asserts case #7
302
359
  now expects `false` (makeup workday off-peak), plus the fixed UTC windows, the Beijing-day boundary,
303
360
  malformed-payload rejection, the 8-day next-change scan, `HH:MM:SS` clamping, and that the rendered
304
361
  SVG carries token colours only.
package/README.md CHANGED
@@ -8,13 +8,39 @@ updated: 2026-11-05
8
8
  summary: Operator manual and behavior contract for Notify for Chrome (Korean: 알림 for 크롬) — Chrome/Web Notification only, silent in-app toast fallback, native DSH settings rows and typography, Windows/install-mode notification branches.
9
9
  -->
10
10
 
11
- > Status: active · Updated: 2026-11-06 · Package: `@nolit/dsh-notify` v`0.0.1-rc.3` · Korean display name: **알림 for 크롬** · Parent: `README.md` in the `dsh-plugins` workspace
11
+ > Status: active · Updated: 2026-11-07 · Package: `@nolit/dsh-notify` v`0.0.1-rc.4` · Korean display name: **알림 for 크롬** · Parent: `README.md` in the `dsh-plugins` workspace
12
12
 
13
13
  **Notify for Chrome** turns DeepSeek Harness events into **Chrome notifications** (the Web
14
14
  Notification API, `new Notification(...)`). It has no native app, no ports, no IPC, no file queue
15
15
  and no system notifier. When Chrome cannot show a notification, the plugin falls back to a
16
16
  **silent in-app toast**.
17
17
 
18
+ ## 0. Version numbering — why `0.0.1-rc.4` is not lower than `0.2.1`
19
+
20
+ The version number went **down** against this project's own history, on purpose. Two separate
21
+ facts explain it, and until 2026-11-06 neither was written down anywhere.
22
+
23
+ **`0.2.x` was never this plugin's version.** The `v0.2.0` and `v0.2.1` releases belonged to the
24
+ **Windows PowerShell/WPF prototype** (a tray widget polling the DeepSeek billing window). The
25
+ browser plugin lived inside that repository under `plugin/` and happened to carry the same
26
+ number — by accident, not by design.
27
+
28
+ **The `@nolit` ladder was reset by owner decision on 2026-11-05.** Every `@nolit` package moved
29
+ to one scheme, `0.0.1-rc.N`, in which only the `rc#` moves. `0.1.8` and `0.2.1` are not
30
+ milestones under that scheme; the scheme replaced them.
31
+
32
+ | Surface | Before | After |
33
+ | :--- | :--- | :--- |
34
+ | Version scheme | per-track, drifting (`0.1.6 → 0.1.7 → 0.1.8 → 0.2.1`) | **`0.0.1-rc.N`** |
35
+ | Package | `@nolit/dsh-notify-web` @ `0.2.1`, nested in `plugin/` | **`@nolit/dsh-notify`** @ `0.0.1-rc.4`, repository root |
36
+ | Repository | Windows prototype at root | plugin at root, prototype preserved in `prototype-windows/` |
37
+
38
+ The `-web` suffix came from the 2026-10-24 recovery and was never the owner's choice;
39
+ `PROVENANCE.md` documented the package as `@nolit/dsh-notify` the entire time while the files
40
+ said `-web`. The rename landed in `0.0.1-rc.3`; `0.0.1-rc.4` is the corrected build.
41
+
42
+ Version numbers are the owner's decision. An agent must not choose, bump or "stabilise" one.
43
+
18
44
  ## 1. Behavior
19
45
 
20
46
  | DSH event | Channel |
@@ -284,8 +310,15 @@ typography token contract, and the absence of every removed legacy path.
284
310
 
285
311
  ---
286
312
 
313
+
287
314
  ## 한국어 요약
288
315
 
316
+ **버전이 내려간 이유부터**: `0.0.1-rc.4`는 `0.2.1`보다 낮은 것이 아닙니다. `0.2.x`는 이
317
+ 플러그인의 버전이 아니라 같은 저장소에 있던 **Windows 프로토타입**의 버전이었고, 2026-11-05
318
+ 소유자 결정으로 모든 `@nolit` 패키지가 **`0.0.1-rc.N`** 단일 사다리로 재설정됐습니다.
319
+ `rc#`만 올라갑니다. 패키지명도 `@nolit/dsh-notify-web` → **`@nolit/dsh-notify`** 로
320
+ 바뀌었습니다. `-web`은 2026-10-24 복구 당시 붙은 것으로 소유자가 고른 이름이 아니었습니다.
321
+
289
322
  **알림 for 크롬**은 DeepSeek Harness의 턴 완료·도구 승인·질문·오류를 **크롬 알림**(Web
290
323
  Notification API)으로 알립니다. 크롬이 알림을 막거나 표시할 수 없으면 **소리 없이 DSH 인앱
291
324
  토스트**로 대신 알립니다. 자체 오디오 엔진·BYO 오디오·자체 CSS·네이티브 앱/포트/파일 경로는
package/lib/client.js CHANGED
@@ -1899,13 +1899,16 @@ window.__ModuleLoader__.load({
1899
1899
  // Route gate: the peak/off-peak clock only describes DeepSeek's own
1900
1900
  // billing. Read at mount (the composer may already carry the attribute)
1901
1901
  // and follow every selection change.
1902
+ // This component is a UI adapter outside createNotifyCore, so the core's
1903
+ // `doc` binding is not in scope here. Read the browser document directly.
1904
+ const docRef = typeof document === 'undefined' ? undefined : document
1902
1905
  const [activeProvider, setActiveProvider] = React.useState(() =>
1903
- readActiveProvider(doc),
1906
+ readActiveProvider(docRef),
1904
1907
  )
1905
1908
  React.useEffect(() => {
1906
- const read = () => setActiveProvider(readActiveProvider(doc))
1909
+ const read = () => setActiveProvider(readActiveProvider(docRef))
1907
1910
  read()
1908
- return watchActiveProvider(doc, read)
1911
+ return watchActiveProvider(docRef, read)
1909
1912
  }, [])
1910
1913
 
1911
1914
  if (!settings.showPeakChip) return null
package/lib/client.src.js CHANGED
@@ -1690,13 +1690,16 @@ window.__ModuleLoader__.load({
1690
1690
  // Route gate: the peak/off-peak clock only describes DeepSeek's own
1691
1691
  // billing. Read at mount (the composer may already carry the attribute)
1692
1692
  // and follow every selection change.
1693
+ // This component is a UI adapter outside createNotifyCore, so the core's
1694
+ // `doc` binding is not in scope here. Read the browser document directly.
1695
+ const docRef = typeof document === 'undefined' ? undefined : document
1693
1696
  const [activeProvider, setActiveProvider] = React.useState(() =>
1694
- readActiveProvider(doc),
1697
+ readActiveProvider(docRef),
1695
1698
  )
1696
1699
  React.useEffect(() => {
1697
- const read = () => setActiveProvider(readActiveProvider(doc))
1700
+ const read = () => setActiveProvider(readActiveProvider(docRef))
1698
1701
  read()
1699
- return watchActiveProvider(doc, read)
1702
+ return watchActiveProvider(docRef, read)
1700
1703
  }, [])
1701
1704
 
1702
1705
  if (!settings.showPeakChip) return null
package/market/index.json CHANGED
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "versions": [
65
65
  {
66
- "version": "0.0.1-rc.3",
66
+ "version": "0.0.1-rc.4",
67
67
  "source": {
68
68
  "type": "npm"
69
69
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolit/dsh-notify",
3
- "version": "0.0.1-rc.3",
3
+ "version": "0.0.1-rc.4",
4
4
  "description": "Notify for Chrome: Chrome notification alerts for DeepSeek Harness turns, approvals, questions and errors, with a silent in-app toast fallback.",
5
5
  "license": "MIT",
6
6
  "author": "nolit",
@@ -4,7 +4,7 @@
4
4
  * Owner decisions recorded here:
5
5
  * - the npm package name stays @nolit/dsh-notify (renaming it would break
6
6
  * the live profile's `link:` dependency); only the display identity changed;
7
- * - the owner's version form 0.0.1.rc3 maps to the semver form 0.0.1-rc.3;
7
+ * - the owner's version form 0.0.1.rc4 maps to the semver form 0.0.1-rc.4;
8
8
  * - DSH 0.2.2-rc.1 installs Cordis 4.1.0 and requires Node >= 24.9.
9
9
  */
10
10
  import assert from 'node:assert/strict'
@@ -26,11 +26,11 @@ test('package name stays @nolit/dsh-notify; display identity is Notify for Chrom
26
26
  assert.match(pkg.description, /Notify for Chrome/)
27
27
  })
28
28
 
29
- test('version is the owner-declared 0.0.1-rc.3 (owner form 0.0.1.rc3)', () => {
30
- assert.equal(pkg.version, '0.0.1-rc.3')
29
+ test('version is the owner-declared 0.0.1-rc.4 (owner form 0.0.1.rc4)', () => {
30
+ assert.equal(pkg.version, '0.0.1-rc.4')
31
31
  assert.match(pkg.version, /^\d+\.\d+\.\d+-rc\.\d+$/)
32
32
  assert.equal(market.plugins[0].versions[0].version, pkg.version)
33
- assert.ok(read('CHANGELOG.md').includes('0.0.1.rc3'), 'CHANGELOG must record the owner-stated form')
33
+ assert.ok(read('CHANGELOG.md').includes('0.0.1.rc4'), 'CHANGELOG must record the owner-stated form')
34
34
  })
35
35
 
36
36
  test('cordis peer range admits the installed Cordis 4.1.0', () => {
@@ -331,3 +331,40 @@ test('the setting defaults off in the shipped defaults and the bundle patch', ()
331
331
  const patch = readFileSync(new URL('../cordis.patch.yml', import.meta.url), 'utf8')
332
332
  assert.match(patch, /^\s+peakOnAnyProvider: false$/m)
333
333
  })
334
+
335
+ // ---------------------------------------------------------------------------
336
+ // Regression: the chip must actually RENDER, not just register.
337
+ //
338
+ // The chip is a UI adapter outside createNotifyCore, so the core's `doc` binding
339
+ // is not in its scope. An earlier revision referenced it anyway, which threw
340
+ // `ReferenceError: doc is not defined` on every render and made the slot appear
341
+ // registered while nothing ever painted. Registering is not rendering.
342
+ // ---------------------------------------------------------------------------
343
+
344
+ test('the registered chip component renders without a ReferenceError', () => {
345
+ const slots = {}
346
+ const ctx = {
347
+ locale: { getSnapshot: () => ({ active: 'ko' }), subscribe: () => () => {} },
348
+ logger: { warn() {}, log() {} },
349
+ remote: { $on: () => () => {} },
350
+ sessions: { list: { getSnapshot: () => ({ byId: {} }) }, scopeOf: () => undefined },
351
+ slots: {
352
+ inject: (key, cb) => {
353
+ const d = cb()
354
+ return typeof d === 'function' ? d : () => {}
355
+ },
356
+ register: (meta, component) => {
357
+ slots[meta.name] = component
358
+ return () => {}
359
+ },
360
+ },
361
+ effect: (fn) => {
362
+ const d = fn()
363
+ return typeof d === 'function' ? d : () => {}
364
+ },
365
+ }
366
+ mod.apply(ctx, {})
367
+ const Chip = slots['conversation.session.header.utilities']
368
+ assert.ok(Chip, 'the chip must register into the header utilities slot')
369
+ assert.doesNotThrow(() => Chip({}), 'rendering the chip must not throw')
370
+ })