@goodandready/dsh-plugin-notify 0.4.0 → 0.4.1
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 +9 -0
- package/lib/client.js +10 -11
- package/lib/index.js +36 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.1] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Settings registration namespace & Desktop crash protection** (#55): Use canonical `ENTRY_ID = 'plugin-notify'` instead of scoped package name in `settings.register`; wrap call with `try / catch` logging warning instead of throwing `TypeError` in DSH Desktop.
|
|
11
|
+
- **Settings form slot binding** (#54): Bind UI `settings.plugin.item` slot `key` to `ROW_ID` (`'plugin-notify'`), and prioritize `ROW_ID` in `configForms` candidates over legacy `NS`.
|
|
12
|
+
- **Dynamic settings observation** (#48): Make describe settings fallback reactive: observe `loader/volatile-update`, `settings/document-updated`, and `settings/updated` to dynamically refresh settings when edited in UI or external config.
|
|
13
|
+
- **Config unboxing guarantee** (#47): Guarantee `getConfig()` always unwraps volatile boxes via `unwrapConfig(config ?? {})`, preventing `TypeError` during `session/event` event unboxing.
|
|
14
|
+
- **DSH 0.2.0-rc.1 clean activation & volatile schema** (#42): Guarantee clean activation on DSH 0.2.0-rc.1 when `settings.register` is absent; confirm all user-facing fields in `Config` have `.volatile()` markers.
|
|
15
|
+
|
|
7
16
|
## [0.4.0] - 2026-10-05
|
|
8
17
|
|
|
9
18
|
### Added
|
package/lib/client.js
CHANGED
|
@@ -675,11 +675,10 @@ window.__ModuleLoader__.load({
|
|
|
675
675
|
}
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
() => (ctx && ctx.configForms ? [ctx.configForms.get(NS), ctx.configForms.get(ROW_ID)] : [undefined, undefined]),
|
|
678
|
+
// In DSH 0.2.x configForms is keyed strictly by entry id (ROW_ID = 'plugin-notify').
|
|
679
|
+
// Hold both candidates and prefer ROW_ID whenever it is ready or NS is unavailable.
|
|
680
|
+
const [scopeRow, scopeNS] = React.useMemo(
|
|
681
|
+
() => (ctx && ctx.configForms ? [ctx.configForms.get(ROW_ID), ctx.configForms.get(NS)] : [undefined, undefined]),
|
|
683
682
|
[ctx],
|
|
684
683
|
)
|
|
685
684
|
const useSnap = (scope) => React.useSyncExternalStore(
|
|
@@ -687,11 +686,11 @@ window.__ModuleLoader__.load({
|
|
|
687
686
|
React.useCallback(() => (scope ? scope.getSnapshot() : { status: 'loading' }), [scope]),
|
|
688
687
|
React.useCallback(() => ({ status: 'loading' }), []),
|
|
689
688
|
)
|
|
690
|
-
const snapNS = useSnap(scopeNS)
|
|
691
689
|
const snapRow = useSnap(scopeRow)
|
|
692
|
-
const
|
|
693
|
-
const
|
|
694
|
-
const
|
|
690
|
+
const snapNS = useSnap(scopeNS)
|
|
691
|
+
const useRow = scopeRow && (snapRow.status === 'ready' || snapNS.status !== 'ready')
|
|
692
|
+
const scope = useRow ? scopeRow : (scopeNS || scopeRow)
|
|
693
|
+
const snapshot = useRow ? snapRow : (scopeNS ? snapNS : snapRow)
|
|
695
694
|
|
|
696
695
|
React.useEffect(() => { ensureStyles() }, [])
|
|
697
696
|
|
|
@@ -905,7 +904,7 @@ window.__ModuleLoader__.load({
|
|
|
905
904
|
})
|
|
906
905
|
ctx.slots.inject('settings.plugin.item', () => {
|
|
907
906
|
ctx.slots.register(
|
|
908
|
-
{ name: 'settings.plugin.item', key:
|
|
907
|
+
{ name: 'settings.plugin.item', key: ROW_ID, locale: NS, order: 40, inject: () => ({ ctx }) },
|
|
909
908
|
(props) => React.createElement(NotifyCard, { ...props, ctx }),
|
|
910
909
|
)
|
|
911
910
|
})
|
|
@@ -916,7 +915,7 @@ window.__ModuleLoader__.load({
|
|
|
916
915
|
let currentSettings = {}
|
|
917
916
|
if (ctx.configForms) {
|
|
918
917
|
try {
|
|
919
|
-
const candidates = [ctx.configForms.get(
|
|
918
|
+
const candidates = [ctx.configForms.get(ROW_ID), ctx.configForms.get(NS)]
|
|
920
919
|
const ready = candidates.find((s) => s && s.getSnapshot && s.getSnapshot().status === 'ready')
|
|
921
920
|
const scope = ready || candidates[0]
|
|
922
921
|
if (scope && typeof scope.subscribe === 'function') {
|
package/lib/index.js
CHANGED
|
@@ -10,6 +10,8 @@ import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
|
10
10
|
export const name = '@goodandready/dsh-plugin-notify';
|
|
11
11
|
/** Settings namespace shared with the Web settings card. */
|
|
12
12
|
export const NS = '@goodandready/dsh-plugin-notify';
|
|
13
|
+
/** Canonical entry id matching cordis.patch.yml */
|
|
14
|
+
export const ENTRY_ID = 'plugin-notify';
|
|
13
15
|
|
|
14
16
|
// Session firehose + credentials (webhook URL refs) + settings scope for the card + webServer for SSE events + connection for authentication.
|
|
15
17
|
export const inject = ['sessions', 'credentials', 'settings', 'webServer', 'connection'];
|
|
@@ -734,7 +736,7 @@ function mountWebServer(targetCtx, hostCtx, getConfig) {
|
|
|
734
736
|
}
|
|
735
737
|
|
|
736
738
|
export function apply(ctx, config = {}) {
|
|
737
|
-
let getConfig = () => config ?? {};
|
|
739
|
+
let getConfig = () => unwrapConfig(config ?? {});
|
|
738
740
|
|
|
739
741
|
let webServerMounted = false;
|
|
740
742
|
let unmountWebServer = null;
|
|
@@ -768,22 +770,48 @@ export function apply(ctx, config = {}) {
|
|
|
768
770
|
if (typeof ctx.inject === 'function') {
|
|
769
771
|
ctx.inject(['settings'], (sctx) => {
|
|
770
772
|
if (typeof sctx.settings?.register === 'function') {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
773
|
+
try {
|
|
774
|
+
const scope = sctx.settings.register(ENTRY_ID, Config, { base: config ?? {} });
|
|
775
|
+
getConfig = () => (scope ? unwrapConfig(scope.get() ?? config ?? {}) : unwrapConfig(config ?? {}));
|
|
776
|
+
return;
|
|
777
|
+
} catch (err) {
|
|
778
|
+
ctx?.logger?.warn?.(`[plugin-notify] settings.register('${ENTRY_ID}') failed, falling back to describe: ${String(err)}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
let currentRow = (() => { try { return sctx.settings?.describe?.().find((r) => r && (r.ns === ENTRY_ID || r.ns === NS)); } catch { return null; } })();
|
|
782
|
+
const refreshSettings = () => {
|
|
783
|
+
try {
|
|
784
|
+
const next = sctx.settings?.describe?.().find((r) => r && (r.ns === ENTRY_ID || r.ns === NS));
|
|
785
|
+
if (next) currentRow = next;
|
|
786
|
+
} catch {
|
|
787
|
+
// best effort
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
if (typeof ctx.on === 'function') {
|
|
791
|
+
ctx.on('settings/document-updated', refreshSettings);
|
|
792
|
+
ctx.on('loader/volatile-update', refreshSettings);
|
|
793
|
+
ctx.on('settings/updated', refreshSettings);
|
|
794
|
+
}
|
|
795
|
+
if (typeof sctx.settings?.on === 'function') {
|
|
796
|
+
try { sctx.settings.on('update', refreshSettings); } catch { /* ignore */ }
|
|
774
797
|
}
|
|
775
|
-
const row = (() => { try { return sctx.settings?.describe?.().find((r) => r && r.ns === NS); } catch { return null; } })();
|
|
776
798
|
const scope = {
|
|
777
|
-
get: () =>
|
|
799
|
+
get: () => {
|
|
800
|
+
refreshSettings();
|
|
801
|
+
return currentRow?.value ?? config ?? {};
|
|
802
|
+
},
|
|
778
803
|
replace: async (next) => {
|
|
779
804
|
try {
|
|
780
|
-
|
|
805
|
+
const targetNs = sctx.settings?.describe?.().some((r) => r && r.ns === ENTRY_ID) ? ENTRY_ID : NS;
|
|
806
|
+
const res = await sctx.settings?.replace?.(targetNs, next, currentRow?.revision);
|
|
807
|
+
refreshSettings();
|
|
808
|
+
return res;
|
|
781
809
|
} catch (err) {
|
|
782
810
|
ctx?.logger?.warn?.(`[plugin-notify] settings replace failed: ${String(err)}`);
|
|
783
811
|
}
|
|
784
812
|
},
|
|
785
813
|
};
|
|
786
|
-
getConfig = () => scope.get() ?? config ?? {};
|
|
814
|
+
getConfig = () => (scope ? unwrapConfig(scope.get() ?? config ?? {}) : unwrapConfig(config ?? {}));
|
|
787
815
|
});
|
|
788
816
|
ctx.inject(['webServer'], (wctx) => tryMountWebServer(wctx))
|
|
789
817
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-plugin-notify",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "DSH plugin: audio chimes, cross-session toasts, desktop push, IM webhooks, and web push (PWA) for turn completion, errors, and approvals.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|