@goodandready/dsh-dsml-artifact-guard 0.2.5 → 0.2.6

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
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.2.6] - 2026-09-25
11
+
12
+ ### Fixed
13
+ - **SettingsForms Migration (DSH 0.1.7)**: marked Config schema as volatile and configured settings policy (`auto: false`) to suppress generic auto-generated form duplicates, while resolving row ID `dsh-dsml-artifact-guard` via client `configForms` with live subscription support (#17).
14
+ - **Client Locale Lifecycle**: wrapped client locale registration in `ctx.effect` to ensure clean namespace disposal on HMR and avoid collision on module reload (#44).
15
+
10
16
  ## [0.2.5] - 2026-09-24
11
17
 
12
18
  ### Fixed
package/lib/client.js CHANGED
@@ -102,16 +102,20 @@ window.__ModuleLoader__.load({
102
102
  });
103
103
 
104
104
  const scopeRef = React.useRef(null);
105
- if (!scopeRef.current && _ctx && _ctx.configForms) {
105
+ if (!scopeRef.current && _ctx) {
106
106
  try {
107
- scopeRef.current = _ctx.configForms.get(NS);
107
+ if (_ctx.configForms && typeof _ctx.configForms.get === 'function') {
108
+ scopeRef.current = _ctx.configForms.get(ROW_ID) || _ctx.configForms.get(NS);
109
+ } else if (_ctx.settingsScope && typeof _ctx.settingsScope.bind === 'function') {
110
+ scopeRef.current = _ctx.settingsScope.bind({ namespace: NS });
111
+ }
108
112
  } catch (e) {
109
113
  scopeRef.current = null;
110
114
  }
111
115
  }
112
116
  const scope = scopeRef.current;
113
117
 
114
- const currentLang = (_ctx?.locale?.get?.() || navigator.language || 'en').startsWith('zh') ? 'zh' : 'en';
118
+ const currentLang = ((typeof _ctx?.locale?.getLocale === 'function' ? _ctx.locale.getLocale() : null) || (typeof navigator !== 'undefined' && navigator.language) || 'en').startsWith('zh') ? 'zh' : 'en';
115
119
  const dict = currentLang === 'zh' ? zh : en;
116
120
  const tt = (k, params) => {
117
121
  let str = (typeof t === 'function' ? t(k) : null) || dict[k] || en[k] || k;
@@ -123,43 +127,67 @@ window.__ModuleLoader__.load({
123
127
  return str;
124
128
  };
125
129
 
126
- // Load settings from snapshot
130
+ // Load settings from snapshot and subscribe to live changes
127
131
  React.useEffect(() => {
128
132
  if (!scope) {
129
133
  setStatus('unavailable');
130
134
  return;
131
135
  }
132
136
  let cancelled = false;
133
- (async () => {
134
- try {
135
- const snap = await scope.get();
136
- if (cancelled) return;
137
- if (snap && typeof snap === 'object' && 'status' in snap) {
138
- if (snap.status === 'ready' && snap.value) {
139
- setDraft({
140
- mode: snap.value.mode || DEFAULTS.mode,
141
- providerId: snap.value.providerId || DEFAULTS.providerId,
142
- modelId: snap.value.modelId || DEFAULTS.modelId,
143
- });
144
- setStatus('ready');
145
- } else {
146
- setStatus(snap.status);
147
- }
148
- } else if (snap && typeof snap === 'object') {
137
+ const syncSnapshot = (snap) => {
138
+ if (!snap || cancelled) return;
139
+ const view = snap.view || snap.value || snap;
140
+ if (typeof snap === 'object' && 'status' in snap) {
141
+ if (snap.status === 'ready' && view) {
149
142
  setDraft({
150
- mode: snap.mode || DEFAULTS.mode,
151
- providerId: snap.providerId || DEFAULTS.providerId,
152
- modelId: snap.modelId || DEFAULTS.modelId,
143
+ mode: view.mode || DEFAULTS.mode,
144
+ providerId: view.providerId || DEFAULTS.providerId,
145
+ modelId: view.modelId || DEFAULTS.modelId,
153
146
  });
154
147
  setStatus('ready');
155
148
  } else {
156
- setStatus('ready');
149
+ setStatus(snap.status);
157
150
  }
158
- } catch {
151
+ } else if (typeof view === 'object') {
152
+ setDraft({
153
+ mode: view.mode || DEFAULTS.mode,
154
+ providerId: view.providerId || DEFAULTS.providerId,
155
+ modelId: view.modelId || DEFAULTS.modelId,
156
+ });
157
+ setStatus('ready');
158
+ } else {
159
+ setStatus('ready');
160
+ }
161
+ };
162
+
163
+ (async () => {
164
+ try {
165
+ const snap = typeof scope.getSnapshot === 'function' ? scope.getSnapshot() : (typeof scope.get === 'function' ? await scope.get() : null);
166
+ syncSnapshot(snap);
167
+ } catch (err) {
159
168
  if (!cancelled) setStatus('unavailable');
160
169
  }
161
170
  })();
162
- return () => { cancelled = true; };
171
+
172
+ let unsubscribe;
173
+ if (typeof scope.subscribe === 'function') {
174
+ try {
175
+ unsubscribe = scope.subscribe(() => {
176
+ try {
177
+ syncSnapshot(typeof scope.getSnapshot === 'function' ? scope.getSnapshot() : null);
178
+ } catch (err) {
179
+ /* Snapshot update read failed */
180
+ }
181
+ });
182
+ } catch (err) {
183
+ /* Subscription to scope updates unavailable */
184
+ }
185
+ }
186
+
187
+ return () => {
188
+ cancelled = true;
189
+ if (typeof unsubscribe === 'function') unsubscribe();
190
+ };
163
191
  }, [scope]);
164
192
 
165
193
  // Check updater status on mount
@@ -179,7 +207,7 @@ window.__ModuleLoader__.load({
179
207
  latestVersion: data.latestVersion || '',
180
208
  updateAvailable: !!data.updateAvailable,
181
209
  }));
182
- } catch {
210
+ } catch (err) {
183
211
  if (!cancelled) {
184
212
  setUpdateState(s => ({ ...s, checking: false, error: tt('checkUpdateFailed') }));
185
213
  }
@@ -544,10 +572,17 @@ window.__ModuleLoader__.load({
544
572
  exports.inject = ['slots', 'configForms', 'locale'];
545
573
  exports.apply = function apply(ctx) {
546
574
  if (typeof ctx.locale?.register === 'function') {
547
- try {
548
- ctx.locale.register(NS, { en, zh });
549
- } catch (err) {
550
- /* Locale namespace already registered or unavailable */
575
+ const registerLocale = () => {
576
+ try {
577
+ return ctx.locale.register(NS, { en, zh });
578
+ } catch (err) {
579
+ /* Locale namespace already registered or unavailable */
580
+ }
581
+ };
582
+ if (typeof ctx.effect === 'function') {
583
+ ctx.effect(registerLocale, 'dsh-dsml-artifact-guard: locale');
584
+ } else {
585
+ registerLocale();
551
586
  }
552
587
  }
553
588
 
package/lib/index.js CHANGED
@@ -9,12 +9,16 @@ const KEEP = 96;
9
9
  export const name = '@goodandready/dsh-dsml-artifact-guard';
10
10
  export const inject = ['llm', 'settings', 'webServer'];
11
11
 
12
- export const Config = Schema.object({
12
+ const rawConfig = Schema.object({
13
13
  mode: Schema.string().default('sanitize'),
14
14
  providerId: Schema.string().default('opencode-go'),
15
15
  modelId: Schema.string().default('deepseek-v4-flash'),
16
16
  });
17
17
 
18
+ export const Config = typeof rawConfig.volatile === 'function'
19
+ ? rawConfig.volatile()
20
+ : rawConfig.extra('volatile', true);
21
+
18
22
  export function matchesScope(o, c) {
19
23
  const provider = o?.providerId ?? o?.provider?.id ?? o?.provider;
20
24
  const model = o?.modelId ?? o?.model?.id ?? o?.model?.name ?? o?.model;
@@ -109,19 +113,31 @@ export function apply(ctx, initialConfig = {}) {
109
113
 
110
114
  let getConfig = () => activeConfig;
111
115
 
112
- // Register settings scope inside ctx.effect
113
- if (typeof ctx.inject === 'function') {
116
+ // Live config subscription from the plugin fiber
117
+ if (typeof ctx.on === 'function') {
118
+ ctx.effect(() => ctx.on('config', (nextConfig) => {
119
+ activeConfig = Config(nextConfig) ?? {};
120
+ }), 'dsh-dsml-artifact-guard: live config');
121
+ }
122
+
123
+ // DSH 0.1.7 SettingsForms: suppress generic duplicate form, custom card mounts in plugins seat
124
+ if (ctx.settings && typeof ctx.settings.configure === 'function') {
125
+ ctx.effect(() => ctx.settings.configure({ auto: false }), 'dsh-dsml-artifact-guard: settings policy');
126
+ } else if (typeof ctx.inject === 'function') {
127
+ // Legacy settings service fallback
114
128
  ctx.inject(['settings'], (sctx) => {
115
- try {
116
- const scope = sctx.settings.register(NS, Config, { base: initialConfig });
117
- getConfig = () => Config(scope?.get() ?? initialConfig) ?? {};
118
- if (typeof scope?.watch === 'function') {
119
- sctx.effect(() => scope.watch(() => {
120
- activeConfig = getConfig();
121
- }));
129
+ if (typeof sctx.settings?.register === 'function') {
130
+ try {
131
+ const scope = sctx.settings.register(NS, Config, { base: initialConfig });
132
+ getConfig = () => Config(scope?.get() ?? initialConfig) ?? {};
133
+ if (typeof scope?.watch === 'function') {
134
+ sctx.effect(() => scope.watch(() => {
135
+ activeConfig = getConfig();
136
+ }));
137
+ }
138
+ } catch (err) {
139
+ /* Legacy settings service registration unavailable */
122
140
  }
123
- } catch (err) {
124
- ctx.logger?.warn?.(`[dsh-dsml-artifact-guard] settings registration skipped: ${String(err)}`);
125
141
  }
126
142
  });
127
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-dsml-artifact-guard",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Sanitizes leaked DeepSeek DSML closing tags from model text streams",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",