@jayyuen66/dsh-quality-gate 0.1.3 → 0.1.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/client.js CHANGED
@@ -7,21 +7,353 @@ window.__ModuleLoader__.load({
7
7
  let exports = module.exports;
8
8
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
9
9
  let react = require("react");
10
+ let zod = require("zod");
10
11
  // node_modules/@jayyuen66/dsh-plugin-shared/dist/card-diff.js
11
- function e(t, n, r) {
12
+ function e$1(t, n, r) {
12
13
  if (t == null || n == null) return (t ?? null) === (n ?? null);
13
14
  if (Object.is(t, n)) return !0;
14
15
  if (typeof t != `object` || typeof n != `object`) return !1;
15
16
  if (r.get(t) === n) return !0;
16
- 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(t, n[i], r));
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));
17
18
  let i = Object.keys(t), a = Object.keys(n);
18
- return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e(Reflect.get(t, i), Reflect.get(n, i), r));
19
+ return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e$1(Reflect.get(t, i), Reflect.get(n, i), r));
19
20
  }
20
- function t(t, n) {
21
+ function t$1(t, n) {
21
22
  let r = [];
22
- for (let i of Object.keys(t)) e(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
23
+ for (let i of Object.keys(t)) e$1(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
23
24
  return r;
24
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
+ }
25
357
  // src/ui-messages.ts
26
358
  const UI_MESSAGES = {
27
359
  zh: {
@@ -36,11 +368,16 @@ const UI_MESSAGES = {
36
368
  saveFailed: "保存失败:{message}",
37
369
  saveRejected: "保存被宿主拒绝(配置校验未通过或写入被跳过),修改未生效",
38
370
  outOfRange: "超出允许范围,未保存",
371
+ gateSection: "门禁",
372
+ memorySection: "记忆反馈",
373
+ unitTimes: "次",
374
+ unitSeconds: "秒",
375
+ dirtyCount: "已修改 {count} 项",
39
376
  enabledLabel: "启用质量门禁",
40
377
  enabledHint: "关闭后回合收口不再跑门禁(模型可带病收口)",
41
378
  injectsLabel: "每回合注入配额",
42
379
  injectsHint: "门禁失败每回合最多注入几次修复指令(1-5,默认 2,防跑飞)",
43
- budgetLabel: "门禁预算(毫秒)",
380
+ budgetLabel: "门禁预算",
44
381
  budgetHint: "单次门禁最大耗时(10s-600s,默认 300s;超时按失败注入不静默放行)",
45
382
  memoryLabel: "门禁失败沉淀记忆",
46
383
  memoryHint: "默认关闭。开启后:代码型门禁失败的证据(命令/根/全文报错,不截断)写入记忆网关的 L0 会话层供后续会话召回;总线 lesson-loop 的同步上报不受此开关影响。关闭后只注入不沉淀。真正决定能否投递的是网关地址能否解析出值(见下面两项)",
@@ -61,11 +398,16 @@ const UI_MESSAGES = {
61
398
  saveFailed: "Save failed: {message}",
62
399
  saveRejected: "Save rejected by the host (validation failed or write skipped) — changes not applied",
63
400
  outOfRange: "Out of the allowed range — not saved",
401
+ gateSection: "Gate",
402
+ memorySection: "Memory feedback",
403
+ unitTimes: "times",
404
+ unitSeconds: "seconds",
405
+ dirtyCount: "{count} changed",
64
406
  enabledLabel: "Enable the quality gate",
65
407
  enabledHint: "When off: no gate runs at turn end (the model may close a turn with broken code)",
66
408
  injectsLabel: "Repair injections per turn",
67
409
  injectsHint: "How many repair instructions a failing gate may inject per turn (1-5, default 2 — runaway protection)",
68
- budgetLabel: "Gate budget (milliseconds)",
410
+ budgetLabel: "Gate budget",
69
411
  budgetHint: "Max duration of one gate run (10s-600s, default 300s; a timeout injects a failure instead of passing silently)",
70
412
  memoryLabel: "Persist gate failures to memory",
71
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)",
@@ -78,133 +420,69 @@ const UI_MESSAGES = {
78
420
  // src/client-entry.ts
79
421
  const NS = "quality-gate";
80
422
  const BUNDLE_PKG = "@jayyuen66/dsh-quality-gate";
81
- const CARD_CSS = [
82
- ".qgc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}",
83
- ".qgc-card:hover{border-color:var(--dsw-alias-label-dimmed)}",
84
- ".qgc-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
85
- ".qgc-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:transparent;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}",
86
- ".qgc-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
87
- ".qgc-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
88
- ".qgc-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
89
- ".qgc-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}",
90
- ".qgc-chevron-open{transform:rotate(180deg)}",
91
- ".qgc-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:8px 0 12px}",
92
- ".qgc-row{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;padding:9px 0}",
93
- ".qgc-label{font-size:13px;color:var(--dsw-alias-label-primary,inherit)}",
94
- ".qgc-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99);line-height:1.5;margin-top:2px}",
95
- ".qgc-switch{appearance:none;position:relative;width:34px;height:20px;border-radius:10px;background:var(--dsw-alias-fill-primary,#d8dbe2);transition:background .16s;cursor:pointer;border:0;flex:none}",
96
- ".qgc-switch::after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#fff);transition:left .16s}",
97
- ".qgc-switch-on{background:var(--dsw-alias-brand-primary,#e07856)}",
98
- ".qgc-switch-on::after{left:16px}",
99
- ".qgc-switch:disabled{cursor:not-allowed;opacity:.6}",
100
- ".qgc-input{width:100%;box-sizing:border-box;font:inherit;font-size:12px;color:var(--dsw-alias-label-primary,inherit);background:var(--dsw-alias-bg-layer-1,transparent);border:1px solid var(--dsw-alias-border-l2,transparent);border-radius:8px;padding:6px 8px;margin-top:4px}",
101
- ".qgc-savebar{display:flex;gap:8px;align-items:center;padding:10px 0 2px;border-top:1px dashed var(--dsw-alias-border-l2);margin-top:6px;flex-wrap:wrap}",
102
- ".qgc-btn{appearance:none;font:inherit;font-size:12px;cursor:pointer;border-radius:6px;padding:4px 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,inherit)}",
103
- ".qgc-btn:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed)}",
104
- ".qgc-btn-primary{background:var(--dsw-alias-brand-primary,#e07856);border-color:var(--dsw-alias-brand-primary,#e07856);color:var(--dsw-alias-label-primary-foreground,#fff)}",
105
- ".qgc-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#e07856);outline-offset:1px}",
106
- ".qgc-btn:disabled{opacity:.5;cursor:not-allowed}",
107
- ".qgc-dirty{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99)}",
108
- ".qgc-saveerr{font-size:12px;color:#c4483f}"
109
- ].join("\n");
110
- function ToggleRow(props) {
111
- return (0, react.createElement)("div", { className: "qgc-row" }, (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "qgc-label" }, props.label), (0, react.createElement)("div", { className: "qgc-hint" }, props.hint)), (0, react.createElement)("button", {
112
- type: "button",
113
- className: `qgc-switch${props.checked ? " qgc-switch-on" : ""}`,
114
- role: "switch",
115
- "aria-checked": props.checked,
116
- "data-field": props.field,
117
- disabled: props.disabled === true,
118
- onClick: props.onToggle
119
- }));
120
- }
121
- /** 数字输入行:settings 数字字段 ↔ UI 字符串(v7 配置面补齐)。 */
122
- function NumberInputRow(props) {
123
- const num = typeof props.value === "number" && Number.isFinite(props.value) ? String(props.value) : "";
124
- const [text, setText] = (0, react.useState)(num);
125
- const [invalid, setInvalid] = (0, react.useState)(false);
126
- (0, react.useEffect)(() => {
127
- setText(num);
128
- setInvalid(false);
129
- }, [num]);
130
- const invalidHint = props.t("outOfRange");
131
- return (0, react.createElement)("div", { className: "qgc-row" }, (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "qgc-label" }, props.label), (0, react.createElement)("div", { className: "qgc-hint" }, props.hint), (0, react.createElement)("input", {
132
- type: "number",
133
- className: "qgc-input",
134
- value: text,
135
- disabled: props.disabled === true,
136
- "aria-invalid": invalid || void 0,
137
- onChange: (event) => {
138
- const next = event.target.value;
139
- setText(next);
140
- if (next === "") {
141
- setInvalid(false);
142
- props.onClear(props.field);
143
- return;
144
- }
145
- const numeric = Number(next);
146
- if (!Number.isFinite(numeric)) {
147
- setInvalid(true);
148
- return;
149
- }
150
- const min = props.min ?? -Infinity;
151
- const max = props.max ?? Infinity;
152
- if (numeric >= min && numeric <= max) {
153
- setInvalid(false);
154
- props.onChange(props.field, numeric);
155
- } else setInvalid(true);
156
- }
157
- }), invalid ? (0, react.createElement)("div", {
158
- className: "qgc-hint",
159
- style: { color: "var(--dsw-alias-danger, #e5484d)" }
160
- }, invalidHint) : null));
161
- }
162
423
  /**
163
- * 文本输入行:settings 字符串字段 ↔ UI 字符串。
164
- * 清空 = unset(回到 schema 默认值)——对本卡的两个网关字段,默认值就是"留空 = 不启用"。
424
+ * 两个数值字段的闭区间只写在 zod schema 上:host schema(`Schema.natural().min/max`)
425
+ * 与卡片输入框都从这里取。zod 4.6 的 `.minValue`/`.maxValue` 直接读出折叠后的边界,
426
+ * `NumberField` 的 `min/max` 因此不再另抄一份数字。
165
427
  */
166
- function TextInputRow(props) {
167
- const text = typeof props.value === "string" ? props.value : "";
168
- const [draft, setDraft] = (0, react.useState)(text);
169
- (0, react.useEffect)(() => {
170
- setDraft(text);
171
- }, [text]);
172
- return (0, react.createElement)("div", { className: "qgc-row" }, (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "qgc-label" }, props.label), (0, react.createElement)("div", { className: "qgc-hint" }, props.hint), (0, react.createElement)("input", {
173
- type: "text",
174
- className: "qgc-input",
175
- value: draft,
176
- placeholder: props.placeholder ?? "",
177
- disabled: props.disabled === true,
178
- onChange: (event) => {
179
- const next = event.target.value;
180
- setDraft(next);
181
- if (next.trim() === "") {
182
- props.onClear(props.field);
183
- return;
184
- }
185
- props.onChange(props.field, next.trim());
186
- }
187
- })));
428
+ const MAX_INJECTS_VALUE = zod.z.int().min(1).max(5);
429
+ const GATE_BUDGET_MS_VALUE = zod.z.int().min(1e4).max(6e5);
430
+ /** 把 zod 折叠后的显式边界摊成可选 props(null = 该方向没声明,不伪造一个上限)。 */
431
+ function boundsOf(schema) {
432
+ const bounds = {};
433
+ if (schema.minValue !== null) bounds.min = schema.minValue;
434
+ if (schema.maxValue !== null) bounds.max = schema.maxValue;
435
+ return bounds;
188
436
  }
189
- function SaveBar(props) {
190
- const { t } = props;
191
- const dis = !props.writable || props.busy;
192
- let dirtyText;
193
- if (props.writable) dirtyText = props.dirty ? t("statusDirty") : t("statusClean");
194
- else dirtyText = t("statusReadOnly");
195
- return (0, react.createElement)("div", { className: "qgc-savebar" }, (0, react.createElement)("button", {
196
- type: "button",
197
- className: "qgc-btn qgc-btn-primary",
198
- "data-field": "save",
199
- disabled: dis || !props.dirty,
200
- onClick: props.onSave
201
- }, props.busy ? t("saving") : t("save")), (0, react.createElement)("button", {
202
- type: "button",
203
- className: "qgc-btn",
204
- "data-field": "discard",
205
- disabled: dis || !props.dirty,
206
- onClick: props.onDiscard
207
- }, t("revert")), props.error === null ? (0, react.createElement)("span", { className: "qgc-dirty" }, dirtyText) : (0, react.createElement)("span", { className: "qgc-saveerr" }, props.error));
437
+ const MAX_INJECTS_BOUNDS = boundsOf(MAX_INJECTS_VALUE);
438
+ const GATE_BUDGET_MS_BOUNDS = boundsOf(GATE_BUDGET_MS_VALUE);
439
+ /** 两个数值 schema 的对外投影:测试用 `.minValue`/`.maxValue` 与 `z.toJSONSchema` 钉边界。 */
440
+ const CARD_NUMBER_SCHEMAS = {
441
+ maxInjectsPerTurn: MAX_INJECTS_VALUE,
442
+ gateBudgetMs: GATE_BUDGET_MS_VALUE
443
+ };
444
+ /** 可选整数:越界/小数/非有限值折成 undefined,只丢那一格而不是整份快照。
445
+ * `.validate()` 只回答合法性、不建 ZodError,失败路径比 `.safeParse().success` 短。
446
+ * 不引 `z.compile`:编译器给客户端 bundle 增约 28 KB,且 CSP 环境会拒绝 `new Function`;
447
+ * 卡片每帧只解析一枚快照对象,收益抵不上这两条代价。 */
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());
450
+ /** 卡片可编辑字段的值面。逐字段先把错误类型折成 undefined:宿主给了坏形状时只丢掉
451
+ * 那一格,其余字段照常渲染;整份快照不是对象时才整体回落到空表。 */
452
+ const optionalBoolean = zod.z.preprocess((value) => typeof value === "boolean" ? value : void 0, zod.z.boolean().optional());
453
+ 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 });
456
+ const optionalHttpUrl = zod.z.preprocess((value) => {
457
+ let text;
458
+ if (typeof value === "string") {
459
+ const trimmed = value.trim();
460
+ if (trimmed !== "") text = trimmed;
461
+ }
462
+ return text;
463
+ }, HTTP_URL_SCHEMA.optional());
464
+ const CARD_VALUES = zod.z.object({
465
+ enabled: optionalBoolean,
466
+ maxInjectsPerTurn: MAX_INJECTS_SCHEMA,
467
+ gateBudgetMs: GATE_BUDGET_MS_SCHEMA,
468
+ memoryFeedback: optionalBoolean,
469
+ memoryGatewayUrl: optionalHttpUrl,
470
+ memoryGatewayKeyRef: optionalString
471
+ });
472
+ /** 草稿面仍按宽 string 读:`TextField` 没有 invalid 态,URL 收口只放在快照投影上,
473
+ * 用户输入中的半截地址不会被当场折成空串。 */
474
+ const CARD_FIELD_SCHEMAS = {
475
+ enabled: optionalBoolean,
476
+ maxInjectsPerTurn: MAX_INJECTS_SCHEMA,
477
+ gateBudgetMs: GATE_BUDGET_MS_SCHEMA,
478
+ memoryFeedback: optionalBoolean,
479
+ memoryGatewayUrl: optionalString,
480
+ memoryGatewayKeyRef: optionalString
481
+ };
482
+ /** 快照值投影:整份一次解析;每个字段的坏形状与越界值各自折成 undefined,其余字段照常渲染。 */
483
+ function projectCardValues(raw) {
484
+ const parsed = CARD_VALUES.safeParse(raw);
485
+ return parsed.success ? parsed.data : {};
208
486
  }
209
487
  /** 值语义相等(与 shared 的 diffTouched 同口径):表单值是标量与数组,引用相等会把等值数组判成改动。 */
210
488
  function sameValue(left, right) {
@@ -243,162 +521,125 @@ async function writeTouched(channels) {
243
521
  setSaveError(t("saveFailed", { message: String(error instanceof Error ? error.message : error) }));
244
522
  }
245
523
  }
246
- /** 头部:渲染子树扁平化(规避 max-nested-calls)——DOM 各层拆成独立 createElement 元素
247
- * const,逐层用变量组合,避免 createElement 回调逐层内嵌。 */
248
- function buildCardHeader(props) {
249
- const { t, open, onToggle } = props;
250
- const nameEl = (0, react.createElement)("div", { className: "qgc-name" }, t("cardTitle"));
251
- const descEl = (0, react.createElement)("div", { className: "qgc-desc" }, t("cardDescription"));
252
- const headDiv = (0, react.createElement)("div", { className: "qgc-head" }, nameEl, descEl);
253
- const chevronPathData = "M3 5l4 4 4-4";
254
- const chevronPath = (0, react.createElement)("path", {
255
- ["d"]: chevronPathData,
256
- fill: "none",
257
- stroke: "currentColor",
258
- strokeWidth: 1.5,
259
- strokeLinecap: "round",
260
- strokeLinejoin: "round"
261
- });
262
- const chevron = (0, react.createElement)("svg", {
263
- width: 14,
264
- height: 14,
265
- viewBox: "0 0 14 14",
266
- "aria-hidden": true,
267
- className: `qgc-chevron${open ? " qgc-chevron-open" : ""}`
268
- }, chevronPath);
269
- return (0, react.createElement)("button", {
270
- type: "button",
271
- className: "qgc-header",
272
- "aria-expanded": open,
273
- onClick: onToggle
274
- }, headDiv, chevron);
275
- }
276
- /** 展开态的 `ul` 本体:行元素仍按从前的顺序逐个传给同一个 createElement(不折成数组
277
- * 子节点,保持宿主侧 children 形状一致)。 */
278
- function buildCardBody(props) {
279
- const { t, writable, eff, setField, clearField, dirty, busy, saveError, onSave, onDiscard } = props;
280
- const rowEnabled = (0, react.createElement)(ToggleRow, {
524
+ /** 门禁小节:总开关 + 注入配额 + 预算(毫秒按秒显示)。 */
525
+ function buildGateSection(props) {
526
+ const { t, writable, eff, setField } = props;
527
+ const maxInjects = eff("maxInjectsPerTurn");
528
+ const budgetMs = eff("gateBudgetMs");
529
+ const budgetSeconds = budgetMs === void 0 ? void 0 : budgetMs / 1e3;
530
+ const budgetMinSeconds = (GATE_BUDGET_MS_BOUNDS.min ?? 0) / 1e3;
531
+ const budgetMaxSeconds = (GATE_BUDGET_MS_BOUNDS.max ?? 0) / 1e3;
532
+ const enabled = (0, react.createElement)(_, {
281
533
  label: t("enabledLabel"),
282
- hint: t("enabledHint"),
534
+ ...l(t("enabledHint")),
283
535
  field: "enabled",
284
- checked: eff("enabled") !== false,
536
+ checked: eff("enabled") ?? true,
285
537
  disabled: !writable,
286
- onToggle: () => {
287
- setField("enabled", eff("enabled") === false);
538
+ onChange: (next) => {
539
+ setField("enabled", next);
288
540
  }
289
541
  });
290
- const rowInjects = (0, react.createElement)(NumberInputRow, {
291
- t,
542
+ const injects = (0, react.createElement)(v, {
292
543
  label: t("injectsLabel"),
293
- hint: t("injectsHint"),
544
+ ...l(t("injectsHint")),
294
545
  field: "maxInjectsPerTurn",
295
- value: eff("maxInjectsPerTurn"),
546
+ value: maxInjects,
547
+ ...MAX_INJECTS_BOUNDS,
548
+ step: 1,
549
+ unit: t("unitTimes"),
550
+ invalidHint: t("outOfRange"),
296
551
  disabled: !writable,
297
- onChange: (field, val) => {
298
- setField(field, val);
299
- },
300
- onClear: (field) => {
301
- clearField(field);
302
- },
303
- min: 1,
304
- max: 5
552
+ onChange: (next) => {
553
+ setField("maxInjectsPerTurn", next);
554
+ }
305
555
  });
306
- const rowBudget = (0, react.createElement)(NumberInputRow, {
307
- t,
556
+ const budget = (0, react.createElement)(v, {
308
557
  label: t("budgetLabel"),
309
- hint: t("budgetHint"),
558
+ ...l(t("budgetHint")),
310
559
  field: "gateBudgetMs",
311
- value: eff("gateBudgetMs"),
560
+ value: budgetSeconds,
561
+ min: budgetMinSeconds,
562
+ max: budgetMaxSeconds,
563
+ step: 1,
564
+ unit: t("unitSeconds"),
565
+ invalidHint: t("outOfRange"),
312
566
  disabled: !writable,
313
- onChange: (field, val) => {
314
- setField(field, val);
315
- },
316
- onClear: (field) => {
317
- clearField(field);
318
- },
319
- min: 1e4,
320
- max: 6e5
567
+ onChange: (next) => {
568
+ setField("gateBudgetMs", next === void 0 ? void 0 : next * 1e3);
569
+ }
321
570
  });
322
- const rowMemory = (0, react.createElement)(ToggleRow, {
571
+ return (0, react.createElement)(d, { title: t("gateSection") }, enabled, injects, budget);
572
+ }
573
+ /** 记忆反馈小节:开关 + 网关地址 + 密钥引用。 */
574
+ function buildMemorySection(props) {
575
+ const { t, writable, eff, setField } = props;
576
+ const gatewayUrl = eff("memoryGatewayUrl") ?? "";
577
+ const keyRef = eff("memoryGatewayKeyRef") ?? "";
578
+ const memory = (0, react.createElement)(_, {
323
579
  label: t("memoryLabel"),
324
- hint: t("memoryHint"),
580
+ ...l(t("memoryHint")),
325
581
  field: "memoryFeedback",
326
582
  checked: eff("memoryFeedback") === true,
327
583
  disabled: !writable,
328
- onToggle: () => {
329
- setField("memoryFeedback", eff("memoryFeedback") !== true);
584
+ onChange: (next) => {
585
+ setField("memoryFeedback", next);
330
586
  }
331
587
  });
332
- const rowGatewayUrl = (0, react.createElement)(TextInputRow, {
588
+ const url = (0, react.createElement)(y, {
333
589
  label: t("gatewayUrlLabel"),
334
- hint: t("gatewayUrlHint"),
590
+ ...l(t("gatewayUrlHint")),
335
591
  field: "memoryGatewayUrl",
336
- value: eff("memoryGatewayUrl"),
592
+ value: gatewayUrl,
337
593
  placeholder: "http://127.0.0.1:8420",
594
+ monospace: true,
338
595
  disabled: !writable,
339
- onChange: (field, val) => {
340
- setField(field, val);
341
- },
342
- onClear: (field) => {
343
- clearField(field);
596
+ onChange: (next) => {
597
+ setField("memoryGatewayUrl", next.trim() === "" ? void 0 : next.trim());
344
598
  }
345
599
  });
346
- const rowGatewayKeyRef = (0, react.createElement)(TextInputRow, {
600
+ const keyRefField = (0, react.createElement)(y, {
347
601
  label: t("keyRefLabel"),
348
- hint: t("keyRefHint"),
602
+ ...l(t("keyRefHint")),
349
603
  field: "memoryGatewayKeyRef",
350
- value: eff("memoryGatewayKeyRef"),
604
+ value: keyRef,
351
605
  placeholder: "TDAI_GATEWAY_KEY",
606
+ monospace: true,
352
607
  disabled: !writable,
353
- onChange: (field, val) => {
354
- setField(field, val);
355
- },
356
- onClear: (field) => {
357
- clearField(field);
608
+ onChange: (next) => {
609
+ setField("memoryGatewayKeyRef", next.trim() === "" ? void 0 : next.trim());
358
610
  }
359
611
  });
360
- const rowSave = (0, react.createElement)(SaveBar, {
361
- t,
362
- dirty,
363
- writable,
364
- busy,
365
- error: saveError,
366
- onSave,
367
- onDiscard
368
- });
369
- return (0, react.createElement)("ul", { className: "qgc-body" }, rowEnabled, rowInjects, rowBudget, rowMemory, rowGatewayUrl, rowGatewayKeyRef, rowSave);
612
+ return (0, react.createElement)(d, { title: t("memorySection") }, memory, url, keyRefField);
370
613
  }
371
614
  function DgCard(props) {
372
- const { t: t$1 } = props;
373
- const [open, setOpen] = (0, react.useState)(false);
615
+ const { t } = props;
374
616
  const snap = props.useCard((cardSnap) => cardSnap);
375
- const { value } = snap;
617
+ const value = projectCardValues(snap.value);
376
618
  const writable = snap.status === "ready" && snap.writable;
377
619
  const [touched, setTouched] = (0, react.useState)({});
378
620
  const [busy, setBusy] = (0, react.useState)(false);
379
621
  const [saveError, setSaveError] = (0, react.useState)(null);
380
- /** 渲染取值:touched 优先,快照兜底(未触碰字段实时跟随外部快照)。 */
381
- const eff = (field) => field in touched ? touched[field] : value[field];
382
- const dirty = t(touched, value).length > 0;
622
+ /** 渲染取值:touched 优先(经 zod 收窄),快照兜底(未触碰字段实时跟随外部快照)。 */
623
+ const eff = (field) => {
624
+ if (!(field in touched)) return value[field];
625
+ const parsedTouched = CARD_FIELD_SCHEMAS[field].safeParse(touched[field]);
626
+ return parsedTouched.success ? parsedTouched.data : void 0;
627
+ };
628
+ const dirtyKeys = t$1(touched, value);
629
+ const dirty = dirtyKeys.length > 0;
383
630
  const setField = (field, val) => {
384
631
  setTouched((prev) => ({
385
632
  ...prev,
386
633
  [field]: val
387
634
  }));
388
635
  };
389
- const clearField = (field) => {
390
- setTouched((prev) => ({
391
- ...prev,
392
- [field]: void 0
393
- }));
394
- };
395
636
  const save = () => {
396
- const keys = t(touched, value);
637
+ const keys = t$1(touched, value);
397
638
  if (keys.length === 0) return;
398
639
  setBusy(true);
399
640
  setSaveError(null);
400
641
  writeTouched({
401
- t: t$1,
642
+ t,
402
643
  set: props.set,
403
644
  unset: props.unset,
404
645
  keys,
@@ -412,26 +653,29 @@ function DgCard(props) {
412
653
  setTouched({});
413
654
  setSaveError(null);
414
655
  };
415
- const header = buildCardHeader({
416
- t: t$1,
417
- open,
418
- onToggle: () => {
419
- setOpen(!open);
420
- }
421
- });
422
- const body = open ? buildCardBody({
423
- t: t$1,
656
+ const inputs = {
657
+ t,
424
658
  writable,
425
659
  eff,
426
- setField,
427
- clearField,
660
+ setField
661
+ };
662
+ const gate = buildGateSection(inputs);
663
+ const memory = buildMemorySection(inputs);
664
+ const saveBar = (0, react.createElement)(S, {
428
665
  dirty,
429
666
  busy,
430
- saveError,
667
+ writable,
668
+ error: saveError,
669
+ saveLabel: t("save"),
670
+ savingLabel: t("saving"),
671
+ revertLabel: t("revert"),
672
+ dirtyLabel: t("dirtyCount", { count: dirtyKeys.length }),
673
+ cleanLabel: t("statusClean"),
674
+ readOnlyLabel: t("statusReadOnly"),
431
675
  onSave: save,
432
676
  onDiscard: discard
433
- }) : null;
434
- return (0, react.createElement)("li", { className: `qgc-card${open ? " qgc-card-open" : ""}` }, header, body);
677
+ });
678
+ return (0, react.createElement)(u, null, gate, memory, saveBar);
435
679
  }
436
680
  function cardStore(scope) {
437
681
  let cachedSnap = null;
@@ -467,8 +711,8 @@ const inject = [
467
711
  function apply(ctx) {
468
712
  ctx.effect(() => {
469
713
  const tag = document.createElement("style");
470
- tag.id = "quality-gate-card-css";
471
- tag.textContent = CARD_CSS;
714
+ tag.id = "dsh-card-ui-css-quality-gate";
715
+ tag.textContent = c;
472
716
  document.head.append(tag);
473
717
  return () => {
474
718
  tag.remove();
@@ -491,8 +735,11 @@ function apply(ctx) {
491
735
  }, DgCard);
492
736
  });
493
737
  }
738
+ exports.CARD_NUMBER_SCHEMAS = CARD_NUMBER_SCHEMAS;
494
739
  exports.apply = apply;
495
740
  exports.inject = inject;
741
+ exports.projectCardValues = projectCardValues;
742
+ exports.writeTouched = writeTouched;
496
743
  return module.exports;
497
744
  },
498
745
  });