@jayyuen66/dsh-quality-gate 0.1.4 → 0.1.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/client.js CHANGED
@@ -8,365 +8,19 @@ window.__ModuleLoader__.load({
8
8
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
9
9
  let react = require("react");
10
10
  let zod = require("zod");
11
- // node_modules/@jayyuen66/dsh-plugin-shared/dist/card-diff.js
12
- function e$1(t, n, r) {
13
- if (t == null || n == null) return (t ?? null) === (n ?? null);
14
- if (Object.is(t, n)) return !0;
15
- if (typeof t != `object` || typeof n != `object`) return !1;
16
- if (r.get(t) === n) return !0;
17
- if (r.set(t, n), Array.isArray(t) || Array.isArray(n)) return !Array.isArray(t) || !Array.isArray(n) || t.length !== n.length ? !1 : t.every((t, i) => e$1(t, n[i], r));
18
- let i = Object.keys(t), a = Object.keys(n);
19
- return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e$1(Reflect.get(t, i), Reflect.get(n, i), r));
20
- }
21
- function t$1(t, n) {
22
- let r = [];
23
- for (let i of Object.keys(t)) e$1(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
24
- return r;
25
- }
26
- // node_modules/@jayyuen66/dsh-plugin-shared/dist/card-ui.js
27
- const c = `
28
- .dsu-form{display:flex;flex-direction:column}
29
- .dsu-section{margin-top:24px;padding-top:24px;border-top:.5px solid var(--dsw-alias-border-l1)}
30
- .dsu-form>.dsu-section:first-child{margin-top:0;padding-top:0;border-top:0}
31
- .dsu-sectionTitle{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}
32
- .dsu-sectionHint{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
33
- .dsu-statusbar{display:flex;flex-wrap:wrap;gap:4px 8px;margin-top:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
34
- .dsu-field{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,360px);column-gap:32px;align-items:center;padding:12px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
35
- .dsu-fieldMeta{display:flex;flex-direction:column;gap:4px;min-width:0}
36
- .dsu-labelRow{display:flex;align-items:center;gap:6px;min-width:0}
37
- .dsu-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
38
- .dsu-labelMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
39
- .dsu-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
40
- .dsu-control{min-width:0;display:flex;align-items:center;gap:8px}
41
- .dsu-fieldWide{grid-template-columns:minmax(0,1fr);align-items:start}
42
- .dsu-fieldWide .dsu-control{width:100%;max-width:720px;margin-top:2px}
43
- .dsu-help{display:inline-flex;align-items:center;gap:6px;min-width:0}
44
- .dsu-helpBtn{appearance:none;border:0;background:transparent;padding:0;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:help;border-radius:50%;flex:none}
45
- .dsu-help:hover .dsu-helpBtn,.dsu-helpBtn:focus-visible{color:var(--dsw-alias-label-primary)}
46
- .dsu-helpBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
47
- .dsu-tip{position:fixed;z-index:60;box-sizing:border-box;max-width:min(340px,calc(100vw - 16px));max-height:calc(100vh - 16px);overflow:auto;overflow-wrap:anywhere;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);box-shadow:0 12px 32px rgb(0 0 0 / 45%);font-size:12px;line-height:1.6;font-weight:400;color:var(--dsw-alias-label-secondary)}
48
- .dsu-input{width:100%;height:34px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
49
- .dsu-input:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}
50
- .dsu-input[aria-invalid="true"]{border-color:var(--dsw-alias-state-error-primary)}
51
- .dsu-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
52
- .dsu-input::placeholder{color:var(--dsw-alias-label-tertiary)}
53
- .dsu-textarea{height:auto;min-height:96px;padding:8px 12px;line-height:1.6;resize:vertical}
54
- .dsu-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
55
- .dsu-numberWrap{display:flex;flex-direction:column;gap:4px;width:100%;min-width:0}
56
- .dsu-number{display:flex;align-items:center;gap:8px;min-width:0}
57
- .dsu-number .dsu-input{min-width:0}
58
- .dsu-unit{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}
59
- .dsu-error{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
60
- .dsu-switch{appearance:none;width:36px;height:20px;border-radius:999px;border:0;padding:2px;background:var(--dsw-alias-border-l3);display:flex;align-items:center;cursor:pointer;flex:none;transition:background-color .16s}
61
- .dsu-knob{width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .16s,background-color .16s}
62
- .dsu-switch[aria-checked="true"]{background:var(--dsw-alias-label-primary)}
63
- .dsu-switch[aria-checked="true"] .dsu-knob{transform:translateX(16px);background:var(--dsw-alias-label-primary-foreground)}
64
- .dsu-switch:disabled{opacity:.4;cursor:not-allowed}
65
- .dsu-switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
66
- .dsu-selectWrap{position:relative;width:100%;min-width:0}
67
- .dsu-select{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;cursor:pointer}
68
- .dsu-select[aria-expanded="true"]{border-color:var(--dsw-alias-state-business-primary)}
69
- .dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
70
- .dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}
71
- .dsu-select[aria-expanded="true"] .dsu-chevron{transform:rotate(180deg)}
72
- .dsu-menu{position:fixed;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
73
- .dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}
74
- .dsu-option:hover,.dsu-option[data-active="true"]{background:var(--dsw-alias-bg-layer-3)}
75
- .dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}
76
- .dsu-option:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
77
- .dsu-optionGroup{padding:6px 10px 2px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary)}
78
- .dsu-check{opacity:0;flex:none}
79
- .dsu-option[aria-selected="true"] .dsu-check{opacity:1}
80
- .dsu-savebar{display:flex;align-items:center;gap:8px;padding:12px 0;margin-top:24px;border-top:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base)}
81
- .dsu-savebar[data-dirty="true"]{position:sticky;bottom:0;z-index:2;box-shadow:0 -12px 24px rgb(0 0 0 / 35%)}
82
- .dsu-btn{appearance:none;border:.5px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.5;border-radius:var(--dsw-radius-md);padding:5px 14px;cursor:pointer;transition:background-color .12s,border-color .12s,color .12s,filter .12s}
83
- .dsu-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l3)}
84
- .dsu-btn:active:not(:disabled){background:var(--dsw-alias-bg-layer-1)}
85
- .dsu-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
86
- .dsu-btn:disabled{opacity:.4;cursor:not-allowed}
87
- .dsu-btnPrimary{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
88
- .dsu-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.9)}
89
- .dsu-btnPrimary:active:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.84)}
90
- .dsu-btnDanger{color:var(--dsw-alias-state-error-primary)}
91
- .dsu-btnDanger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2)}
92
- .dsu-status{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}
93
- .dsu-statusError{color:var(--dsw-alias-state-error-primary)}
94
- .dsu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 24px}
95
- .dsu-grid>.dsu-field{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:10px 0;border-top:.5px solid var(--dsw-alias-border-l1)}
96
- .dsu-grid>.dsu-field .dsu-control{width:100%}
97
- @media (max-width:640px){
98
- .dsu-field{grid-template-columns:minmax(0,1fr);align-items:start}
99
- .dsu-control{width:100%;max-width:720px}
100
- .dsu-section{margin-top:20px;padding-top:20px}
101
- }
102
- @media (prefers-reduced-motion:reduce){
103
- .dsu-switch,.dsu-knob,.dsu-chevron,.dsu-btn{transition:none}
104
- }
105
- `;
106
- function l(e, t = 30) {
107
- return e.length <= t ? { hint: e } : { detail: e };
108
- }
109
- function u(t) {
110
- return (0, react.createElement)(`div`, { className: `dsu-form` }, t.children);
111
- }
112
- function d(t) {
113
- return (0, react.createElement)(`section`, { className: `dsu-section` }, (0, react.createElement)(`h2`, { className: `dsu-sectionTitle` }, t.title), t.hint === void 0 ? null : (0, react.createElement)(`p`, { className: `dsu-sectionHint` }, t.hint), t.children);
114
- }
115
- function m(a) {
116
- let c = (0, react.useId)(), [l, u] = (0, react.useState)(!1), [d, f] = (0, react.useState)(!1), p = (0, react.useRef)(!1), m = (0, react.useRef)(!1), h = (0, react.useRef)(!1), g = (0, react.useRef)(null), _ = (0, react.useRef)(null), v = (0, react.useRef)(null), y = (0, react.useCallback)(() => {
117
- v.current !== null && (clearTimeout(v.current), v.current = null);
118
- }, []), b = (0, react.useCallback)(() => {
119
- let e = g.current, t = _.current;
120
- /* v8 ignore next 3 -- place 只在 open 的 layout effect 与挂载期监听器里调用;refs 为空只可能是卸载竞态 */
121
- if (e === null || t === null) return;
122
- let n = e.getBoundingClientRect(), r = t.offsetWidth, i = t.offsetHeight, { left: a } = n, o = n.bottom + 8;
123
- o + i > window.innerHeight - 8 && (o = n.top - i - 8), a + r > window.innerWidth - 8 && (a = window.innerWidth - r - 8);
124
- let s = Math.max(8, window.innerWidth - r - 8), c = Math.max(8, window.innerHeight - i - 8);
125
- t.style.left = `${Math.min(Math.max(8, a), s)}px`, t.style.top = `${Math.min(Math.max(8, o), c)}px`;
126
- }, []), x = (0, react.useCallback)(() => {
127
- y(), h.current = !1, f(!1), u(!1);
128
- }, [y]), S = (0, react.useCallback)(() => {
129
- y(), u(!0);
130
- }, [y]), C = (0, react.useCallback)(() => {
131
- y(), !(p.current || m.current || h.current) && (v.current = setTimeout(() => {
132
- /* v8 ignore next 3 -- 任何重新进入/钉住都会 cancelHide;这次复查只防未来的异步路径 */
133
- v.current = null, p.current || m.current || h.current || u(!1);
134
- }, 120));
135
- }, [y]), w = (0, react.useCallback)((e) => {
136
- h.current = e, f(e);
137
- }, []);
138
- (0, react.useLayoutEffect)(() => {
139
- if (!l) return () => {};
140
- b();
141
- let e = (e) => {
142
- let { target: t } = e;
143
- t instanceof Node && _.current?.contains(t) === !0 || (h.current ? b() : x());
144
- }, t = () => {
145
- b();
146
- };
147
- return window.addEventListener(`scroll`, e, !0), window.addEventListener(`resize`, t), () => {
148
- window.removeEventListener(`scroll`, e, !0), window.removeEventListener(`resize`, t);
149
- };
150
- }, [
151
- l,
152
- a.detail,
153
- b,
154
- x
155
- ]), (0, react.useEffect)(() => {
156
- if (!l) return () => {};
157
- let e = (e) => {
158
- e.key === `Escape` && x();
159
- };
160
- return document.addEventListener(`keydown`, e), () => {
161
- document.removeEventListener(`keydown`, e);
162
- };
163
- }, [l, x]), (0, react.useEffect)(() => y, [y]);
164
- let T = (0, react.createElement)(`button`, {
165
- ref: g,
166
- type: `button`,
167
- className: `dsu-helpBtn`,
168
- "aria-label": `${a.label} 说明`,
169
- "aria-describedby": c,
170
- "aria-pressed": d,
171
- onClick: () => {
172
- h.current ? x() : (w(!0), S());
173
- }
174
- }, (0, react.createElement)(`svg`, {
175
- width: 12,
176
- height: 12,
177
- viewBox: `0 0 14 14`,
178
- "aria-hidden": !0
179
- }, (0, react.createElement)(`circle`, {
180
- cx: 7,
181
- cy: 7,
182
- r: 6,
183
- fill: `none`,
184
- stroke: `currentColor`,
185
- strokeWidth: 1.2
186
- }), (0, react.createElement)(`path`, {
187
- d: `M7 6.2v4`,
188
- stroke: `currentColor`,
189
- strokeWidth: 1.3,
190
- strokeLinecap: `round`
191
- }), (0, react.createElement)(`circle`, {
192
- cx: 7,
193
- cy: 4.3,
194
- r: .8,
195
- fill: `currentColor`
196
- }))), E = (0, react.createElement)(`div`, {
197
- ref: _,
198
- id: c,
199
- role: `tooltip`,
200
- className: `dsu-tip`,
201
- hidden: !l
202
- }, a.detail), D = a.showLabel === !1 ? null : (0, react.createElement)(`span`, { className: `dsu-label${a.compact === !0 ? ` dsu-labelMono` : ``}` }, a.label);
203
- return (0, react.createElement)(`span`, {
204
- className: `dsu-help`,
205
- onMouseEnter: () => {
206
- p.current = !0, S();
207
- },
208
- onMouseLeave: () => {
209
- p.current = !1, C();
210
- },
211
- onFocus: () => {
212
- m.current = !0, S();
213
- },
214
- onBlur: (e) => {
215
- let t = e.relatedTarget;
216
- t instanceof Node && e.currentTarget.contains(t) || (m.current = !1, C());
217
- }
218
- }, D, T, E);
219
- }
220
- function h(t) {
221
- let n = `dsu-label${t.compact === !0 ? ` dsu-labelMono` : ``}`, r = t.controlId === void 0 ? (0, react.createElement)(`span`, { className: n }, t.label) : (0, react.createElement)(`label`, {
222
- className: n,
223
- htmlFor: t.controlId
224
- }, t.label);
225
- return (0, react.createElement)(`div`, { className: `dsu-labelRow` }, r, t.detail === void 0 ? null : (0, react.createElement)(m, {
226
- label: t.label,
227
- detail: t.detail,
228
- showLabel: !1
229
- }));
230
- }
231
- function g(t) {
232
- let n = t.controlId === void 0 ? void 0 : `${t.controlId}-hint`;
233
- return (0, react.createElement)(`div`, { className: `dsu-field${t.wide === !0 ? ` dsu-fieldWide` : ``}` }, (0, react.createElement)(`div`, { className: `dsu-fieldMeta` }, (0, react.createElement)(h, {
234
- label: t.label,
235
- detail: t.detail,
236
- compact: t.compact,
237
- controlId: t.controlId
238
- }), t.hint === void 0 ? null : (0, react.createElement)(`p`, {
239
- className: `dsu-hint`,
240
- id: n
241
- }, t.hint)), (0, react.createElement)(`div`, { className: `dsu-control` }, t.children));
242
- }
243
- function _(t) {
244
- let n = (0, react.useId)(), i = t.hint === void 0 ? void 0 : `${n}-hint`;
245
- return (0, react.createElement)(g, {
246
- ...t,
247
- controlId: n
248
- }, (0, react.createElement)(`button`, {
249
- type: `button`,
250
- role: `switch`,
251
- id: n,
252
- className: `dsu-switch`,
253
- "aria-checked": t.checked,
254
- "aria-describedby": i,
255
- "data-field": t.field,
256
- disabled: t.disabled === !0,
257
- onClick: () => {
258
- t.onChange(!t.checked);
259
- }
260
- }, (0, react.createElement)(`span`, { className: `dsu-knob` })));
261
- }
262
- function v(t) {
263
- let i = typeof t.value == `number` && Number.isFinite(t.value) ? String(t.value) : ``, [a, c] = (0, react.useState)(i), [l, u] = (0, react.useState)(!1), d = (0, react.useRef)(void 0), f = (0, react.useId)(), p = (0, react.useId)(), m = [t.hint === void 0 ? void 0 : `${f}-hint`, l ? p : void 0].filter((e) => e !== void 0), h = m.length === 0 ? void 0 : m.join(` `);
264
- (0, react.useEffect)(() => {
265
- t.value !== d.current && (d.current = t.value, c(i), u(!1));
266
- }, [t.value, i]);
267
- let _ = (e) => {
268
- let n = t.min ?? -1 / 0, r = t.max ?? 1 / 0;
269
- if (!Number.isFinite(e) || e < n || e > r) return !1;
270
- let { step: i } = t;
271
- if (i === void 0 || i <= 0) return !0;
272
- let a = (e - (t.min ?? 0)) / i;
273
- return Math.abs(a - Math.round(a)) < 1e-9;
274
- }, v = (e) => {
275
- if (c(e), e === ``) {
276
- u(!1), d.current = void 0, t.onChange(void 0);
277
- return;
278
- }
279
- let n = Number(e);
280
- _(n) ? (u(!1), d.current = n, t.onChange(n)) : u(!0);
281
- }, y = () => {
282
- a === `` || l || c(String(Number(a)));
283
- };
284
- return (0, react.createElement)(g, {
285
- ...t,
286
- controlId: f
287
- }, (0, react.createElement)(`div`, { className: `dsu-numberWrap` }, (0, react.createElement)(`div`, { className: `dsu-number` }, (0, react.createElement)(`input`, {
288
- className: `dsu-input`,
289
- type: `number`,
290
- inputMode: `numeric`,
291
- id: f,
292
- value: a,
293
- min: t.min,
294
- max: t.max,
295
- step: t.step,
296
- placeholder: t.placeholder,
297
- "aria-invalid": l || void 0,
298
- "aria-describedby": h,
299
- "data-field": t.field,
300
- disabled: t.disabled === !0,
301
- onChange: (e) => {
302
- v(e.target.value);
303
- },
304
- onBlur: y
305
- }), t.unit === void 0 ? null : (0, react.createElement)(`span`, { className: `dsu-unit` }, t.unit)), l ? (0, react.createElement)(`p`, {
306
- className: `dsu-error`,
307
- id: p
308
- }, t.invalidHint) : null));
309
- }
310
- function y(t) {
311
- let [i, a] = (0, react.useState)(t.value), [o, c] = (0, react.useState)(!1), l = (0, react.useId)(), u = t.hint === void 0 ? void 0 : `${l}-hint`;
312
- (0, react.useEffect)(() => {
313
- o || a(t.value);
314
- }, [t.value, o]);
315
- let d = {
316
- id: l,
317
- className: `dsu-input${t.multiline === !0 ? ` dsu-textarea` : ``}${t.monospace === !0 ? ` dsu-mono` : ``}`,
318
- value: i,
319
- placeholder: t.placeholder,
320
- "aria-describedby": u,
321
- "data-field": t.field,
322
- disabled: t.disabled === !0,
323
- onFocus: () => {
324
- c(!0);
325
- },
326
- onBlur: () => {
327
- c(!1);
328
- },
329
- onChange: (e) => {
330
- a(e.target.value), t.onChange(e.target.value);
331
- }
332
- };
333
- return (0, react.createElement)(g, {
334
- ...t,
335
- controlId: l
336
- }, t.multiline === !0 ? (0, react.createElement)(`textarea`, d) : (0, react.createElement)(`input`, d));
337
- }
338
- function S(t) {
339
- let n = !t.writable || t.busy || !t.dirty, r;
340
- return r = t.error === null ? t.writable ? t.dirty ? t.dirtyLabel : t.cleanLabel : t.readOnlyLabel : t.error, (0, react.createElement)(`div`, {
341
- className: `dsu-savebar`,
342
- "data-dirty": t.dirty
343
- }, (0, react.createElement)(`button`, {
344
- type: `button`,
345
- className: `dsu-btn dsu-btnPrimary`,
346
- "data-field": `save`,
347
- disabled: n,
348
- onClick: t.onSave
349
- }, t.busy ? t.savingLabel : t.saveLabel), (0, react.createElement)(`button`, {
350
- type: `button`,
351
- className: `dsu-btn`,
352
- "data-field": `discard`,
353
- disabled: n,
354
- onClick: t.onDiscard
355
- }, t.revertLabel), (0, react.createElement)(`span`, { className: t.error === null ? `dsu-status` : `dsu-status dsu-statusError` }, r));
356
- }
11
+ let _jayyuen66_dsh_plugin_shared_client = require("@jayyuen66/dsh-plugin-shared/client");
357
12
  // src/ui-messages.ts
358
13
  const UI_MESSAGES = {
359
14
  zh: {
360
- cardTitle: "quality-gate 回合收口质量门禁",
361
- cardDescription: "回合收口质量门禁(ECC stop-format-typecheck 移植):本回合编辑过代码文件时,收口前自动跑项目门禁(根 package.json 声明了 check 才用 pnpm check,否则 cargo check / ruff / tsc),失败自动开新回合注入修复指令;失败事实同步 lesson-loop 总线,记忆网关沉淀默认关闭",
362
15
  statusReadOnly: "当前作用域只读",
363
- statusDirty: "有未保存的修改,点「保存」生效",
364
16
  statusClean: "无未保存的修改",
365
17
  save: "保存",
366
18
  saving: "保存中…",
367
19
  revert: "撤销",
368
20
  saveFailed: "保存失败:{message}",
369
21
  saveRejected: "保存被宿主拒绝(配置校验未通过或写入被跳过),修改未生效",
22
+ invalidGatewayUrl: "网关地址不接受:不能带用户名/口令;远端地址必须是 https,明文 http 只允许本机回环",
23
+ invalidKeyRef: "凭据引用名不接受:只能由字母、数字、下划线组成,且不能以数字开头",
370
24
  outOfRange: "超出允许范围,未保存",
371
25
  gateSection: "门禁",
372
26
  memorySection: "记忆反馈",
@@ -375,28 +29,27 @@ const UI_MESSAGES = {
375
29
  dirtyCount: "已修改 {count} 项",
376
30
  enabledLabel: "启用质量门禁",
377
31
  enabledHint: "关闭后回合收口不再跑门禁(模型可带病收口)",
378
- injectsLabel: "每回合注入配额",
379
- injectsHint: "门禁失败每回合最多注入几次修复指令(1-5,默认 2,防跑飞)",
380
- budgetLabel: "门禁预算",
381
- budgetHint: "单次门禁最大耗时(10s-600s,默认 300s;超时按失败注入不静默放行)",
32
+ injectsLabel: "每会话注入配额",
33
+ injectsHint: "门禁失败后最多注入几次修复指令;配额按会话累计,门禁通过后重置",
34
+ budgetLabel: "门禁总预算",
35
+ budgetHint: "一次回合内所有门禁共享的总耗时预算(多根均分);超时按「未能执行」注入,不静默放行",
382
36
  memoryLabel: "门禁失败沉淀记忆",
383
- memoryHint: "默认关闭。开启后:代码型门禁失败的证据(命令/根/全文报错,不截断)写入记忆网关的 L0 会话层供后续会话召回;总线 lesson-loop 的同步上报不受此开关影响。关闭后只注入不沉淀。真正决定能否投递的是网关地址能否解析出值(见下面两项)",
37
+ memoryHint: "默认关闭。开启后:代码型门禁失败的证据(命令/根/全文报错,不截断)写入记忆网关的 L0 会话层供后续会话召回;总线 lesson-loop 的同步上报不受此开关影响。关闭后只注入不沉淀。投递要求网关地址与凭据引用都可用(见下面两项)",
384
38
  gatewayUrlLabel: "记忆网关地址",
385
- gatewayUrlHint: "插件不带任何默认地址:本机用户请填自己的网关地址(例如 http://127.0.0.1:8420),没有网关就让它空着。此项留空时回落到环境变量 TDAI_GATEWAY_URL,两处都空才算未配置、不启用记忆反馈",
39
+ gatewayUrlHint: "插件不带任何默认地址:本机用户请填自己的网关地址(例如 http://127.0.0.1:8420),没有网关就让它空着。此项留空时回落到环境变量 TDAI_GATEWAY_URL,两处都空才算未配置、不启用记忆反馈。远端地址必须 https,明文 http 只允许本机回环(127.0.0.0/8、::1、localhost)",
386
40
  keyRefLabel: "网关密钥引用",
387
41
  keyRefHint: "网关鉴权令牌的凭据引用名(环境变量名形态,默认 TDAI_GATEWAY_KEY)。这里存的只是引用名,密钥值由 dsh 凭据服务管理(Web 凭据页或 ~/.dsh/.credentials.yaml 的 refs),不会写进 settings.yaml;清空即恢复默认引用名,而不是停用记忆反馈"
388
42
  },
389
43
  en: {
390
- cardTitle: "quality-gate turn-end quality gate",
391
- cardDescription: "Turn-end quality gate (ported from ECC stop-format-typecheck): when this turn edited code files, the project gate runs automatically before the turn closes (pnpm check only if the root package.json declares a check script, otherwise cargo check / ruff / tsc); a failure opens a new turn with repair instructions. Failure facts are also reported to the lesson-loop bus; memory-gateway persistence is off by default",
392
44
  statusReadOnly: "This scope is read-only",
393
- statusDirty: "Unsaved changes — press Save to apply",
394
45
  statusClean: "No unsaved changes",
395
46
  save: "Save",
396
47
  saving: "Saving…",
397
48
  revert: "Revert",
398
49
  saveFailed: "Save failed: {message}",
399
50
  saveRejected: "Save rejected by the host (validation failed or write skipped) — changes not applied",
51
+ invalidGatewayUrl: "Gateway URL rejected: it must not embed credentials; remote addresses must use https, plain http is only allowed for loopback",
52
+ invalidKeyRef: "Credential reference rejected: use letters, digits and underscores only, and do not start with a digit",
400
53
  outOfRange: "Out of the allowed range — not saved",
401
54
  gateSection: "Gate",
402
55
  memorySection: "Memory feedback",
@@ -405,18 +58,82 @@ const UI_MESSAGES = {
405
58
  dirtyCount: "{count} changed",
406
59
  enabledLabel: "Enable the quality gate",
407
60
  enabledHint: "When off: no gate runs at turn end (the model may close a turn with broken code)",
408
- injectsLabel: "Repair injections per turn",
409
- injectsHint: "How many repair instructions a failing gate may inject per turn (1-5, default 2 — runaway protection)",
410
- budgetLabel: "Gate budget",
411
- budgetHint: "Max duration of one gate run (10s-600s, default 300s; a timeout injects a failure instead of passing silently)",
61
+ injectsLabel: "Repair injections per session",
62
+ injectsHint: "How many repair instructions a failing gate may inject; the quota accumulates across turns in one session and resets after a clean gate pass",
63
+ budgetLabel: "Gate total budget",
64
+ budgetHint: "Shared time budget for all gates in one turn (split across project roots); a timeout is injected as \"could not run\", never as a silent pass",
412
65
  memoryLabel: "Persist gate failures to memory",
413
- memoryHint: "Off by default. When on: the evidence of code-level gate failures (command, root, full error text, untruncated) is written into the memory gateway's L0 session layer so later sessions can recall it; synchronous reporting to the lesson-loop bus is unaffected by this switch. When off, repairs are injected but nothing is persisted. Whether it can actually deliver is decided by the gateway URL resolving to a value (see the two fields below)",
66
+ memoryHint: "Off by default. When on: the evidence of code-level gate failures (command, root, full error text, untruncated) is written into the memory gateway's L0 session layer so later sessions can recall it; synchronous reporting to the lesson-loop bus is unaffected by this switch. When off, repairs are injected but nothing is persisted. Delivery requires both the gateway URL and the credential ref to be usable (see the two fields below)",
414
67
  gatewayUrlLabel: "Memory gateway URL",
415
- gatewayUrlHint: "The plugin ships no default address: fill in your own gateway URL (e.g. http://127.0.0.1:8420), or leave it empty if you have no gateway. When this is empty the TDAI_GATEWAY_URL environment variable is used instead; only when both are empty is memory feedback unconfigured and disabled",
68
+ gatewayUrlHint: "The plugin ships no default address: fill in your own gateway URL (e.g. http://127.0.0.1:8420), or leave it empty if you have no gateway. When this is empty the TDAI_GATEWAY_URL environment variable is used instead; only when both are empty is memory feedback unconfigured and disabled. Remote addresses must use https; plain http is only allowed for loopback (127.0.0.0/8, ::1, localhost)",
416
69
  keyRefLabel: "Gateway credential ref",
417
70
  keyRefHint: "Name of the credential holding the gateway token (POSIX env-var form, default TDAI_GATEWAY_KEY). Only the ref name is stored here — the secret itself is managed by the dsh credentials service (the web credentials page or the refs in ~/.dsh/.credentials.yaml) and never lands in settings.yaml; clearing it restores the default ref name rather than disabling memory feedback"
418
71
  }
419
72
  };
73
+ // lib/credential-url.ts
74
+ /** IPv4 单段。 */
75
+ const IPV4_OCTET = /^\d{1,3}$/u;
76
+ /** IPv4-mapped IPv6 的字面前缀。 */
77
+ const IPV4_MAPPED_PREFIX = "::ffff:";
78
+ /**
79
+ * 是否为 IPv4-mapped 的 IPv4 回环(`::ffff:127.0.0.0/8`,URL 会归一成 `::ffff:7fxx:xxxx`)。
80
+ *
81
+ * 必须按末 32 位的**实际数值**判,不能按字符串前缀 `::ffff:7f`:前缀命中的
82
+ * `::ffff:7fa:0` 末 32 位是 0x07fa0000 = 7.250.0.0,并不在 127.0.0.0/8 里。按前缀放行
83
+ * 等于把 Bearer 令牌发往远端明文 http,与本函数的安全承诺相反(fail-open 比不判更糟)。
84
+ * 非规范的展开写法(如 `::ffff:0:7f00:1`)判否——fail-closed,可接受。
85
+ *
86
+ * @param host - `URL.hostname` 去掉方括号后的主机名
87
+ * @returns 是否为 IPv4 回环的映射写法
88
+ */
89
+ function isIpv4MappedLoopback(host) {
90
+ if (!host.startsWith(IPV4_MAPPED_PREFIX)) return false;
91
+ const segments = host.slice(7).split(":");
92
+ if (segments.length !== 2) return false;
93
+ const high = Number.parseInt(String(segments[0]), 16);
94
+ return high >= 32512 && high <= 32767;
95
+ }
96
+ /**
97
+ * 是否为 IPv4 回环(127.0.0.0/8 整段,不只是 127.0.0.1)。
98
+ *
99
+ * @param host - `URL.hostname` 去掉方括号后的主机名
100
+ * @returns 是否落在 127.0.0.0/8
101
+ */
102
+ function isIpv4Loopback(host) {
103
+ const parts = host.split(".");
104
+ if (parts.length !== 4 || parts[0] !== "127") return false;
105
+ return parts.every((part) => IPV4_OCTET.test(part) && Number(part) <= 255);
106
+ }
107
+ /**
108
+ * 凭据只允许发往 https 或回环地址:明文 http 到远端会把 Bearer 令牌暴露给同网段
109
+ * (设置卡默认值就是本机 http,属有意用法)。地址里带 userinfo 一律判否——那是另一条
110
+ * 会随 URL 一起写进设置文件的凭据形态,与本插件把密钥交给凭据服务的设计冲突。
111
+ *
112
+ * 回环按整段判定而不是三个字面量:IPv4 回环是 127.0.0.0/8,网关绑在 127.0.0.2 /
113
+ * 127.0.1.1 时同样只在本机,判成"不安全"会让记忆反馈静默停掉、而用户从提示里看不出
114
+ * 自己其实就在本机;IPv6 的 IPv4-mapped 写法(URL 把 `[::ffff:127.0.0.1]` 归一成
115
+ * `[::ffff:7f00:1]`)同理。
116
+ *
117
+ * `localhost` 这一支是**有意保留的 fail-open**:它依赖 hosts/DNS 解析,hosts 被指向远端时
118
+ * 本函数仍判安全。保留它是为了让设置卡的默认写法可用;要彻底关掉这个口子只能改成只认 IP
119
+ * 字面量,那会让"本机网关"这条最常见用法需要用户自己去查 IP。
120
+ *
121
+ * @param url - 待判定的完整 URL
122
+ * @returns 是否可以携带凭据发往该地址
123
+ */
124
+ function isCredentialSafeUrl(url) {
125
+ let parsed;
126
+ try {
127
+ parsed = new URL(url);
128
+ } catch {
129
+ return false;
130
+ }
131
+ if (parsed.username !== "" || parsed.password !== "") return false;
132
+ if (parsed.protocol === "https:") return true;
133
+ if (parsed.protocol !== "http:") return false;
134
+ const host = parsed.hostname.replaceAll(/^\[|\]$/gu, "");
135
+ return host === "localhost" || isIpv4Loopback(host) || host === "::1" || isIpv4MappedLoopback(host);
136
+ }
420
137
  // src/client-entry.ts
421
138
  const NS = "quality-gate";
422
139
  const BUNDLE_PKG = "@jayyuen66/dsh-quality-gate";
@@ -441,18 +158,23 @@ const CARD_NUMBER_SCHEMAS = {
441
158
  maxInjectsPerTurn: MAX_INJECTS_VALUE,
442
159
  gateBudgetMs: GATE_BUDGET_MS_VALUE
443
160
  };
161
+ /** 版本可移植的闭区间整数判:宿主平台冻结的 zod(实测 4.4.3)没有 `.validate`
162
+ * (4.6 才回到 classic 面),判据用 `boundsOf` 折出的边界自持,不押 zod 的方法面——
163
+ * 否则单仓测试(catalog 4.6.7)全绿、真宿主里槽位崩溃、设置表单静默消失。 */
164
+ function inBounds(bounds, value) {
165
+ return Number.isInteger(value) && (bounds.min ?? Number.NEGATIVE_INFINITY) <= value && value <= (bounds.max ?? Number.POSITIVE_INFINITY);
166
+ }
444
167
  /** 可选整数:越界/小数/非有限值折成 undefined,只丢那一格而不是整份快照。
445
- * `.validate()` 只回答合法性、不建 ZodError,失败路径比 `.safeParse().success` 短。
446
168
  * 不引 `z.compile`:编译器给客户端 bundle 增约 28 KB,且 CSP 环境会拒绝 `new Function`;
447
169
  * 卡片每帧只解析一枚快照对象,收益抵不上这两条代价。 */
448
- const MAX_INJECTS_SCHEMA = zod.z.preprocess((value) => typeof value === "number" && MAX_INJECTS_VALUE.validate(value) ? value : void 0, MAX_INJECTS_VALUE.optional());
449
- const GATE_BUDGET_MS_SCHEMA = zod.z.preprocess((value) => typeof value === "number" && GATE_BUDGET_MS_VALUE.validate(value) ? value : void 0, GATE_BUDGET_MS_VALUE.optional());
170
+ const MAX_INJECTS_SCHEMA = zod.z.preprocess((value) => typeof value === "number" && inBounds(MAX_INJECTS_BOUNDS, value) ? value : void 0, MAX_INJECTS_VALUE.optional());
171
+ const GATE_BUDGET_MS_SCHEMA = zod.z.preprocess((value) => typeof value === "number" && inBounds(GATE_BUDGET_MS_BOUNDS, value) ? value : void 0, GATE_BUDGET_MS_VALUE.optional());
450
172
  /** 卡片可编辑字段的值面。逐字段先把错误类型折成 undefined:宿主给了坏形状时只丢掉
451
173
  * 那一格,其余字段照常渲染;整份快照不是对象时才整体回落到空表。 */
452
174
  const optionalBoolean = zod.z.preprocess((value) => typeof value === "boolean" ? value : void 0, zod.z.boolean().optional());
453
175
  const optionalString = zod.z.preprocess((value) => typeof value === "string" ? value : void 0, zod.z.string().optional());
454
- /** 网关地址是 HTTP(S) URL;空串与坏文本折成未设置。本地回环/端口由 `z.url` 放行。 */
455
- const HTTP_URL_SCHEMA = zod.z.url({ protocol: /^https?$/u });
176
+ /** 网关地址:必须是 http(s),且与宿主运行期同一份判据——不能带 userinfo,远端只能 https,
177
+ * 明文 http 只允许本机回环。空串与坏文本折成未设置。 */
456
178
  const optionalHttpUrl = zod.z.preprocess((value) => {
457
179
  let text;
458
180
  if (typeof value === "string") {
@@ -460,7 +182,7 @@ const optionalHttpUrl = zod.z.preprocess((value) => {
460
182
  if (trimmed !== "") text = trimmed;
461
183
  }
462
184
  return text;
463
- }, HTTP_URL_SCHEMA.optional());
185
+ }, zod.z.string().refine(isCredentialSafeUrl, { error: "credential-unsafe-url" }).optional());
464
186
  const CARD_VALUES = zod.z.object({
465
187
  enabled: optionalBoolean,
466
188
  maxInjectsPerTurn: MAX_INJECTS_SCHEMA,
@@ -469,8 +191,8 @@ const CARD_VALUES = zod.z.object({
469
191
  memoryGatewayUrl: optionalHttpUrl,
470
192
  memoryGatewayKeyRef: optionalString
471
193
  });
472
- /** 草稿面仍按宽 string 读:`TextField` 没有 invalid 态,URL 收口只放在快照投影上,
473
- * 用户输入中的半截地址不会被当场折成空串。 */
194
+ /** 草稿面仍按宽 string 读:`TextField` 没有 invalid 态,输入中的半截地址不会被当场折成空串;
195
+ * 提交时由 `writeTouched` 用同一份凭据地址判据拦下,不合规的地址不落盘。 */
474
196
  const CARD_FIELD_SCHEMAS = {
475
197
  enabled: optionalBoolean,
476
198
  maxInjectsPerTurn: MAX_INJECTS_SCHEMA,
@@ -479,15 +201,23 @@ const CARD_FIELD_SCHEMAS = {
479
201
  memoryGatewayUrl: optionalString,
480
202
  memoryGatewayKeyRef: optionalString
481
203
  };
482
- /** 快照值投影:整份一次解析;每个字段的坏形状与越界值各自折成 undefined,其余字段照常渲染。 */
204
+ /** 快照值投影:逐字段解析;坏形状与越界值只折掉那一格,其余字段照常渲染。
205
+ * 整份一次 `safeParse` 会让一个坏 URL 把 enabled 与两个数字字段一起清空。 */
483
206
  function projectCardValues(raw) {
484
- const parsed = CARD_VALUES.safeParse(raw);
485
- return parsed.success ? parsed.data : {};
207
+ const out = {};
208
+ for (const [field, schema] of Object.entries(CARD_VALUES.shape)) {
209
+ const parsed = schema.safeParse(raw[field]);
210
+ if (parsed.success) out[field] = parsed.data;
211
+ }
212
+ return out;
486
213
  }
487
214
  /** 值语义相等(与 shared 的 diffTouched 同口径):表单值是标量与数组,引用相等会把等值数组判成改动。 */
488
215
  function sameValue(left, right) {
489
216
  return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
490
217
  }
218
+ /** 凭据引用名形状:与 @deepseek-ai/dsh-credentials 的 REF_PATTERN 同式。客户端不引该包的
219
+ * 值导出(那是宿主侧凭据服务面),所以这里按同一正则复判;两处失配时宿主运行期仍会拒绝。 */
220
+ const CREDENTIAL_REF_PATTERN = /^[A-Za-z_]\w*$/u;
491
221
  /**
492
222
  * 逐键 await 写入并**消费受理位**(0.1.7 的 set/unset 回 Promise<boolean>:
493
223
  * true=宿主受理;false=拒绝或写入被跳过(官方语义:"refuses stale revisions";
@@ -503,6 +233,16 @@ async function writeTouched(channels) {
503
233
  try {
504
234
  for (const key of keys) {
505
235
  const val = touched[key];
236
+ if (key === "memoryGatewayUrl" && typeof val === "string" && val.trim() !== "" && !isCredentialSafeUrl(val.trim())) {
237
+ setBusy(false);
238
+ setSaveError(t("invalidGatewayUrl"));
239
+ return;
240
+ }
241
+ if (key === "memoryGatewayKeyRef" && typeof val === "string" && val.trim() !== "" && !CREDENTIAL_REF_PATTERN.test(val.trim())) {
242
+ setBusy(false);
243
+ setSaveError(t("invalidKeyRef"));
244
+ return;
245
+ }
506
246
  if (!await (val === void 0 ? unset(key) : set(key, val))) {
507
247
  setBusy(false);
508
248
  setSaveError(t("saveRejected"));
@@ -529,9 +269,9 @@ function buildGateSection(props) {
529
269
  const budgetSeconds = budgetMs === void 0 ? void 0 : budgetMs / 1e3;
530
270
  const budgetMinSeconds = (GATE_BUDGET_MS_BOUNDS.min ?? 0) / 1e3;
531
271
  const budgetMaxSeconds = (GATE_BUDGET_MS_BOUNDS.max ?? 0) / 1e3;
532
- const enabled = (0, react.createElement)(_, {
272
+ const enabled = (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.ToggleField, {
533
273
  label: t("enabledLabel"),
534
- ...l(t("enabledHint")),
274
+ ...(0, _jayyuen66_dsh_plugin_shared_client.splitHint)(t("enabledHint")),
535
275
  field: "enabled",
536
276
  checked: eff("enabled") ?? true,
537
277
  disabled: !writable,
@@ -539,9 +279,9 @@ function buildGateSection(props) {
539
279
  setField("enabled", next);
540
280
  }
541
281
  });
542
- const injects = (0, react.createElement)(v, {
282
+ const injects = (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.NumberField, {
543
283
  label: t("injectsLabel"),
544
- ...l(t("injectsHint")),
284
+ ...(0, _jayyuen66_dsh_plugin_shared_client.splitHint)(t("injectsHint")),
545
285
  field: "maxInjectsPerTurn",
546
286
  value: maxInjects,
547
287
  ...MAX_INJECTS_BOUNDS,
@@ -553,9 +293,9 @@ function buildGateSection(props) {
553
293
  setField("maxInjectsPerTurn", next);
554
294
  }
555
295
  });
556
- const budget = (0, react.createElement)(v, {
296
+ const budget = (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.NumberField, {
557
297
  label: t("budgetLabel"),
558
- ...l(t("budgetHint")),
298
+ ...(0, _jayyuen66_dsh_plugin_shared_client.splitHint)(t("budgetHint")),
559
299
  field: "gateBudgetMs",
560
300
  value: budgetSeconds,
561
301
  min: budgetMinSeconds,
@@ -568,16 +308,16 @@ function buildGateSection(props) {
568
308
  setField("gateBudgetMs", next === void 0 ? void 0 : next * 1e3);
569
309
  }
570
310
  });
571
- return (0, react.createElement)(d, { title: t("gateSection") }, enabled, injects, budget);
311
+ return (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.Section, { title: t("gateSection") }, enabled, injects, budget);
572
312
  }
573
313
  /** 记忆反馈小节:开关 + 网关地址 + 密钥引用。 */
574
314
  function buildMemorySection(props) {
575
315
  const { t, writable, eff, setField } = props;
576
316
  const gatewayUrl = eff("memoryGatewayUrl") ?? "";
577
317
  const keyRef = eff("memoryGatewayKeyRef") ?? "";
578
- const memory = (0, react.createElement)(_, {
318
+ const memory = (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.ToggleField, {
579
319
  label: t("memoryLabel"),
580
- ...l(t("memoryHint")),
320
+ ...(0, _jayyuen66_dsh_plugin_shared_client.splitHint)(t("memoryHint")),
581
321
  field: "memoryFeedback",
582
322
  checked: eff("memoryFeedback") === true,
583
323
  disabled: !writable,
@@ -585,9 +325,9 @@ function buildMemorySection(props) {
585
325
  setField("memoryFeedback", next);
586
326
  }
587
327
  });
588
- const url = (0, react.createElement)(y, {
328
+ const url = (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.TextField, {
589
329
  label: t("gatewayUrlLabel"),
590
- ...l(t("gatewayUrlHint")),
330
+ ...(0, _jayyuen66_dsh_plugin_shared_client.splitHint)(t("gatewayUrlHint")),
591
331
  field: "memoryGatewayUrl",
592
332
  value: gatewayUrl,
593
333
  placeholder: "http://127.0.0.1:8420",
@@ -597,9 +337,9 @@ function buildMemorySection(props) {
597
337
  setField("memoryGatewayUrl", next.trim() === "" ? void 0 : next.trim());
598
338
  }
599
339
  });
600
- const keyRefField = (0, react.createElement)(y, {
340
+ const keyRefField = (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.TextField, {
601
341
  label: t("keyRefLabel"),
602
- ...l(t("keyRefHint")),
342
+ ...(0, _jayyuen66_dsh_plugin_shared_client.splitHint)(t("keyRefHint")),
603
343
  field: "memoryGatewayKeyRef",
604
344
  value: keyRef,
605
345
  placeholder: "TDAI_GATEWAY_KEY",
@@ -609,7 +349,7 @@ function buildMemorySection(props) {
609
349
  setField("memoryGatewayKeyRef", next.trim() === "" ? void 0 : next.trim());
610
350
  }
611
351
  });
612
- return (0, react.createElement)(d, { title: t("memorySection") }, memory, url, keyRefField);
352
+ return (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.Section, { title: t("memorySection") }, memory, url, keyRefField);
613
353
  }
614
354
  function DgCard(props) {
615
355
  const { t } = props;
@@ -625,7 +365,7 @@ function DgCard(props) {
625
365
  const parsedTouched = CARD_FIELD_SCHEMAS[field].safeParse(touched[field]);
626
366
  return parsedTouched.success ? parsedTouched.data : void 0;
627
367
  };
628
- const dirtyKeys = t$1(touched, value);
368
+ const dirtyKeys = (0, _jayyuen66_dsh_plugin_shared_client.diffTouched)(touched, value);
629
369
  const dirty = dirtyKeys.length > 0;
630
370
  const setField = (field, val) => {
631
371
  setTouched((prev) => ({
@@ -634,7 +374,7 @@ function DgCard(props) {
634
374
  }));
635
375
  };
636
376
  const save = () => {
637
- const keys = t$1(touched, value);
377
+ const keys = (0, _jayyuen66_dsh_plugin_shared_client.diffTouched)(touched, value);
638
378
  if (keys.length === 0) return;
639
379
  setBusy(true);
640
380
  setSaveError(null);
@@ -661,7 +401,7 @@ function DgCard(props) {
661
401
  };
662
402
  const gate = buildGateSection(inputs);
663
403
  const memory = buildMemorySection(inputs);
664
- const saveBar = (0, react.createElement)(S, {
404
+ const saveBar = (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.SaveBar, {
665
405
  dirty,
666
406
  busy,
667
407
  writable,
@@ -675,7 +415,7 @@ function DgCard(props) {
675
415
  onSave: save,
676
416
  onDiscard: discard
677
417
  });
678
- return (0, react.createElement)(u, null, gate, memory, saveBar);
418
+ return (0, react.createElement)(_jayyuen66_dsh_plugin_shared_client.CardForm, null, gate, memory, saveBar);
679
419
  }
680
420
  function cardStore(scope) {
681
421
  let cachedSnap = null;
@@ -712,7 +452,7 @@ function apply(ctx) {
712
452
  ctx.effect(() => {
713
453
  const tag = document.createElement("style");
714
454
  tag.id = "dsh-card-ui-css-quality-gate";
715
- tag.textContent = c;
455
+ tag.textContent = _jayyuen66_dsh_plugin_shared_client.CARD_UI_CSS;
716
456
  document.head.append(tag);
717
457
  return () => {
718
458
  tag.remove();