@ddtcorex/dsh-maestro-config 0.1.1 → 0.2.0

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/lib/client.js CHANGED
@@ -15,8 +15,8 @@ var __commonJS = (cb, mod) => function __require() {
15
15
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
16
  };
17
17
  var __export = (target, all) => {
18
- for (var name2 in all)
19
- __defProp(target, name2, { get: all[name2], enumerable: true });
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
20
  };
21
21
  var __copyProps = (to, from, except, desc) => {
22
22
  if (from && typeof from === "object" || typeof from === "function") {
@@ -1366,16 +1366,16 @@ var require_dijkstra = __commonJS({
1366
1366
  */
1367
1367
  PriorityQueue: {
1368
1368
  make: function(opts) {
1369
- var T = dijkstra.PriorityQueue, t = {}, key;
1369
+ var T = dijkstra.PriorityQueue, t2 = {}, key;
1370
1370
  opts = opts || {};
1371
1371
  for (key in T) {
1372
1372
  if (T.hasOwnProperty(key)) {
1373
- t[key] = T[key];
1373
+ t2[key] = T[key];
1374
1374
  }
1375
1375
  }
1376
- t.queue = [];
1377
- t.sorter = opts.sorter || T.default_sorter;
1378
- return t;
1376
+ t2.queue = [];
1377
+ t2.sorter = opts.sorter || T.default_sorter;
1378
+ return t2;
1379
1379
  },
1380
1380
  default_sorter: function(a, b) {
1381
1381
  return a.cost - b.cost;
@@ -2117,11 +2117,12 @@ var require_browser = __commonJS({
2117
2117
  // src/client/index.tsx
2118
2118
  var index_exports = {};
2119
2119
  __export(index_exports, {
2120
- apply: () => apply
2120
+ apply: () => apply,
2121
+ inject: () => inject
2121
2122
  });
2122
2123
  module.exports = __toCommonJS(index_exports);
2123
2124
 
2124
- // src/client/maestro-card.jsx
2125
+ // src/client/MaestroSettings.tsx
2125
2126
  var import_react = require("react");
2126
2127
  var import_qrcode = __toESM(require_browser(), 1);
2127
2128
 
@@ -2155,72 +2156,252 @@ function gitlabWebhookUrl(hostname) {
2155
2156
  return `https://${authority}/hooks/gitlab-mr`;
2156
2157
  }
2157
2158
 
2158
- // src/client/maestro-card.jsx
2159
- var inputStyle = {
2160
- height: 34,
2161
- padding: "0 12px",
2162
- border: "1px solid var(--dsw-alias-border-l2)",
2163
- borderRadius: 8,
2164
- background: "var(--dsw-alias-bg-layer-3)",
2165
- font: "inherit",
2166
- fontSize: 13,
2167
- color: "var(--dsw-alias-label-primary)",
2168
- width: "100%",
2169
- boxSizing: "border-box"
2159
+ // src/client/MaestroSettings.tsx
2160
+ var t = {
2161
+ bgLayer1: "var(--dsw-alias-bg-layer-1)",
2162
+ bgLayer2: "var(--dsw-alias-bg-layer-2)",
2163
+ bgLayer3: "var(--dsw-alias-bg-layer-3)",
2164
+ borderL2: "var(--dsw-alias-border-l2)",
2165
+ labelPrimary: "var(--dsw-alias-label-primary)",
2166
+ labelSecondary: "var(--dsw-alias-label-secondary)",
2167
+ labelTertiary: "var(--dsw-alias-label-tertiary)",
2168
+ labelDimmed: "var(--dsw-alias-label-dimmed)",
2169
+ labelFg: "var(--dsw-alias-label-primary-foreground)",
2170
+ primaryFill: "var(--dsw-alias-button-primary-fill)",
2171
+ primaryHover: "var(--dsw-alias-button-primary-hover)",
2172
+ interactiveHover: "var(--dsw-alias-interactive-bg-hover)",
2173
+ interactiveActive: "var(--dsw-alias-interactive-bg-active)",
2174
+ stateError: "var(--dsw-alias-state-error-primary)",
2175
+ brand: "var(--dsw-alias-brand-primary)",
2176
+ shadowLv3: "var(--dsw-shadow-lv3)",
2177
+ scrollbarL2: "var(--dsw-alias-scrollbar-bg-l2)"
2170
2178
  };
2179
+ function Button({
2180
+ variant = "ghost",
2181
+ size = "md",
2182
+ icon,
2183
+ children,
2184
+ style,
2185
+ ...rest
2186
+ }) {
2187
+ const base = {
2188
+ display: "inline-flex",
2189
+ alignItems: "center",
2190
+ justifyContent: "center",
2191
+ gap: 4,
2192
+ border: "none",
2193
+ borderRadius: size === "sm" ? 16 : 18,
2194
+ cursor: "pointer",
2195
+ fontSize: size === "sm" ? 13 : 14,
2196
+ lineHeight: size === "sm" ? "18px" : "22px",
2197
+ padding: size === "sm" ? "0 12px" : "0 14px",
2198
+ height: size === "sm" ? 32 : 36,
2199
+ color: t.labelPrimary,
2200
+ background: "transparent",
2201
+ fontFamily: "inherit"
2202
+ };
2203
+ if (variant === "primary") {
2204
+ base.background = t.primaryFill;
2205
+ base.color = t.labelFg;
2206
+ }
2207
+ if (variant === "outline") {
2208
+ base.border = `1px solid ${t.borderL2}`;
2209
+ base.background = "transparent";
2210
+ }
2211
+ const merged = { ...base, ...style };
2212
+ return (0, import_react.createElement)(
2213
+ "button",
2214
+ {
2215
+ type: "button",
2216
+ style: merged,
2217
+ onMouseEnter: (e) => {
2218
+ if (rest.disabled) return;
2219
+ if (variant === "primary") e.currentTarget.style.background = t.primaryHover;
2220
+ else e.currentTarget.style.background = t.interactiveHover;
2221
+ },
2222
+ onMouseLeave: (e) => {
2223
+ if (variant === "primary") e.currentTarget.style.background = t.primaryFill;
2224
+ else e.currentTarget.style.background = variant === "outline" ? "transparent" : "transparent";
2225
+ },
2226
+ ...rest
2227
+ },
2228
+ icon ? (0, import_react.createElement)("span", { style: { display: "inline-flex", width: 16, height: 16, alignItems: "center", justifyContent: "center" } }, icon) : null,
2229
+ children
2230
+ );
2231
+ }
2232
+ function InputWrap({
2233
+ icon,
2234
+ children,
2235
+ style,
2236
+ focused
2237
+ }) {
2238
+ return (0, import_react.createElement)(
2239
+ "span",
2240
+ {
2241
+ style: {
2242
+ display: "inline-flex",
2243
+ alignItems: "center",
2244
+ gap: 8,
2245
+ height: 36,
2246
+ padding: "0 12px",
2247
+ border: `1px solid ${focused ? t.brand : t.borderL2}`,
2248
+ borderRadius: 10,
2249
+ background: t.bgLayer1,
2250
+ flex: 1,
2251
+ minWidth: 0,
2252
+ boxSizing: "border-box",
2253
+ ...style
2254
+ }
2255
+ },
2256
+ icon ? (0, import_react.createElement)("span", { style: { display: "inline-flex", width: 16, height: 16, color: t.labelTertiary } }, icon) : null,
2257
+ children
2258
+ );
2259
+ }
2260
+ function FieldInput(props) {
2261
+ const [focused, setFocused] = (0, import_react.useState)(false);
2262
+ const { icon, style, ...rest } = props;
2263
+ return (0, import_react.createElement)(
2264
+ InputWrap,
2265
+ { icon, focused, style: { ...style, flex: "1 1 auto" } },
2266
+ (0, import_react.createElement)("input", {
2267
+ ...rest,
2268
+ onFocus: (e) => {
2269
+ setFocused(true);
2270
+ rest.onFocus?.(e);
2271
+ },
2272
+ onBlur: (e) => {
2273
+ setFocused(false);
2274
+ rest.onBlur?.(e);
2275
+ },
2276
+ style: {
2277
+ flex: 1,
2278
+ minWidth: 0,
2279
+ border: "none",
2280
+ outline: "none",
2281
+ background: "transparent",
2282
+ fontSize: 14,
2283
+ lineHeight: "22px",
2284
+ color: t.labelPrimary,
2285
+ fontFamily: "inherit"
2286
+ }
2287
+ })
2288
+ );
2289
+ }
2290
+ function TextareaField(props) {
2291
+ const [focused, setFocused] = (0, import_react.useState)(false);
2292
+ return (0, import_react.createElement)("textarea", {
2293
+ ...props,
2294
+ onFocus: (e) => {
2295
+ setFocused(true);
2296
+ props.onFocus?.(e);
2297
+ },
2298
+ onBlur: (e) => {
2299
+ setFocused(false);
2300
+ props.onBlur?.(e);
2301
+ },
2302
+ style: {
2303
+ width: "100%",
2304
+ minHeight: 96,
2305
+ padding: "8px 10px",
2306
+ border: `1px solid ${focused ? t.brand : t.borderL2}`,
2307
+ borderRadius: 8,
2308
+ background: t.bgLayer1,
2309
+ color: t.labelPrimary,
2310
+ fontFamily: "inherit",
2311
+ fontSize: 13,
2312
+ lineHeight: "18px",
2313
+ resize: "vertical",
2314
+ boxSizing: "border-box",
2315
+ outline: "none",
2316
+ ...props.style
2317
+ }
2318
+ });
2319
+ }
2171
2320
  var fieldLabelStyle = {
2172
- display: "block",
2173
- fontSize: 12,
2174
- lineHeight: 1.5,
2175
- color: "var(--dsw-alias-label-secondary)",
2176
- margin: "10px 0 4px"
2321
+ display: "flex",
2322
+ flexDirection: "column",
2323
+ gap: "6px",
2324
+ fontSize: "12px",
2325
+ lineHeight: "16px",
2326
+ fontWeight: "500",
2327
+ color: t.labelSecondary,
2328
+ margin: "12px 0 0"
2177
2329
  };
2178
- var secondaryButtonStyle = {
2179
- height: 32,
2180
- padding: "0 14px",
2181
- borderRadius: 16,
2182
- border: "1px solid var(--dsw-alias-border-l2)",
2183
- background: "transparent",
2184
- color: "var(--dsw-alias-label-primary)",
2185
- font: "inherit",
2186
- fontSize: 13,
2187
- cursor: "pointer",
2188
- flex: "none"
2330
+ var captionStyle = {
2331
+ fontSize: "12px",
2332
+ lineHeight: "16px",
2333
+ color: t.labelSecondary,
2334
+ margin: "4px 0"
2189
2335
  };
2190
- var primaryButtonStyle = {
2191
- ...secondaryButtonStyle,
2192
- border: "none",
2193
- background: "var(--dsw-alias-button-primary-fill)",
2194
- color: "var(--dsw-alias-label-primary-foreground)"
2336
+ var cardInsetStyle = {
2337
+ display: "flex",
2338
+ flexDirection: "column",
2339
+ gap: "8px",
2340
+ padding: "12px",
2341
+ borderRadius: "12px",
2342
+ border: `1px solid ${t.borderL2}`,
2343
+ background: t.bgLayer1
2195
2344
  };
2196
- var captionStyle = {
2197
- fontSize: 12,
2198
- lineHeight: 1.5,
2199
- margin: "4px 0",
2200
- color: "var(--dsw-alias-label-secondary)"
2345
+ var rowStyle = {
2346
+ display: "flex",
2347
+ alignItems: "center",
2348
+ gap: "8px",
2349
+ padding: "16px 0",
2350
+ borderBottom: `1px solid ${t.borderL2}`,
2351
+ minWidth: "0"
2201
2352
  };
2202
- var sectionStyle = {
2203
- marginTop: 20,
2204
- paddingTop: 16,
2205
- borderTop: "1px solid var(--dsw-alias-border-l2)"
2353
+ var rowTextStyle = {
2354
+ flex: "1",
2355
+ minWidth: "0",
2356
+ display: "flex",
2357
+ flexDirection: "column",
2358
+ gap: "4px",
2359
+ paddingRight: "48px"
2206
2360
  };
2207
- var headingStyle = {
2208
- margin: "0 0 6px",
2209
- fontSize: 13,
2210
- fontWeight: 600,
2211
- color: "var(--dsw-alias-label-primary)"
2361
+ var rowTitleStyle = {
2362
+ fontSize: "14px",
2363
+ fontWeight: "400",
2364
+ lineHeight: "22px",
2365
+ color: t.labelPrimary
2212
2366
  };
2213
- var errorStyle = {
2214
- color: "var(--dsw-alias-state-error-primary)",
2215
- fontSize: 12,
2216
- margin: "8px 0 0"
2367
+ var rowDescStyle = {
2368
+ fontSize: "12px",
2369
+ fontWeight: "400",
2370
+ lineHeight: "18px",
2371
+ color: t.labelTertiary
2217
2372
  };
2218
- var codeStyle = {
2219
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2220
- fontSize: 12,
2221
- color: "var(--dsw-alias-label-primary)",
2222
- wordBreak: "break-all"
2373
+ var pillSelectorStyle = {
2374
+ display: "inline-flex",
2375
+ alignItems: "center",
2376
+ gap: "12px",
2377
+ height: "36px",
2378
+ padding: "0 14px",
2379
+ border: "none",
2380
+ borderRadius: "18px",
2381
+ background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
2382
+ font: "inherit",
2383
+ fontSize: "14px",
2384
+ lineHeight: "22px",
2385
+ color: t.labelPrimary,
2386
+ cursor: "pointer",
2387
+ whiteSpace: "nowrap"
2223
2388
  };
2389
+ function SettingRow({ title, description, control }) {
2390
+ return (0, import_react.createElement)(
2391
+ "div",
2392
+ { "data-maestro-row": "", style: rowStyle },
2393
+ (0, import_react.createElement)("div", { "data-maestro-row-text": "", style: rowTextStyle }, (0, import_react.createElement)("div", { style: rowTitleStyle }, title), description ? (0, import_react.createElement)("div", { style: rowDescStyle }, description) : null),
2394
+ (0, import_react.createElement)("div", { "data-maestro-control": "", style: { flex: "none", display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 8, minHeight: "36px" } }, control)
2395
+ );
2396
+ }
2397
+ function ToggleRow({ title, description, checked, onChange }) {
2398
+ return (0, import_react.createElement)(
2399
+ "label",
2400
+ { "data-maestro-row": "", style: { ...rowStyle, cursor: "pointer", alignItems: "flex-start" } },
2401
+ (0, import_react.createElement)("input", { type: "checkbox", checked: checked === true, onChange: (e) => onChange(e.target.checked), style: { width: 16, height: 16, accentColor: t.primaryFill, marginTop: 4, flex: "none" } }),
2402
+ (0, import_react.createElement)("div", { "data-maestro-row-text": "", style: { ...rowTextStyle, paddingRight: "0" } }, (0, import_react.createElement)("div", { style: rowTitleStyle }, title), description ? (0, import_react.createElement)("div", { style: rowDescStyle }, description) : null)
2403
+ );
2404
+ }
2224
2405
  function QrImage({ url, size = 104 }) {
2225
2406
  const [dataUrl, setDataUrl] = (0, import_react.useState)(null);
2226
2407
  (0, import_react.useEffect)(() => {
@@ -2246,34 +2427,24 @@ function QrImage({ url, size = 104 }) {
2246
2427
  alignItems: "center",
2247
2428
  justifyContent: "center",
2248
2429
  lineHeight: 0,
2249
- flex: "none",
2250
- alignSelf: "flex-start"
2430
+ flex: "none"
2251
2431
  }
2252
2432
  },
2253
2433
  dataUrl === null ? (0, import_react.createElement)("div", { style: { width: size, height: size, background: "var(--dsw-alias-bg-skeleton)", borderRadius: 4 } }) : (0, import_react.createElement)("img", { src: dataUrl, alt: url, width: size, height: size, style: { display: "block" } })
2254
2434
  );
2255
2435
  }
2256
- function NamedTunnelSetupNote() {
2257
- return (0, import_react.createElement)(
2258
- "div",
2259
- { style: captionStyle },
2260
- (0, import_react.createElement)("p", { style: { ...captionStyle, marginBottom: 4 } }, "Named tunnel needs a one-time manual setup (requires your own Cloudflare account \u2014 cannot be automated):"),
2261
- (0, import_react.createElement)(
2262
- "ol",
2263
- { style: { margin: "4px 0", paddingLeft: 20 } },
2264
- (0, import_react.createElement)("li", null, "cloudflared tunnel login"),
2265
- (0, import_react.createElement)("li", null, "cloudflared tunnel create dsh-maestro-webhook"),
2266
- (0, import_react.createElement)("li", null, "cloudflared tunnel route dns dsh-maestro-webhook <your-hostname>"),
2267
- (0, import_react.createElement)("li", null, "Paste the printed Tunnel ID, the credentials file path (~/.cloudflared/<id>.json), and the hostname below.")
2268
- )
2269
- );
2270
- }
2271
- function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onChange, label }) {
2436
+ function ReviewModelSelector({
2437
+ value,
2438
+ catalog,
2439
+ fallbackValue,
2440
+ fallbackLabel,
2441
+ onChange,
2442
+ label
2443
+ }) {
2272
2444
  const groups = catalog?.groups ?? [];
2273
2445
  const providers = groups.map((g) => g.provider);
2274
2446
  const selectedProvider = value?.provider ?? "";
2275
2447
  const providerGroup = groups.find((g) => g.provider === selectedProvider);
2276
- const models = providerGroup?.models ?? [];
2277
2448
  const selectedEffort = value?.reasoningEffort ?? "";
2278
2449
  const [open, setOpen] = (0, import_react.useState)(false);
2279
2450
  const [pane, setPane] = (0, import_react.useState)("root");
@@ -2321,7 +2492,7 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2321
2492
  if (filtered.length > 0) return filtered;
2322
2493
  return ["low", "medium", "high"];
2323
2494
  })();
2324
- const warning = selectedEffort !== "" && !supportsReasoning && selectedModelInfo !== null ? `\u26A0\uFE0F This model does not support reasoning effort "${selectedEffort}" \u2014 reviews will fail. Clear effort or choose a reasoning-capable model.` : null;
2495
+ const warning = selectedEffort !== "" && !supportsReasoning && selectedModelInfo !== null ? `\u26A0\uFE0F This model does not support reasoning effort "${selectedEffort}" \u2014 reviews will fail.` : null;
2325
2496
  const update = (field, newVal) => {
2326
2497
  if (newVal === "" && field === "provider") {
2327
2498
  onChange(null);
@@ -2335,51 +2506,30 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2335
2506
  const first = g?.models[0];
2336
2507
  next.provider = newVal;
2337
2508
  next.model = first !== void 0 ? getModelId(first) : "";
2338
- } else if (field === "model") {
2339
- next.model = newVal;
2340
- } else if (field === "reasoningEffort") {
2509
+ } else if (field === "model") next.model = newVal;
2510
+ else if (field === "reasoningEffort") {
2341
2511
  if (newVal === "") delete next.reasoningEffort;
2342
2512
  else next.reasoningEffort = newVal;
2343
2513
  }
2344
- if (!next.provider || !next.model) {
2345
- onChange(null);
2346
- } else {
2347
- onChange(next);
2348
- }
2514
+ if (!next.provider || !next.model) onChange(null);
2515
+ else onChange(next);
2349
2516
  };
2350
2517
  const effectiveFallback = fallbackValue !== void 0 ? fallbackValue : catalog?.current ?? null;
2351
- const effectiveFallbackLabel = fallbackLabel ?? "Use DSH default";
2352
- const triggerLabel = value ? `${value.provider} / ${value.model}${value.reasoningEffort ? ` \xB7 ${value.reasoningEffort}` : ""}` : effectiveFallback ? `${effectiveFallbackLabel} \xB7 ${effectiveFallback.provider}/${effectiveFallback.model}${effectiveFallback.reasoningEffort ? ` \xB7 ${effectiveFallback.reasoningEffort}` : ""}` : effectiveFallbackLabel;
2353
- const triggerStyle = {
2354
- height: 32,
2355
- padding: "0 12px 0 14px",
2356
- borderRadius: 20,
2357
- border: "1px solid var(--dsw-alias-border-l2)",
2358
- background: "var(--dsw-alias-bg-layer-2)",
2359
- color: "var(--dsw-alias-label-primary)",
2360
- font: "inherit",
2361
- fontSize: 13,
2362
- display: "inline-flex",
2363
- alignItems: "center",
2364
- gap: 8,
2365
- cursor: "pointer",
2366
- maxWidth: 320,
2367
- whiteSpace: "nowrap"
2368
- };
2518
+ const triggerLabel = value ? `${value.provider} / ${value.model}${value.reasoningEffort ? ` \xB7 ${value.reasoningEffort}` : ""}` : effectiveFallback ? `${fallbackLabel} \xB7 ${effectiveFallback.provider}/${effectiveFallback.model}${effectiveFallback.reasoningEffort ? ` \xB7 ${effectiveFallback.reasoningEffort}` : ""}` : fallbackLabel;
2369
2519
  const menuStyle = {
2370
2520
  position: "absolute",
2371
2521
  top: "calc(100% + 8px)",
2372
- left: 0,
2373
- minWidth: 300,
2374
- maxWidth: 360,
2375
- background: "var(--dsw-alias-bg-layer-1)",
2376
- border: "1px solid var(--dsw-alias-border-l2)",
2377
- borderRadius: 12,
2378
- boxShadow: "0 8 24px rgba(0,0,0,.12)",
2379
- zIndex: 20,
2380
- padding: 6
2522
+ left: "0",
2523
+ minWidth: "300px",
2524
+ maxWidth: "360px",
2525
+ background: t.bgLayer2,
2526
+ border: `1px solid ${t.borderL2}`,
2527
+ borderRadius: "12px",
2528
+ boxShadow: t.shadowLv3,
2529
+ zIndex: "20",
2530
+ padding: "6px"
2381
2531
  };
2382
- const rowStyle = {
2532
+ const rowStyle2 = {
2383
2533
  width: "100%",
2384
2534
  display: "flex",
2385
2535
  alignItems: "center",
@@ -2389,66 +2539,87 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2389
2539
  borderRadius: 8,
2390
2540
  border: "none",
2391
2541
  background: "transparent",
2392
- color: "var(--dsw-alias-label-primary)",
2393
- font: "inherit",
2542
+ color: t.labelPrimary,
2543
+ fontFamily: "inherit",
2394
2544
  fontSize: 13,
2395
2545
  cursor: "pointer",
2396
2546
  textAlign: "left"
2397
2547
  };
2398
2548
  const check = (active) => active ? (0, import_react.createElement)("svg", { width: 16, height: 16, viewBox: "0 0 16 16", style: { flex: "none" } }, (0, import_react.createElement)("path", { d: "M3.5 8.2l2.8 2.8L12.5 4.8", fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" })) : (0, import_react.createElement)("span", { style: { width: 16, flex: "none" } });
2399
- const chevronDown = (0, import_react.createElement)("svg", { width: 14, height: 14, viewBox: "0 0 14 14", style: { flex: "none", opacity: 0.7 } }, (0, import_react.createElement)("path", { d: "M3.5 5L7 8.5L10.5 5", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round" }));
2400
2549
  const chevronRight = (0, import_react.createElement)("svg", { width: 14, height: 14, viewBox: "0 0 14 14", style: { flex: "none", opacity: 0.6 } }, (0, import_react.createElement)("path", { d: "M5 3.5L8.5 7L5 10.5", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round" }));
2401
2550
  const effortLabel = selectedEffort === "" ? "Default effort" : selectedEffort;
2402
2551
  const modelLabel = selectedProvider === "" ? "Select model" : value?.model ?? "Select model";
2403
2552
  return (0, import_react.createElement)(
2404
2553
  "div",
2405
- { ref: rootRef, style: { position: "relative", display: "inline-block", maxWidth: "100%" } },
2406
- label && (0, import_react.createElement)("span", { style: fieldLabelStyle }, label),
2554
+ { ref: rootRef, "data-maestro-trigger-wrap": "", style: { position: "relative", display: "inline-block", maxWidth: "100%" } },
2555
+ label ? (0, import_react.createElement)("span", { style: fieldLabelStyle }, label) : null,
2407
2556
  (0, import_react.createElement)(
2408
2557
  "button",
2409
- { type: "button", style: triggerStyle, onClick: () => {
2410
- setOpen((v) => !v);
2411
- setPane("root");
2412
- }, "aria-expanded": open, "aria-haspopup": "menu", title: triggerLabel },
2558
+ {
2559
+ type: "button",
2560
+ style: {
2561
+ height: 36,
2562
+ padding: "0 14px 0 16px",
2563
+ borderRadius: 18,
2564
+ border: "none",
2565
+ background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
2566
+ color: t.labelPrimary,
2567
+ fontFamily: "inherit",
2568
+ fontSize: 13,
2569
+ display: "inline-flex",
2570
+ alignItems: "center",
2571
+ gap: 8,
2572
+ cursor: "pointer",
2573
+ maxWidth: 320,
2574
+ whiteSpace: "nowrap"
2575
+ },
2576
+ onClick: () => {
2577
+ setOpen((v) => !v);
2578
+ setPane("root");
2579
+ },
2580
+ "aria-expanded": open,
2581
+ "aria-haspopup": "menu",
2582
+ title: triggerLabel
2583
+ },
2413
2584
  (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, triggerLabel),
2414
- chevronDown
2585
+ (0, import_react.createElement)("svg", { width: 14, height: 14, viewBox: "0 0 14 14", style: { flex: "none", opacity: 0.7 } }, (0, import_react.createElement)("path", { d: "M3.5 5L7 8.5L10.5 5", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round" }))
2415
2586
  ),
2416
- open && (0, import_react.createElement)(
2587
+ open ? (0, import_react.createElement)(
2417
2588
  "div",
2418
- { style: menuStyle, role: "menu" },
2419
- pane === "root" && (0, import_react.createElement)(
2589
+ { "data-maestro-menu": "", style: menuStyle, role: "menu" },
2590
+ pane === "root" ? (0, import_react.createElement)(
2420
2591
  "div",
2421
2592
  null,
2422
2593
  (0, import_react.createElement)(
2423
2594
  "button",
2424
- { type: "button", style: { ...rowStyle, background: !value ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2595
+ { type: "button", style: { ...rowStyle2, background: !value ? t.bgLayer1 : "transparent" }, onClick: () => {
2425
2596
  onChange(null);
2426
2597
  setOpen(false);
2427
2598
  } },
2428
- (0, import_react.createElement)("span", null, effectiveFallbackLabel),
2599
+ (0, import_react.createElement)("span", null, fallbackLabel),
2429
2600
  check(!value)
2430
2601
  ),
2431
- (0, import_react.createElement)("div", { style: { height: 1, background: "var(--dsw-alias-border-l2)", margin: "6px 2px" } }),
2602
+ (0, import_react.createElement)("div", { style: { height: 1, background: t.borderL2, margin: "6px 2px" } }),
2432
2603
  (0, import_react.createElement)(
2433
2604
  "button",
2434
- { type: "button", style: rowStyle, onClick: () => setPane("model") },
2605
+ { type: "button", style: rowStyle2, onClick: () => setPane("model") },
2435
2606
  (0, import_react.createElement)("span", null, "Model"),
2436
- (0, import_react.createElement)("span", { style: { display: "flex", alignItems: "center", gap: 8, color: "var(--dsw-alias-label-secondary)", maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, modelLabel), chevronRight)
2607
+ (0, import_react.createElement)("span", { style: { display: "flex", alignItems: "center", gap: 8, color: t.labelSecondary, maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, modelLabel), chevronRight)
2437
2608
  ),
2438
2609
  (0, import_react.createElement)(
2439
2610
  "button",
2440
- { type: "button", style: rowStyle, onClick: () => setPane("effort") },
2611
+ { type: "button", style: rowStyle2, onClick: () => setPane("effort") },
2441
2612
  (0, import_react.createElement)("span", null, "Effort"),
2442
- (0, import_react.createElement)("span", { style: { display: "flex", alignItems: "center", gap: 8, color: "var(--dsw-alias-label-secondary)" } }, effortLabel, chevronRight)
2613
+ (0, import_react.createElement)("span", { style: { display: "flex", alignItems: "center", gap: 8, color: t.labelSecondary } }, effortLabel, chevronRight)
2443
2614
  ),
2444
- value && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, `Selected: ${value.provider} / ${value.model}${value.reasoningEffort ? ` (${value.reasoningEffort})` : ""}`),
2445
- !value && effectiveFallback && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, `${effectiveFallbackLabel === "Use Global" ? "Using Global" : "Using DSH default"}: ${effectiveFallback.provider} / ${effectiveFallback.model}${effectiveFallback.reasoningEffort ? ` (${effectiveFallback.reasoningEffort})` : ""}`),
2446
- warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "4px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
2447
- ),
2448
- pane === "model" && (0, import_react.createElement)(
2615
+ value ? (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, `Selected: ${value.provider} / ${value.model}${value.reasoningEffort ? ` (${value.reasoningEffort})` : ""}`) : null,
2616
+ !value && effectiveFallback ? (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, `${fallbackLabel === "Use Global" ? "Using Global" : "Using DSH default"}: ${effectiveFallback.provider} / ${effectiveFallback.model}${effectiveFallback.reasoningEffort ? ` (${effectiveFallback.reasoningEffort})` : ""}`) : null,
2617
+ warning ? (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "4px 4px 2px", color: t.stateError } }, warning) : null
2618
+ ) : null,
2619
+ pane === "model" ? (0, import_react.createElement)(
2449
2620
  "div",
2450
2621
  null,
2451
- (0, import_react.createElement)("button", { type: "button", style: { ...rowStyle, color: "var(--dsw-alias-label-secondary)" }, onClick: () => setPane("root") }, (0, import_react.createElement)("span", null, "\u2190 Back"), (0, import_react.createElement)("span", { style: { fontSize: 12 } }, "Model")),
2622
+ (0, import_react.createElement)("button", { type: "button", style: { ...rowStyle2, color: t.labelSecondary }, onClick: () => setPane("root") }, (0, import_react.createElement)("span", null, "\u2190 Back"), (0, import_react.createElement)("span", { style: { fontSize: 12 } }, "Model")),
2452
2623
  (0, import_react.createElement)(
2453
2624
  "div",
2454
2625
  { style: { maxHeight: 260, overflowY: "auto", marginTop: 4 } },
@@ -2458,33 +2629,43 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2458
2629
  return (0, import_react.createElement)(
2459
2630
  "div",
2460
2631
  { key: p, style: { marginBottom: 8 } },
2461
- (0, import_react.createElement)("div", { style: { fontSize: 11, fontWeight: 600, color: "var(--dsw-alias-label-secondary)", padding: "6px 10px 2px", textTransform: "uppercase", letterSpacing: 0.4, display: "flex", alignItems: "center", gap: 6 } }, (0, import_react.createElement)("span", { style: { width: 6, height: 6, borderRadius: 3, background: "var(--dsw-alias-border-l2)", flex: "none" } }), g?.name ?? p),
2632
+ (0, import_react.createElement)("div", { style: { fontSize: 11, fontWeight: 600, color: t.labelSecondary, padding: "6px 10px 2px", textTransform: "uppercase", letterSpacing: 0.4, display: "flex", alignItems: "center", gap: 6 } }, (0, import_react.createElement)("span", { style: { width: 6, height: 6, borderRadius: 3, background: t.borderL2, flex: "none" } }), g?.name ?? p),
2462
2633
  ms.length === 0 ? (0, import_react.createElement)("p", { style: { ...captionStyle, padding: "2px 10px 2px 28px" } }, "No models") : (0, import_react.createElement)(
2463
2634
  "div",
2464
- { style: { marginLeft: 12, borderLeft: "1px solid var(--dsw-alias-border-l2)", paddingLeft: 6, display: "flex", flexDirection: "column", gap: 2 } },
2635
+ { style: { marginLeft: 12, borderLeft: `1px solid ${t.borderL2}`, paddingLeft: 6, display: "flex", flexDirection: "column", gap: 2 } },
2465
2636
  ms.map((m) => {
2466
2637
  const mid = getModelId(m);
2467
2638
  const mname = getModelName(m);
2468
2639
  const active = value?.provider === p && value?.model === mid;
2469
- return (0, import_react.createElement)("button", { key: mid, type: "button", style: { ...rowStyle, paddingLeft: 10, background: active ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2470
- update("model", mid);
2471
- if (value?.provider !== p) update("provider", p);
2472
- else {
2473
- const next = { provider: p, model: mid, ...selectedEffort ? { reasoningEffort: selectedEffort } : {} };
2474
- onChange(next);
2475
- setPane("root");
2476
- }
2477
- } }, (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, mname), check(active));
2640
+ return (0, import_react.createElement)(
2641
+ "button",
2642
+ {
2643
+ key: mid,
2644
+ type: "button",
2645
+ style: { ...rowStyle2, paddingLeft: 10, background: active ? t.bgLayer1 : "transparent" },
2646
+ onClick: () => {
2647
+ update("model", mid);
2648
+ if (value?.provider !== p) update("provider", p);
2649
+ else {
2650
+ const next = { provider: p, model: mid, ...selectedEffort ? { reasoningEffort: selectedEffort } : {} };
2651
+ onChange(next);
2652
+ setPane("root");
2653
+ }
2654
+ }
2655
+ },
2656
+ (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, mname),
2657
+ check(active)
2658
+ );
2478
2659
  })
2479
2660
  )
2480
2661
  );
2481
2662
  })
2482
2663
  )
2483
- ),
2484
- pane === "effort" && (0, import_react.createElement)(
2664
+ ) : null,
2665
+ pane === "effort" ? (0, import_react.createElement)(
2485
2666
  "div",
2486
2667
  null,
2487
- (0, import_react.createElement)("button", { type: "button", style: { ...rowStyle, color: "var(--dsw-alias-label-secondary)" }, onClick: () => setPane("root") }, (0, import_react.createElement)("span", null, "\u2190 Back"), (0, import_react.createElement)("span", { style: { fontSize: 12 } }, "Effort")),
2668
+ (0, import_react.createElement)("button", { type: "button", style: { ...rowStyle2, color: t.labelSecondary }, onClick: () => setPane("root") }, (0, import_react.createElement)("span", null, "\u2190 Back"), (0, import_react.createElement)("span", { style: { fontSize: 12 } }, "Effort")),
2488
2669
  selectedModelInfo === null ? (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, "Select a model first to configure effort.") : !supportsReasoning ? (0, import_react.createElement)(
2489
2670
  "div",
2490
2671
  null,
@@ -2492,133 +2673,228 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2492
2673
  (0, import_react.createElement)(
2493
2674
  "div",
2494
2675
  { style: { marginTop: 4 } },
2495
- [{ id: "", label: "Default effort" }].map((e) => (0, import_react.createElement)("button", { key: e.id || "default", type: "button", style: { ...rowStyle, background: selectedEffort === e.id ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2496
- update("reasoningEffort", e.id);
2497
- setPane("root");
2498
- } }, (0, import_react.createElement)("span", null, e.label), check(selectedEffort === e.id)))
2676
+ [{ id: "", label: "Default effort" }].map(
2677
+ (e) => (0, import_react.createElement)(
2678
+ "button",
2679
+ { key: e.id || "default", type: "button", style: { ...rowStyle2, background: selectedEffort === e.id ? t.bgLayer1 : "transparent" }, onClick: () => {
2680
+ update("reasoningEffort", e.id);
2681
+ setPane("root");
2682
+ } },
2683
+ (0, import_react.createElement)("span", null, e.label),
2684
+ check(selectedEffort === e.id)
2685
+ )
2686
+ )
2499
2687
  ),
2500
- warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
2688
+ warning ? (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px", color: t.stateError } }, warning) : null
2501
2689
  ) : (0, import_react.createElement)(
2502
2690
  "div",
2503
2691
  null,
2504
2692
  (0, import_react.createElement)(
2505
2693
  "div",
2506
2694
  { style: { marginTop: 4 } },
2507
- [{ id: "", label: "Default effort" }, ...availableEfforts.map((id) => ({ id, label: id }))].map((e) => (0, import_react.createElement)("button", { key: e.id || "default", type: "button", style: { ...rowStyle, background: selectedEffort === e.id ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2508
- update("reasoningEffort", e.id);
2509
- setPane("root");
2510
- } }, (0, import_react.createElement)("span", null, e.label), check(selectedEffort === e.id)))
2695
+ [{ id: "", label: "Default effort" }, ...availableEfforts.map((id) => ({ id, label: id }))].map(
2696
+ (e) => (0, import_react.createElement)(
2697
+ "button",
2698
+ { key: e.id || "default", type: "button", style: { ...rowStyle2, background: selectedEffort === e.id ? t.bgLayer1 : "transparent" }, onClick: () => {
2699
+ update("reasoningEffort", e.id);
2700
+ setPane("root");
2701
+ } },
2702
+ (0, import_react.createElement)("span", null, e.label),
2703
+ check(selectedEffort === e.id)
2704
+ )
2705
+ )
2511
2706
  ),
2512
- warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
2707
+ warning ? (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px", color: t.stateError } }, warning) : null
2513
2708
  )
2514
- )
2515
- )
2709
+ ) : null
2710
+ ) : null
2516
2711
  );
2517
2712
  }
2518
2713
  function ProjectMappingsEditor({ mappings, onChange, catalog, globalReviewModel }) {
2519
- const rows = mappings.length > 0 ? mappings : [{ projectPath: "", localRepoPath: "", reviewProfile: "magento2" }];
2714
+ const rows = mappings;
2520
2715
  const updateRow = (index, field, value) => {
2521
2716
  const next = rows.map((row, i) => i === index ? { ...row, [field]: value } : row);
2522
- onChange(next.filter((r) => r.projectPath !== "" || r.localRepoPath !== ""));
2717
+ onChange(next.filter((r) => (r.projectPath ?? "") !== "" || (r.localRepoPath ?? "") !== ""));
2523
2718
  };
2524
2719
  const removeRow = (index) => onChange(rows.filter((_, i) => i !== index));
2525
2720
  const addRow = () => onChange([...rows, { projectPath: "", localRepoPath: "", reviewProfile: "magento2" }]);
2526
2721
  return (0, import_react.createElement)(
2527
2722
  "div",
2528
- null,
2529
- (0, import_react.createElement)("span", { style: fieldLabelStyle }, "Tracked projects (GitLab path \u2192 local repo checkout \u2192 review profile \u2192 review model override)"),
2530
- rows.map((row, i) => (0, import_react.createElement)(
2723
+ { "data-maestro-projects": "", style: { display: "flex", flexDirection: "column", gap: 12, paddingTop: 8 } },
2724
+ // Header count + primary Add
2725
+ (0, import_react.createElement)(
2531
2726
  "div",
2532
- { key: i, "data-maestro-mapping-row": "", style: { display: "flex", gap: 8, marginBottom: 8, alignItems: "center", flexWrap: "wrap" } },
2533
- (0, import_react.createElement)("input", { placeholder: "group/project", style: { ...inputStyle, flex: "1 1 120px" }, value: row.projectPath, onChange: (e) => updateRow(i, "projectPath", e.target.value) }),
2534
- (0, import_react.createElement)("input", { placeholder: "/path/to/local/clone", style: { ...inputStyle, flex: "1 1 140px" }, value: row.localRepoPath, onChange: (e) => updateRow(i, "localRepoPath", e.target.value) }),
2535
- (0, import_react.createElement)(
2536
- "select",
2537
- { style: { ...inputStyle, flex: "0 0 130px" }, value: row.reviewProfile ?? "magento2", onChange: (e) => updateRow(i, "reviewProfile", e.target.value) },
2538
- (0, import_react.createElement)("option", { value: "magento2" }, "Magento 2"),
2539
- (0, import_react.createElement)("option", { value: "generic" }, "Generic")
2540
- ),
2727
+ { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, padding: "4px 0 4px" } },
2541
2728
  (0, import_react.createElement)(
2542
2729
  "div",
2543
- { style: { flex: "0 0 auto" } },
2544
- (0, import_react.createElement)(ReviewModelSelector, {
2545
- value: row.reviewModel ?? null,
2546
- catalog,
2547
- fallbackValue: globalReviewModel ?? catalog?.current ?? null,
2548
- fallbackLabel: globalReviewModel ? "Use Global" : "Use DSH default",
2549
- onChange: (v) => updateRow(i, "reviewModel", v),
2550
- label: null
2551
- })
2730
+ { style: { flex: 1, minWidth: 0 } },
2731
+ (0, import_react.createElement)("div", { style: { fontSize: 14, fontWeight: 600, color: t.labelPrimary, lineHeight: "20px" } }, `Projects \u2014 ${rows.length} tracked`),
2732
+ (0, import_react.createElement)("div", { style: { fontSize: 12, color: t.labelSecondary, lineHeight: "16px", marginTop: 2 } }, "GitLab path \u2192 local checkout \u2192 profile \u2192 model override")
2552
2733
  ),
2553
- (0, import_react.createElement)("button", { onClick: () => removeRow(i), style: secondaryButtonStyle, title: "Remove mapping" }, "\u2715")
2554
- )),
2555
- (0, import_react.createElement)("button", { onClick: addRow, style: secondaryButtonStyle }, "+ Add mapping")
2556
- );
2557
- }
2558
- function SecretInput({ label, placeholder, hasSaved, onSave }) {
2559
- const [draft, setDraft] = (0, import_react.useState)("");
2560
- const clear = () => {
2561
- setDraft("");
2562
- onSave("");
2563
- };
2564
- return (0, import_react.createElement)(
2565
- "div",
2566
- null,
2567
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, label),
2568
- (0, import_react.createElement)(
2734
+ (0, import_react.createElement)(Button, { variant: "primary", size: "md", onClick: addRow }, "+ Add project")
2735
+ ),
2736
+ rows.length === 0 ? (0, import_react.createElement)(
2569
2737
  "div",
2570
- { style: { display: "flex", gap: 8 } },
2571
- (0, import_react.createElement)("input", {
2572
- placeholder: hasSaved === true ? "saved \u2014 leave blank to keep" : placeholder,
2573
- type: "password",
2574
- autoComplete: "off",
2575
- style: inputStyle,
2576
- value: draft,
2577
- onChange: (e) => setDraft(e.target.value),
2578
- onBlur: () => {
2579
- if (draft !== "") onSave(draft);
2738
+ {
2739
+ "data-maestro-project-empty": "",
2740
+ style: {
2741
+ display: "flex",
2742
+ flexDirection: "column",
2743
+ alignItems: "center",
2744
+ gap: 10,
2745
+ padding: "20px 16px",
2746
+ borderRadius: 12,
2747
+ border: `1px dashed ${t.borderL2}`,
2748
+ background: "transparent",
2749
+ textAlign: "center"
2580
2750
  }
2581
- }),
2582
- hasSaved === true && (0, import_react.createElement)("button", { type: "button", style: secondaryButtonStyle, onClick: clear }, "Clear")
2583
- )
2584
- );
2585
- }
2586
- function ToggleField({ label, caption, checked, onChange }) {
2587
- return (0, import_react.createElement)(
2588
- "label",
2589
- { style: { display: "flex", alignItems: "flex-start", gap: 8, margin: "8px 0", cursor: "pointer" } },
2590
- (0, import_react.createElement)("input", { type: "checkbox", checked: checked === true, onChange: (e) => onChange(e.target.checked), style: { marginTop: 3 } }),
2591
- (0, import_react.createElement)(
2592
- "span",
2593
- null,
2594
- (0, import_react.createElement)("div", { style: { fontSize: 13 } }, label),
2595
- caption != null && (0, import_react.createElement)("div", { style: captionStyle }, caption)
2596
- )
2597
- );
2598
- }
2599
- function ReviewHistoryPanel({ rpcCall }) {
2600
- const [entries, setEntries] = (0, import_react.useState)(null);
2601
- (0, import_react.useEffect)(() => {
2602
- rpcCall(MAESTRO_ENDPOINTS.reviewsList, {}).then((res) => {
2603
- if (res?.ok) setEntries(res.value ?? []);
2604
- }).catch(() => setEntries([]));
2605
- }, []);
2606
- if (entries === null) return (0, import_react.createElement)("p", { style: captionStyle }, "Loading review history\u2026");
2607
- if (entries.length === 0) return (0, import_react.createElement)("p", { style: captionStyle }, "No reviews recorded yet.");
2608
- const icon = (entry) => entry.status === "completed" ? "\u2705" : entry.status === "failed" ? "\u26A0\uFE0F" : "\u{1F440}";
2609
- return (0, import_react.createElement)(
2610
- "ul",
2611
- { style: { listStyle: "none", margin: 0, padding: 0 } },
2612
- entries.map((entry) => (0, import_react.createElement)(
2613
- "li",
2614
- { key: entry.id, style: { padding: "6px 0", borderBottom: "1px solid var(--dsw-alias-separator-default, #333)", fontSize: 13 } },
2615
- (0, import_react.createElement)("span", null, `${icon(entry)} ${entry.projectPath} !${entry.mrIid} \xB7 ${entry.mode}${entry.trigger !== "mention" ? ` \xB7 ${entry.trigger}` : ""}`),
2616
- (0, import_react.createElement)(
2617
- "div",
2618
- { style: captionStyle },
2619
- `${new Date(entry.startedAt).toLocaleString()}${entry.summary ? ` \u2014 ${entry.summary}` : ""}${entry.error ? ` \u2014 ${entry.error}` : ""}`
2751
+ },
2752
+ (0, import_react.createElement)("div", { style: { fontSize: 13, color: t.labelSecondary, lineHeight: "18px" } }, "No projects yet \u2014 add your first mapping"),
2753
+ (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: addRow }, "+ Add project")
2754
+ ) : (0, import_react.createElement)(
2755
+ "div",
2756
+ { style: { display: "flex", flexDirection: "column", gap: 12 } },
2757
+ ...rows.map(
2758
+ (row, i) => (0, import_react.createElement)(
2759
+ "div",
2760
+ {
2761
+ key: i,
2762
+ "data-maestro-project-card": "",
2763
+ style: {
2764
+ display: "flex",
2765
+ flexDirection: "column",
2766
+ gap: 12,
2767
+ padding: 12,
2768
+ borderRadius: 12,
2769
+ border: `1px solid ${t.borderL2}`,
2770
+ background: t.bgLayer1,
2771
+ boxSizing: "border-box"
2772
+ }
2773
+ },
2774
+ // Card header: index + path + remove
2775
+ (0, import_react.createElement)(
2776
+ "div",
2777
+ { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 } },
2778
+ (0, import_react.createElement)(
2779
+ "div",
2780
+ { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0, flex: 1 } },
2781
+ (0, import_react.createElement)(
2782
+ "span",
2783
+ {
2784
+ style: {
2785
+ flex: "none",
2786
+ width: 24,
2787
+ height: 24,
2788
+ borderRadius: 12,
2789
+ background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
2790
+ display: "inline-flex",
2791
+ alignItems: "center",
2792
+ justifyContent: "center",
2793
+ fontSize: 11,
2794
+ fontWeight: 600,
2795
+ color: t.labelSecondary
2796
+ }
2797
+ },
2798
+ String(i + 1)
2799
+ ),
2800
+ (0, import_react.createElement)(
2801
+ "span",
2802
+ {
2803
+ style: {
2804
+ fontSize: 12,
2805
+ fontFamily: "ui-monospace, monospace",
2806
+ color: row.projectPath ? t.labelPrimary : t.labelTertiary,
2807
+ overflow: "hidden",
2808
+ textOverflow: "ellipsis",
2809
+ whiteSpace: "nowrap",
2810
+ minWidth: 0
2811
+ }
2812
+ },
2813
+ row.projectPath || "Untitled project"
2814
+ )
2815
+ ),
2816
+ (0, import_react.createElement)(
2817
+ Button,
2818
+ { variant: "outline", size: "sm", onClick: () => removeRow(i), "aria-label": `Remove project ${i + 1}`, title: "Remove mapping" },
2819
+ "\u2715"
2820
+ )
2821
+ ),
2822
+ // Grid 2-col for paths
2823
+ (0, import_react.createElement)(
2824
+ "div",
2825
+ { "data-maestro-project-grid": "", style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } },
2826
+ (0, import_react.createElement)(
2827
+ "label",
2828
+ { style: fieldLabelStyle, "data-maestro-field": "" },
2829
+ "GitLab path",
2830
+ (0, import_react.createElement)(FieldInput, {
2831
+ placeholder: "group/project",
2832
+ value: row.projectPath,
2833
+ onChange: (e) => updateRow(i, "projectPath", e.target.value),
2834
+ "aria-label": `GitLab path ${i + 1}`,
2835
+ style: { width: "100%" }
2836
+ })
2837
+ ),
2838
+ (0, import_react.createElement)(
2839
+ "label",
2840
+ { style: fieldLabelStyle, "data-maestro-field": "" },
2841
+ "Local checkout",
2842
+ (0, import_react.createElement)(FieldInput, {
2843
+ placeholder: "/path/to/local/clone",
2844
+ value: row.localRepoPath,
2845
+ onChange: (e) => updateRow(i, "localRepoPath", e.target.value),
2846
+ "aria-label": `Local checkout ${i + 1}`,
2847
+ style: { width: "100%" }
2848
+ })
2849
+ )
2850
+ ),
2851
+ // Row for profile + model — unified label+control, gap 12, pill h36
2852
+ (0, import_react.createElement)(
2853
+ "div",
2854
+ { "data-maestro-project-profile-row": "", style: { display: "flex", gap: 12, flexWrap: "wrap", alignItems: "flex-start" } },
2855
+ (0, import_react.createElement)(
2856
+ "label",
2857
+ { style: { ...fieldLabelStyle, flex: "1 1 160px", minWidth: 0 } },
2858
+ "Review profile",
2859
+ (0, import_react.createElement)(
2860
+ "select",
2861
+ {
2862
+ value: row.reviewProfile ?? "magento2",
2863
+ onChange: (e) => updateRow(i, "reviewProfile", e.target.value),
2864
+ "aria-label": `Review profile ${i + 1}`,
2865
+ style: {
2866
+ height: 36,
2867
+ width: "100%",
2868
+ padding: "0 14px",
2869
+ border: "none",
2870
+ borderRadius: 18,
2871
+ background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
2872
+ color: t.labelPrimary,
2873
+ font: "inherit",
2874
+ fontSize: 13
2875
+ }
2876
+ },
2877
+ (0, import_react.createElement)("option", { value: "magento2" }, "Magento 2"),
2878
+ (0, import_react.createElement)("option", { value: "generic" }, "Generic")
2879
+ )
2880
+ ),
2881
+ (0, import_react.createElement)(
2882
+ "label",
2883
+ { style: { ...fieldLabelStyle, flex: "1 1 200px", minWidth: 0 } },
2884
+ "Model override",
2885
+ (0, import_react.createElement)(ReviewModelSelector, {
2886
+ value: row.reviewModel ?? null,
2887
+ catalog,
2888
+ fallbackValue: globalReviewModel ?? catalog?.current ?? null,
2889
+ fallbackLabel: globalReviewModel ? "Use Global" : "Use DSH default",
2890
+ onChange: (v) => updateRow(i, "reviewModel", v),
2891
+ label: null
2892
+ })
2893
+ )
2894
+ )
2895
+ )
2620
2896
  )
2621
- ))
2897
+ )
2622
2898
  );
2623
2899
  }
2624
2900
  function LanAccess({ proxyStatus, lanPin }) {
@@ -2626,40 +2902,26 @@ function LanAccess({ proxyStatus, lanPin }) {
2626
2902
  const [selected, setSelected] = (0, import_react.useState)(0);
2627
2903
  const index = Math.min(selected, Math.max(urls.length - 1, 0));
2628
2904
  if (!proxyStatus?.running) {
2629
- return (0, import_react.createElement)("p", { style: errorStyle }, proxyStatus?.errorMessage ?? "Proxy not running");
2905
+ return (0, import_react.createElement)("p", { style: { color: t.stateError, fontSize: 12, margin: "8px 0 0" } }, proxyStatus?.errorMessage ?? "Proxy not running");
2630
2906
  }
2631
2907
  return (0, import_react.createElement)(
2632
2908
  "div",
2633
2909
  null,
2634
- (0, import_react.createElement)("p", { style: captionStyle }, lanPin?.enabled === true ? "Open this full DSH UI from any device on your network \u2014 visitors enter the LAN PIN below." : "Open this full DSH UI from any device on your network \u2014 no PIN needed."),
2635
- urls.length > 0 && (0, import_react.createElement)(
2910
+ (0, import_react.createElement)("p", { style: captionStyle }, lanPin?.enabled === true ? "Open this DSH UI from any device on your network \u2014 visitors enter the LAN PIN below." : "Open this DSH UI from any device on your network \u2014 no PIN needed."),
2911
+ urls.length > 0 ? (0, import_react.createElement)(
2636
2912
  "div",
2637
2913
  { style: { display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 12 } },
2638
- urls.map((url, i) => (0, import_react.createElement)("button", {
2639
- key: url,
2640
- onClick: () => setSelected(i),
2641
- style: {
2642
- ...secondaryButtonStyle,
2643
- height: 26,
2644
- padding: "0 10px",
2645
- fontSize: 12,
2646
- borderRadius: 13,
2647
- ...i === index ? { background: "var(--dsw-alias-button-primary-fill)", borderColor: "transparent", color: "var(--dsw-alias-label-primary-foreground)" } : {}
2648
- }
2649
- }, url.replace(/^http:\/\//, "")))
2650
- ),
2651
- urls.length > 0 && (0, import_react.createElement)(
2914
+ ...urls.map(
2915
+ (url, i) => (0, import_react.createElement)(Button, { key: url, variant: i === index ? "primary" : "outline", size: "sm", onClick: () => setSelected(i) }, url.replace(/^http:\/\//, ""))
2916
+ )
2917
+ ) : null,
2918
+ urls.length > 0 ? (0, import_react.createElement)(
2652
2919
  "div",
2653
- { style: { display: "flex", gap: 14, alignItems: "center" } },
2920
+ { "data-maestro-qr-row": "", style: { display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" } },
2654
2921
  (0, import_react.createElement)(QrImage, { url: urls[index], size: 116 }),
2655
- (0, import_react.createElement)(
2656
- "div",
2657
- null,
2658
- (0, import_react.createElement)("div", { style: codeStyle }, urls[index]),
2659
- (0, import_react.createElement)("p", { style: captionStyle }, "Scan with a phone connected to the same network.")
2660
- )
2661
- ),
2662
- lanPin !== null && (0, import_react.createElement)(LanPinRow, { lanPin })
2922
+ (0, import_react.createElement)("div", null, (0, import_react.createElement)("div", { style: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12, color: t.labelPrimary, wordBreak: "break-all" } }, urls[index]), (0, import_react.createElement)("p", { style: captionStyle }, "Scan with a phone on the same network."))
2923
+ ) : null,
2924
+ lanPin !== null ? (0, import_react.createElement)(LanPinRow, { lanPin }) : null
2663
2925
  );
2664
2926
  }
2665
2927
  function LanPinRow({ lanPin }) {
@@ -2669,22 +2931,17 @@ function LanPinRow({ lanPin }) {
2669
2931
  (0, import_react.createElement)(
2670
2932
  "label",
2671
2933
  { style: { display: "flex", gap: 8, alignItems: "center", cursor: "pointer" } },
2672
- (0, import_react.createElement)("input", {
2673
- type: "checkbox",
2674
- checked: lanPin.enabled,
2675
- onChange: (e) => lanPin.onToggle(e.target.checked),
2676
- style: { width: 15, height: 15, accentColor: "var(--dsw-alias-button-primary-fill)" }
2677
- }),
2934
+ (0, import_react.createElement)("input", { type: "checkbox", checked: lanPin.enabled, onChange: (e) => lanPin.onToggle(e.target.checked), style: { width: 15, height: 15, accentColor: t.primaryFill } }),
2678
2935
  (0, import_react.createElement)("span", { style: { ...fieldLabelStyle, margin: 0 } }, "Require a PIN on the LAN")
2679
2936
  ),
2680
- lanPin.enabled && (0, import_react.createElement)(
2937
+ lanPin.enabled ? (0, import_react.createElement)(
2681
2938
  "div",
2682
- { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 8 } },
2939
+ { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 8, flexWrap: "wrap" } },
2683
2940
  (0, import_react.createElement)("span", { style: { ...fieldLabelStyle, margin: 0 } }, "LAN PIN"),
2684
- (0, import_react.createElement)("code", { style: { ...codeStyle, fontSize: 15, letterSpacing: 2 } }, lanPin.show ? lanPin.pin ?? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"),
2685
- lanPin.show ? (0, import_react.createElement)("button", { onClick: lanPin.onHide, style: secondaryButtonStyle }, "Hide") : (0, import_react.createElement)("button", { onClick: lanPin.onShow, style: secondaryButtonStyle }, "Show"),
2686
- (0, import_react.createElement)("button", { onClick: lanPin.onRotate, style: secondaryButtonStyle }, "Rotate")
2687
- )
2941
+ (0, import_react.createElement)("code", { style: { fontFamily: "ui-monospace, monospace", fontSize: 15, letterSpacing: 2, color: t.labelPrimary } }, lanPin.show ? lanPin.pin ?? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"),
2942
+ lanPin.show ? (0, import_react.createElement)(Button, { variant: "outline", size: "sm", onClick: lanPin.onHide }, "Hide") : (0, import_react.createElement)(Button, { variant: "outline", size: "sm", onClick: lanPin.onShow }, "Show"),
2943
+ (0, import_react.createElement)(Button, { variant: "outline", size: "sm", onClick: lanPin.onRotate }, "Rotate")
2944
+ ) : null
2688
2945
  );
2689
2946
  }
2690
2947
  function PublicAccess({ status, pin, showPin, onRevealPin, onHidePin, onRotatePin }) {
@@ -2696,27 +2953,30 @@ function PublicAccess({ status, pin, showPin, onRevealPin, onHidePin, onRotatePi
2696
2953
  null,
2697
2954
  (0, import_react.createElement)(
2698
2955
  "div",
2699
- { style: { display: "flex", gap: 14, alignItems: "center", marginBottom: 12 } },
2956
+ { "data-maestro-qr-row": "", style: { display: "flex", gap: 14, alignItems: "center", marginBottom: 12, flexWrap: "wrap" } },
2700
2957
  (0, import_react.createElement)(QrImage, { url: status.publicUrl, size: 116 }),
2701
- (0, import_react.createElement)(
2702
- "div",
2703
- null,
2704
- (0, import_react.createElement)("div", { style: codeStyle }, status.publicUrl),
2705
- (0, import_react.createElement)("p", { style: captionStyle }, "Works from anywhere; visitors enter the PIN below.")
2706
- )
2958
+ (0, import_react.createElement)("div", null, (0, import_react.createElement)("div", { style: { fontFamily: "ui-monospace, monospace", fontSize: 12, color: t.labelPrimary, wordBreak: "break-all" } }, status.publicUrl), (0, import_react.createElement)("p", { style: captionStyle }, "Works from anywhere; visitors enter the PIN below."))
2707
2959
  )
2708
2960
  ) : (0, import_react.createElement)("p", { style: captionStyle }, "Start the tunnel to get a public address."),
2709
2961
  (0, import_react.createElement)(
2710
2962
  "div",
2711
- { style: { display: "flex", gap: 8, alignItems: "center" } },
2963
+ { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" } },
2712
2964
  (0, import_react.createElement)("span", { style: { ...fieldLabelStyle, margin: 0 } }, "Access PIN"),
2713
- (0, import_react.createElement)("code", { style: { ...codeStyle, fontSize: 15, letterSpacing: 2 } }, showPin && pin !== null ? pin : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"),
2714
- showPin ? (0, import_react.createElement)("button", { onClick: onHidePin, style: secondaryButtonStyle }, "Hide") : (0, import_react.createElement)("button", { onClick: onRevealPin, style: secondaryButtonStyle }, "Show"),
2715
- (0, import_react.createElement)("button", { onClick: onRotatePin, style: secondaryButtonStyle }, "Rotate")
2965
+ (0, import_react.createElement)("code", { style: { fontFamily: "ui-monospace, monospace", fontSize: 15, letterSpacing: 2, color: t.labelPrimary } }, showPin && pin !== null ? pin : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"),
2966
+ showPin ? (0, import_react.createElement)(Button, { variant: "outline", size: "sm", onClick: onHidePin }, "Hide") : (0, import_react.createElement)(Button, { variant: "outline", size: "sm", onClick: onRevealPin }, "Show"),
2967
+ (0, import_react.createElement)(Button, { variant: "outline", size: "sm", onClick: onRotatePin }, "Rotate")
2716
2968
  ),
2717
2969
  (0, import_react.createElement)("p", { style: captionStyle }, "Stays the same across tunnel and DSH restarts; use Rotate when you need a new PIN.")
2718
2970
  );
2719
2971
  }
2972
+ function NamedTunnelSetupNote() {
2973
+ return (0, import_react.createElement)(
2974
+ "div",
2975
+ { style: { ...cardInsetStyle, marginTop: "12px" } },
2976
+ (0, import_react.createElement)("p", { style: { ...captionStyle, marginBottom: 4, fontWeight: 500, color: t.labelPrimary } }, "Named tunnel \u2014 one-time manual setup (requires your own Cloudflare account):"),
2977
+ (0, import_react.createElement)("ol", { style: { margin: "4px 0", paddingLeft: 20, fontSize: 12, color: t.labelSecondary, lineHeight: "18px" } }, (0, import_react.createElement)("li", null, "cloudflared tunnel login"), (0, import_react.createElement)("li", null, "cloudflared tunnel create dsh-maestro-webhook"), (0, import_react.createElement)("li", null, "cloudflared tunnel route dns dsh-maestro-webhook <your-hostname>"), (0, import_react.createElement)("li", null, "Paste the printed Tunnel ID, credentials file path and hostname below."))
2978
+ );
2979
+ }
2720
2980
  function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2721
2981
  const [status, setStatus] = (0, import_react.useState)(null);
2722
2982
  const [proxyStatus, setProxyStatus] = (0, import_react.useState)(null);
@@ -2729,11 +2989,141 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2729
2989
  const [lanPinEnabled, setLanPinEnabled] = (0, import_react.useState)(false);
2730
2990
  const [lanPin, setLanPin] = (0, import_react.useState)(null);
2731
2991
  const [showLanPin, setShowLanPin] = (0, import_react.useState)(false);
2992
+ const [guard, setGuard] = (0, import_react.useState)({});
2993
+ const [patternsText, setPatternsText] = (0, import_react.useState)("");
2994
+ const [placeholdersText, setPlaceholdersText] = (0, import_react.useState)("");
2995
+ const [supervisorCfg, setSupervisorCfg] = (0, import_react.useState)({});
2996
+ const [notifierCfg, setNotifierCfg] = (0, import_react.useState)({});
2997
+ const [activeTab, setActiveTab] = (0, import_react.useState)("tunnel");
2998
+ (0, import_react.useEffect)(() => {
2999
+ const css = `
3000
+ /* Maestro nested tabs \u2014 market-like pill bar + mobile fixes */
3001
+ [data-maestro-tabs] { display:flex; gap:6px; overflow-x:auto; overflow-y:hidden; scrollbar-width:none; -webkit-overflow-scrolling:touch; overscroll-behavior-x:contain; touch-action:pan-x; padding-bottom:6px; margin-bottom:10px; border-bottom:1px solid var(--dsw-alias-border-l1, rgba(0,0,0,.08)); }
3002
+ [data-maestro-tabs]::-webkit-scrollbar { display:none; width:0; height:0; }
3003
+ [data-maestro-tab] { flex:none; height:32px; min-width:fit-content; padding:0 14px; border-radius:999px; border:1px solid var(--dsw-alias-border-l1, rgba(0,0,0,.12)); background:transparent; color:var(--dsw-alias-label-primary); font-size:13px; line-height:20px; white-space:nowrap; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; font-family:inherit; -webkit-tap-highlight-color:transparent; }
3004
+ [data-maestro-tab][data-active="true"] { background:var(--dsw-specific-sidebar-nav-item-active, #EBEEF2); border-color:var(--dsw-specific-sidebar-nav-item-active, #EBEEF2); color:var(--dsw-alias-label-primary); }
3005
+ [data-maestro-tab]:hover { background:var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.06)); }
3006
+ [data-maestro-tab][data-active="true"]:hover { background:var(--dsw-specific-sidebar-nav-item-active, #EBEEF2); }
3007
+ [data-maestro-tab]:focus-visible { outline:2px solid var(--dsw-alias-state-business-primary, #4f6ef7); outline-offset:1px; }
3008
+ [data-maestro-panel] { width:100%; min-width:0; box-sizing:border-box; }
3009
+ /* label \u2192 input d\xEDnh nhau fix: flex column gap + full width */
3010
+ [data-maestro-panel] label { gap:6px !important; }
3011
+ [data-maestro-row]:last-child { border-bottom:none !important; }
3012
+ [data-maestro-panel] label > span { width:100% !important; box-sizing:border-box !important; }
3013
+ @media (max-width: 640px) {
3014
+ [data-maestro-settings-card] { max-width:100% !important; gap:6px !important; padding:0 2px !important; }
3015
+ [data-maestro-tabs] { gap:6px !important; padding:0 0 6px !important; margin:0 -2px 10px !important; }
3016
+ [data-maestro-tab] { height:32px !important; padding:0 12px !important; font-size:13px !important; }
3017
+ [data-maestro-mapping-row] { flex-direction:column !important; align-items:stretch !important; gap:8px !important; }
3018
+ [data-maestro-mapping-row] > * { flex:1 1 100% !important; width:100% !important; max-width:100% !important; }
3019
+ [data-maestro-qr-row] { flex-direction:column !important; align-items:flex-start !important; }
3020
+ [data-maestro-trigger-wrap] { max-width:100% !important; }
3021
+ [data-maestro-menu] { min-width:0 !important; max-width:calc(100vw - 32px) !important; left:0 !important; right:auto !important; }
3022
+ [data-maestro-panel] label { gap:8px !important; }
3023
+ div[data-maestro-row] { flex-direction:column !important; align-items:stretch !important; padding:12px 0 !important; }
3024
+ [data-maestro-row-text] { padding-right:0 !important; }
3025
+ [data-maestro-control] { width:100% !important; justify-content:flex-start !important; }
3026
+ [data-maestro-control] > span { width:100% !important; }
3027
+ [data-maestro-control] select { width:100% !important; }
3028
+ [data-maestro-project-grid] { grid-template-columns:1fr !important; }
3029
+ [data-maestro-project-card] { padding:10px !important; }
3030
+ [data-maestro-project-profile-row] { flex-direction:column !important; align-items:stretch !important; }
3031
+ [data-maestro-project-profile-row] > label { flex:1 1 100% !important; width:100% !important; }
3032
+ }
3033
+ @media (max-width: 390px) {
3034
+ [data-maestro-tab] { height:30px !important; padding:0 10px !important; font-size:12px !important; }
3035
+ }
3036
+ `;
3037
+ const tag = document.createElement("style");
3038
+ tag.dataset.plugin = "@ddtcorex/dsh-maestro-config";
3039
+ tag.dataset.pluginCss = "maestro/mobile-tabs.css";
3040
+ tag.textContent = css;
3041
+ document.head.appendChild(tag);
3042
+ return () => tag.remove();
3043
+ }, []);
2732
3044
  const call = async (endpoint, payload) => {
2733
3045
  const res = await rpcCall(endpoint, payload);
2734
3046
  if (!res?.ok) throw new Error(res?.error?.message ?? "RPC failed");
2735
3047
  return res.value;
2736
3048
  };
3049
+ const unwrap = (res) => {
3050
+ if (res && typeof res === "object" && "ok" in res) {
3051
+ if (res.ok) return res.value;
3052
+ throw new Error(res.error?.message ?? "RPC failed");
3053
+ }
3054
+ return res;
3055
+ };
3056
+ const cfgGet = async (domain) => {
3057
+ if (!configRpcCall) throw new Error("config RPC not available");
3058
+ const res = await configRpcCall("get", { domain });
3059
+ return unwrap(res);
3060
+ };
3061
+ const cfgSet = async (domain, patch) => {
3062
+ if (!configRpcCall) throw new Error("config RPC not available");
3063
+ const res = await configRpcCall("set", { domain, patch });
3064
+ return unwrap(res);
3065
+ };
3066
+ const saveGuard = async (patch) => {
3067
+ setError(null);
3068
+ const next = { ...guard, ...patch };
3069
+ if (patch.gitProtection && guard.gitProtection) next.gitProtection = { ...guard.gitProtection, ...patch.gitProtection };
3070
+ setGuard(next);
3071
+ try {
3072
+ await cfgSet("guard", patch);
3073
+ } catch (e) {
3074
+ setError(e.message ?? String(e));
3075
+ }
3076
+ };
3077
+ const commitBlacklistPatterns = async (text) => {
3078
+ const patterns = text.split("\n").map((s) => s.trim()).filter(Boolean);
3079
+ setError(null);
3080
+ try {
3081
+ await cfgSet("guardBlacklist", { patterns });
3082
+ } catch (e) {
3083
+ setError(e.message ?? String(e));
3084
+ }
3085
+ };
3086
+ const commitPlaceholders = async () => {
3087
+ setError(null);
3088
+ let obj = {};
3089
+ try {
3090
+ obj = placeholdersText.trim() ? JSON.parse(placeholdersText) : {};
3091
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) throw new Error("placeholders must be JSON object");
3092
+ } catch (e) {
3093
+ setError(`placeholders JSON invalid: ${e.message ?? String(e)}`);
3094
+ return;
3095
+ }
3096
+ try {
3097
+ await cfgSet("guardBlacklist", { placeholders: obj });
3098
+ } catch (e) {
3099
+ setError(e.message ?? String(e));
3100
+ }
3101
+ };
3102
+ const saveSupervisorCfg = async (patch) => {
3103
+ setError(null);
3104
+ setSupervisorCfg((prev) => ({ ...prev, ...patch }));
3105
+ try {
3106
+ await cfgSet("supervisor", patch);
3107
+ } catch (e) {
3108
+ setError(e.message ?? String(e));
3109
+ }
3110
+ };
3111
+ const saveNotifierCfg = async (patch) => {
3112
+ setError(null);
3113
+ setNotifierCfg((prev) => {
3114
+ const next = { ...prev };
3115
+ for (const [k, v] of Object.entries(patch)) {
3116
+ if (k === "telegram" && typeof v === "object" && v !== null) next.telegram = { ...prev.telegram ?? {}, ...v };
3117
+ else next[k] = v;
3118
+ }
3119
+ return next;
3120
+ });
3121
+ try {
3122
+ await cfgSet("notifier", patch);
3123
+ } catch (e) {
3124
+ setError(e.message ?? String(e));
3125
+ }
3126
+ };
2737
3127
  const refresh = async () => {
2738
3128
  try {
2739
3129
  setStatus(await call(MAESTRO_ENDPOINTS.status, {}));
@@ -2749,9 +3139,17 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2749
3139
  });
2750
3140
  if (configRpcCall) {
2751
3141
  configRpcCall("get", { domain: "supervisor" }).then((res) => {
2752
- if (res?.ok && res.value?.model) {
2753
- setConfig((prev) => ({ ...prev, supervisorModel: res.value.model }));
2754
- }
3142
+ if (res?.ok && res.value?.model) setConfig((prev) => ({ ...prev, supervisorModel: res.value.model }));
3143
+ }).catch(() => {
3144
+ });
3145
+ Promise.all([cfgGet("guard").catch(() => ({})), cfgGet("guardBlacklist").catch(() => ({ patterns: [], placeholders: {} })), cfgGet("supervisor").catch(() => ({})), cfgGet("notifier").catch(() => ({}))]).then(([g, bl, sup, not]) => {
3146
+ setGuard(g ?? {});
3147
+ const pats = Array.isArray(bl?.patterns) ? bl.patterns : [];
3148
+ const ph = bl?.placeholders && typeof bl.placeholders === "object" ? bl.placeholders : {};
3149
+ setPatternsText(pats.join("\n"));
3150
+ setPlaceholdersText(JSON.stringify(ph, null, 2));
3151
+ setSupervisorCfg(sup ?? {});
3152
+ setNotifierCfg(not ?? {});
2755
3153
  }).catch(() => {
2756
3154
  });
2757
3155
  }
@@ -2765,8 +3163,8 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2765
3163
  }, []);
2766
3164
  (0, import_react.useEffect)(() => {
2767
3165
  refresh();
2768
- const t = setInterval(refresh, 3e3);
2769
- return () => clearInterval(t);
3166
+ const t2 = setInterval(refresh, 3e3);
3167
+ return () => clearInterval(t2);
2770
3168
  }, []);
2771
3169
  const revealPin = async () => {
2772
3170
  if (pin === null) {
@@ -2856,7 +3254,7 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2856
3254
  try {
2857
3255
  const res = await configRpcCall("set", { domain: "supervisor", patch: { model: value } });
2858
3256
  if (res?.ok) return;
2859
- } catch (e) {
3257
+ } catch {
2860
3258
  }
2861
3259
  }
2862
3260
  try {
@@ -2865,150 +3263,131 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2865
3263
  setError(err.message);
2866
3264
  }
2867
3265
  };
2868
- return (0, import_react.createElement)(
2869
- "div",
2870
- { "data-maestro-settings-card": "", style: { maxWidth: 520 } },
2871
- (0, import_react.createElement)("h3", { style: { ...headingStyle, fontSize: 15, margin: "0 0 12px" } }, "Maestro"),
2872
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Tunnel mode"),
2873
- (0, import_react.createElement)(
2874
- "select",
2875
- { value: config.tunnelMode, style: inputStyle, onChange: (e) => saveField("tunnelMode", e.target.value) },
2876
- (0, import_react.createElement)("option", { value: "quick" }, "Quick (no setup, URL changes on restart)"),
2877
- (0, import_react.createElement)("option", { value: "named" }, "Named (stable URL, one-time setup)")
2878
- ),
2879
- config.tunnelMode === "named" && (0, import_react.createElement)(NamedTunnelSetupNote),
2880
- config.tunnelMode === "named" && (0, import_react.createElement)(
3266
+ const TABS = [
3267
+ { id: "tunnel", label: "Tunnel" },
3268
+ { id: "notify", label: "Notify" },
3269
+ { id: "gitlab", label: "GitLab" },
3270
+ { id: "review", label: "Review" },
3271
+ { id: "guard", label: "Guard" },
3272
+ { id: "blacklist", label: "Blacklist" },
3273
+ { id: "supervisor", label: "Supervisor" },
3274
+ { id: "notifier", label: "Notifier" }
3275
+ ];
3276
+ const tabContents = {
3277
+ tunnel: (0, import_react.createElement)(
2881
3278
  "div",
2882
- null,
2883
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Tunnel ID"),
2884
- (0, import_react.createElement)("input", { placeholder: "Tunnel ID", style: inputStyle, value: config.tunnelId ?? "", onChange: (e) => saveField("tunnelId", e.target.value) }),
2885
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Credentials file path"),
2886
- (0, import_react.createElement)("input", { placeholder: "~/.cloudflared/<id>.json", style: inputStyle, value: config.tunnelCredentialsFile ?? "", onChange: (e) => saveField("tunnelCredentialsFile", e.target.value) }),
2887
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Hostname"),
2888
- (0, import_react.createElement)("input", { placeholder: "dsh.example.com", style: inputStyle, value: config.tunnelHostname ?? "", onChange: (e) => saveField("tunnelHostname", e.target.value) })
3279
+ { style: { display: "flex", flexDirection: "column" } },
3280
+ (0, import_react.createElement)(SettingRow, { title: "Tunnel mode", description: "Quick = ephemeral URL, Named = stable URL with Cloudflare setup.", control: (0, import_react.createElement)("select", { value: config.tunnelMode, onChange: (e) => saveField("tunnelMode", e.target.value), style: { height: 36, padding: "0 12px", border: `1px solid ${t.borderL2}`, borderRadius: 18, background: "var(--dsw-alias-bg-module-platform, #F5F6F7)", color: t.labelPrimary, font: "inherit", fontSize: 13 } }, (0, import_react.createElement)("option", { value: "quick" }, "Quick"), (0, import_react.createElement)("option", { value: "named" }, "Named")) }),
3281
+ config.tunnelMode === "named" ? (0, import_react.createElement)(NamedTunnelSetupNote, null) : null,
3282
+ config.tunnelMode === "named" ? (0, import_react.createElement)(
3283
+ "div",
3284
+ { style: { display: "flex", flexDirection: "column" } },
3285
+ (0, import_react.createElement)(SettingRow, { title: "Tunnel ID", description: "Cloudflare tunnel ID.", control: (0, import_react.createElement)(FieldInput, { placeholder: "Tunnel ID", value: config.tunnelId ?? "", onChange: (e) => saveField("tunnelId", e.target.value), style: { width: 260 } }) }),
3286
+ (0, import_react.createElement)(SettingRow, { title: "Credentials file", description: "Path to tunnel credentials JSON.", control: (0, import_react.createElement)(FieldInput, { placeholder: "~/.cloudflared/<id>.json", value: config.tunnelCredentialsFile ?? "", onChange: (e) => saveField("tunnelCredentialsFile", e.target.value), style: { width: 260 } }) }),
3287
+ (0, import_react.createElement)(SettingRow, { title: "Hostname", description: "Public hostname for the tunnel.", control: (0, import_react.createElement)(FieldInput, { placeholder: "dsh.example.com", value: config.tunnelHostname ?? "", onChange: (e) => saveField("tunnelHostname", e.target.value), style: { width: 260 } }) })
3288
+ ) : null,
3289
+ (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", padding: "12px 0", borderBottom: `1px solid ${t.borderL2}` } }, status?.running ? (0, import_react.createElement)(Button, { variant: "outline", size: "md", disabled: busy, onClick: stopTunnel }, "Stop tunnel") : (0, import_react.createElement)(Button, { variant: "primary", size: "md", disabled: busy, onClick: startTunnel }, "Start tunnel")),
3290
+ (0, import_react.createElement)("div", { style: { ...cardInsetStyle, marginTop: "12px" } }, (0, import_react.createElement)("div", { style: { fontSize: 13, fontWeight: 600, color: t.labelPrimary } }, "Remote access \u2014 LAN"), (0, import_react.createElement)(LanAccess, { proxyStatus, lanPin: lanPinEnabled === null ? null : { enabled: lanPinEnabled, pin: lanPin, show: showLanPin, onShow: revealLanPin, onHide: () => setShowLanPin(false), onRotate: rotateLanPin, onToggle: toggleLanPin } })),
3291
+ (0, import_react.createElement)("div", { style: { ...cardInsetStyle, marginTop: "12px" } }, (0, import_react.createElement)("div", { style: { fontSize: 13, fontWeight: 600, color: t.labelPrimary } }, "Public access"), (0, import_react.createElement)(PublicAccess, { status, pin, showPin, onRevealPin: revealPin, onHidePin: () => setShowPin(false), onRotatePin: rotatePin }))
2889
3292
  ),
2890
- (0, import_react.createElement)(
3293
+ notify: (0, import_react.createElement)(
2891
3294
  "div",
2892
- { style: { marginTop: 12 } },
2893
- status?.running ? (0, import_react.createElement)("button", { disabled: busy, onClick: stopTunnel, style: secondaryButtonStyle }, "Stop tunnel") : (0, import_react.createElement)("button", { disabled: busy, onClick: startTunnel, style: primaryButtonStyle }, "Start tunnel")
3295
+ { style: { display: "flex", flexDirection: "column" } },
3296
+ (0, import_react.createElement)("div", { style: { padding: "12px 0", borderBottom: `1px solid ${t.borderL2}` } }, (0, import_react.createElement)("p", { style: captionStyle }, "Sends one protected startup update with the current public-access PIN to a single chat. Leave either credential blank to disable.")),
3297
+ (0, import_react.createElement)(SettingRow, { title: "Bot token", description: "Telegram bot token from @BotFather.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(FieldInput, { placeholder: "123456:ABC-DEF...", type: "password", autoComplete: "off", value: config.hasTelegramBotToken ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "", onChange: (e) => saveField("telegramBotToken", e.target.value), style: { width: 220 } }), config.hasTelegramBotToken ? (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveField("telegramBotToken", "") }, "Clear") : null) }),
3298
+ (0, import_react.createElement)(SettingRow, { title: "Chat ID", description: "Target chat, e.g. -1001234567890.", control: (0, import_react.createElement)(FieldInput, { placeholder: "-1001234567890", value: config.telegramChatId ?? "", onChange: (e) => saveField("telegramChatId", e.target.value), style: { width: 220 } }) }),
3299
+ (0, import_react.createElement)(ToggleRow, { title: "Also notify about reviews", description: "One message per review run with its outcome.", checked: config.telegramReviewNotifications === true, onChange: (v) => saveField("telegramReviewNotifications", v) })
2894
3300
  ),
2895
- (0, import_react.createElement)(
3301
+ gitlab: (0, import_react.createElement)(
2896
3302
  "div",
2897
- { style: sectionStyle },
2898
- (0, import_react.createElement)("h4", { style: headingStyle }, "Remote access (LAN)"),
2899
- (0, import_react.createElement)(LanAccess, {
2900
- proxyStatus,
2901
- lanPin: lanPinEnabled === null ? null : {
2902
- enabled: lanPinEnabled,
2903
- pin: lanPin,
2904
- show: showLanPin,
2905
- onShow: revealLanPin,
2906
- onHide: () => setShowLanPin(false),
2907
- onRotate: rotateLanPin,
2908
- onToggle: toggleLanPin
2909
- }
2910
- })
3303
+ { style: { display: "flex", flexDirection: "column" } },
3304
+ (0, import_react.createElement)(SettingRow, { title: "GitLab base URL", description: "e.g. https://gitlab.example.com", control: (0, import_react.createElement)(FieldInput, { placeholder: "https://gitlab.example.com", value: config.gitlabBaseUrl ?? "", onChange: (e) => saveField("gitlabBaseUrl", e.target.value), style: { width: 260 } }) }),
3305
+ (0, import_react.createElement)(SettingRow, { title: "GitLab token", description: "Personal access token with api scope.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(FieldInput, { type: "password", autoComplete: "off", value: config.hasGitlabToken ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "", placeholder: "GitLab token", onChange: (e) => saveField("gitlabToken", e.target.value), style: { width: 200 } }), config.hasGitlabToken ? (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveField("gitlabToken", "") }, "Clear") : null) }),
3306
+ (0, import_react.createElement)(SettingRow, { title: "Bot username", description: "Username of the bot that posts reviews.", control: (0, import_react.createElement)(FieldInput, { placeholder: "maestro-bot", value: config.botUsername ?? "", onChange: (e) => saveField("botUsername", e.target.value), style: { width: 220 } }) }),
3307
+ (0, import_react.createElement)(SettingRow, { title: "Webhook secret", description: "Secret token for GitLab webhooks.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(FieldInput, { type: "password", autoComplete: "off", value: config.hasWebhookSecret ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : "", placeholder: "Webhook secret", onChange: (e) => saveField("webhookSecret", e.target.value), style: { width: 200 } }), (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveField("webhookSecret", generateWebhookSecret()) }, "Generate")) }),
3308
+ (0, import_react.createElement)(
3309
+ "div",
3310
+ { style: { padding: "16px 0", display: "flex", flexDirection: "column", gap: 6 } },
3311
+ (0, import_react.createElement)("p", { style: captionStyle }, "In GitLab: Settings \u2192 Webhooks, Secret token = this value, enable Merge request events. Webhook URL:"),
3312
+ (0, import_react.createElement)("div", { style: { fontFamily: "ui-monospace, monospace", fontSize: 12, color: t.labelPrimary, wordBreak: "break-all", padding: "10px 12px", borderRadius: 8, background: t.bgLayer3, border: `1px solid ${t.borderL2}`, overflowWrap: "anywhere" } }, gitlabWebhookUrl(config.tunnelHostname))
3313
+ )
2911
3314
  ),
2912
- (0, import_react.createElement)(
3315
+ review: (0, import_react.createElement)(
2913
3316
  "div",
2914
- { style: sectionStyle },
2915
- (0, import_react.createElement)("h4", { style: headingStyle }, "Public access"),
2916
- (0, import_react.createElement)(PublicAccess, { status, pin, showPin, onRevealPin: revealPin, onHidePin: () => setShowPin(false), onRotatePin: rotatePin })
3317
+ { style: { display: "flex", flexDirection: "column" } },
3318
+ (0, import_react.createElement)(ToggleRow, { title: "Re-review on push", description: "When new commits are pushed, trigger an automatic re-review.", checked: config.autoRereviewOnPush === true, onChange: (v) => saveField("autoRereviewOnPush", v) }),
3319
+ (0, import_react.createElement)(SettingRow, { title: "Global review model", description: "Model for automated reviews. Empty = DSH default.", control: (0, import_react.createElement)(ReviewModelSelector, { value: config.reviewModel ?? null, catalog, fallbackValue: catalog?.current ?? null, fallbackLabel: "Use DSH default", onChange: (v) => saveField("reviewModel", v), label: null }) }),
3320
+ (0, import_react.createElement)(SettingRow, { title: "Supervisor model", description: "Model for auto-fixing DSH Web crashes.", control: (0, import_react.createElement)(ReviewModelSelector, { value: config.supervisorModel ?? null, catalog, fallbackValue: catalog?.current ?? null, fallbackLabel: "Use DSH default", onChange: (v) => saveField("supervisorModel", v), label: null }) }),
3321
+ (0, import_react.createElement)(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: (mappings) => saveField("projectMappings", mappings), catalog, globalReviewModel: config.reviewModel ?? null })
2917
3322
  ),
2918
- (0, import_react.createElement)(
3323
+ guard: (0, import_react.createElement)(
2919
3324
  "div",
2920
- { style: sectionStyle },
2921
- (0, import_react.createElement)("h4", { style: headingStyle }, "Telegram notifications"),
2922
- (0, import_react.createElement)("p", { style: captionStyle }, "Send one protected startup update with the current public-access PIN to a single Telegram chat."),
2923
- (0, import_react.createElement)(SecretInput, {
2924
- label: "Bot token",
2925
- placeholder: "123456:ABC-DEF...",
2926
- hasSaved: config.hasTelegramBotToken,
2927
- onSave: (value) => saveField("telegramBotToken", value)
2928
- }),
2929
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Chat ID"),
2930
- (0, import_react.createElement)("input", { placeholder: "-1001234567890", autoComplete: "off", style: inputStyle, value: config.telegramChatId ?? "", onChange: (e) => saveField("telegramChatId", e.target.value) }),
2931
- (0, import_react.createElement)(ToggleField, {
2932
- label: "Also notify about finished reviews",
2933
- caption: "One message per review run with its outcome and a short summary.",
2934
- checked: config.telegramReviewNotifications,
2935
- onChange: (checked) => saveField("telegramReviewNotifications", checked)
2936
- }),
2937
- (0, import_react.createElement)("p", { style: captionStyle }, "Leave either credential blank to disable notifications. Telegram delivery failures never prevent DSH from starting.")
3325
+ { style: { display: "flex", flexDirection: "column" } },
3326
+ (0, import_react.createElement)(ToggleRow, { title: "Block publish commands", description: "Prevent publish-related commands when enabled.", checked: guard.publishBlocked === true, onChange: (v) => saveGuard({ publishBlocked: v }) }),
3327
+ (0, import_react.createElement)(ToggleRow, { title: "Protect git branches", description: "Block direct pushes to protected branches.", checked: guard.gitProtection?.enabled === true, onChange: (v) => saveGuard({ gitProtection: { enabled: v, branches: guard.gitProtection?.branches ?? ["master", "main"] } }) }),
3328
+ (0, import_react.createElement)(SettingRow, { title: "Protected branches", description: "Comma-separated list, e.g. master, main.", control: (0, import_react.createElement)(FieldInput, { value: (guard.gitProtection?.branches ?? ["master", "main"]).join(", "), placeholder: "master, main", onChange: (e) => saveGuard({ gitProtection: { enabled: guard.gitProtection?.enabled ?? true, branches: e.target.value.split(",").map((s) => s.trim()).filter(Boolean) } }), style: { width: 260 } }) }),
3329
+ (0, import_react.createElement)(ToggleRow, { title: "Contain working directory", description: "Restrict file operations to the session working directory.", checked: guard.cwdContainment === true, onChange: (v) => saveGuard({ cwdContainment: v }) }),
3330
+ (0, import_react.createElement)(SettingRow, { title: "Credential file paths", description: "Comma-separated paths to credential files.", control: (0, import_react.createElement)(FieldInput, { value: (guard.credentialPaths ?? []).join(", "), placeholder: "~/.config/credentials.yaml", onChange: (e) => saveGuard({ credentialPaths: e.target.value.split(",").map((s) => s.trim()).filter(Boolean) }), style: { width: 260 } }) })
2938
3331
  ),
2939
- (0, import_react.createElement)(
3332
+ blacklist: (0, import_react.createElement)(
2940
3333
  "div",
2941
- { style: sectionStyle },
2942
- (0, import_react.createElement)("h4", { style: headingStyle }, "GitLab"),
2943
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "GitLab base URL"),
2944
- (0, import_react.createElement)("input", { placeholder: "https://gitlab.example.com", style: inputStyle, value: config.gitlabBaseUrl ?? "", onChange: (e) => saveField("gitlabBaseUrl", e.target.value) }),
2945
- (0, import_react.createElement)(SecretInput, {
2946
- label: "GitLab token",
2947
- placeholder: "GitLab token",
2948
- hasSaved: config.hasGitlabToken,
2949
- onSave: (value) => saveField("gitlabToken", value)
2950
- }),
2951
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Bot username"),
2952
- (0, import_react.createElement)("input", { placeholder: "maestro-bot", style: inputStyle, value: config.botUsername ?? "", onChange: (e) => saveField("botUsername", e.target.value) }),
2953
- (0, import_react.createElement)(SecretInput, {
2954
- label: "Webhook secret",
2955
- placeholder: "Webhook secret",
2956
- hasSaved: config.hasWebhookSecret,
2957
- onSave: (value) => saveField("webhookSecret", value)
2958
- }),
2959
- (0, import_react.createElement)("button", { type: "button", style: { ...secondaryButtonStyle, marginTop: 10 }, onClick: () => saveField("webhookSecret", generateWebhookSecret()) }, "Generate new secret"),
2960
- (0, import_react.createElement)("p", { style: captionStyle }, "In GitLab: Settings \u2192 Webhooks, set Secret token to this value and enable Merge request events."),
2961
- (0, import_react.createElement)("p", { style: captionStyle }, "Webhook URL:"),
2962
- (0, import_react.createElement)("code", { style: codeStyle }, gitlabWebhookUrl(config.tunnelHostname)),
2963
- (0, import_react.createElement)("p", { style: captionStyle }, "Overrides MAESTRO_GITLAB_WEBHOOK_SECRET immediately, no restart needed.")
3334
+ { style: { display: "flex", flexDirection: "column" } },
3335
+ (0, import_react.createElement)(
3336
+ "div",
3337
+ { style: { ...rowStyle, flexDirection: "column", alignItems: "stretch", gap: 8 } },
3338
+ (0, import_react.createElement)("div", { style: rowTitleStyle }, "Blacklist patterns"),
3339
+ (0, import_react.createElement)("div", { style: rowDescStyle }, "One pattern per line. Matching files are blocked from commit."),
3340
+ (0, import_react.createElement)(TextareaField, { value: patternsText, placeholder: "example-project\nacme-shop", onChange: (e) => setPatternsText(e.target.value), onBlur: (e) => commitBlacklistPatterns(e.target.value) })
3341
+ ),
3342
+ (0, import_react.createElement)(
3343
+ "div",
3344
+ { style: { ...rowStyle, flexDirection: "column", alignItems: "stretch", gap: 8, borderBottom: "none" } },
3345
+ (0, import_react.createElement)("div", { style: rowTitleStyle }, "Placeholder mappings"),
3346
+ (0, import_react.createElement)("div", { style: rowDescStyle }, "JSON object mapping blocked patterns to placeholder suggestions."),
3347
+ (0, import_react.createElement)(TextareaField, { value: placeholdersText, placeholder: '{"example-project":"my-project"}', onChange: (e) => setPlaceholdersText(e.target.value), onBlur: () => commitPlaceholders(), style: { minHeight: 80 } }),
3348
+ (0, import_react.createElement)("div", { style: { marginTop: 8 } }, (0, import_react.createElement)(Button, { variant: "outline", size: "sm", onClick: () => {
3349
+ commitBlacklistPatterns(patternsText);
3350
+ commitPlaceholders();
3351
+ } }, "Save Blacklist"))
3352
+ )
2964
3353
  ),
2965
- (0, import_react.createElement)(
3354
+ supervisor: (0, import_react.createElement)(
2966
3355
  "div",
2967
- { style: sectionStyle },
2968
- (0, import_react.createElement)("h4", { style: headingStyle }, "Review automation"),
2969
- (0, import_react.createElement)(ToggleField, {
2970
- label: "Re-review when new commits are pushed",
2971
- caption: "After a completed review, further pushes to the same MR trigger an automatic quick re-review.",
2972
- checked: config.autoRereviewOnPush,
2973
- onChange: (checked) => saveField("autoRereviewOnPush", checked)
2974
- }),
2975
- (0, import_react.createElement)(ReviewHistoryPanel, { rpcCall })
3356
+ { style: { display: "flex", flexDirection: "column" } },
3357
+ (0, import_react.createElement)(SettingRow, { title: "Check interval", description: "Milliseconds between supervisor checks. Default 5000.", control: (0, import_react.createElement)(FieldInput, { type: "number", value: supervisorCfg.intervalMs ?? "", placeholder: "5000", onChange: (e) => {
3358
+ const v = e.target.value === "" ? void 0 : Number(e.target.value);
3359
+ saveSupervisorCfg({ intervalMs: v });
3360
+ }, style: { width: 160 } }) }),
3361
+ (0, import_react.createElement)(SettingRow, { title: "Down threshold", description: "Consecutive failures before marking a session as down.", control: (0, import_react.createElement)(FieldInput, { type: "number", value: supervisorCfg.downThreshold ?? "", placeholder: "3", onChange: (e) => {
3362
+ const v = e.target.value === "" ? void 0 : Number(e.target.value);
3363
+ saveSupervisorCfg({ downThreshold: v });
3364
+ }, style: { width: 160 } }) }),
3365
+ (0, import_react.createElement)(ToggleRow, { title: "Auto-resume sessions", description: "Automatically resume sessions marked as down.", checked: supervisorCfg.autoResumeEnabled === true, onChange: (v) => saveSupervisorCfg({ autoResumeEnabled: v }) })
2976
3366
  ),
2977
- (0, import_react.createElement)(
3367
+ notifier: (0, import_react.createElement)(
2978
3368
  "div",
2979
- { style: sectionStyle },
2980
- (0, import_react.createElement)("h4", { style: headingStyle }, "Review model"),
2981
- (0, import_react.createElement)("p", { style: captionStyle }, "Model used for automated GitLab reviews (reviewer & auditor). Empty = DSH default. Per-project empty = inherits Global, or DSH default when Global is empty."),
2982
- (0, import_react.createElement)(ReviewModelSelector, {
2983
- value: config.reviewModel ?? null,
2984
- catalog,
2985
- fallbackValue: catalog?.current ?? null,
2986
- fallbackLabel: "Use DSH default",
2987
- onChange: (v) => saveField("reviewModel", v),
2988
- label: "Global review model"
2989
- })
2990
- ),
3369
+ { style: { display: "flex", flexDirection: "column" } },
3370
+ (0, import_react.createElement)(SettingRow, { title: "Bot token", description: "Telegram bot token from @BotFather.", control: (0, import_react.createElement)(FieldInput, { type: "password", autoComplete: "off", value: notifierCfg.telegram?.botToken ?? "", placeholder: "123456:ABC-DEF...", onChange: (e) => saveNotifierCfg({ telegram: { botToken: e.target.value } }), style: { width: 260 } }) }),
3371
+ (0, import_react.createElement)(SettingRow, { title: "Chat ID", description: "Target chat, e.g. -1001234567890.", control: (0, import_react.createElement)(FieldInput, { value: notifierCfg.telegram?.chatId ?? "", placeholder: "-1001234567890", onChange: (e) => saveNotifierCfg({ telegram: { chatId: e.target.value } }), style: { width: 260 } }) }),
3372
+ (0, import_react.createElement)(ToggleRow, { title: "Review notifications", description: "Also notify about finished reviews.", checked: notifierCfg.telegram?.reviewNotifications === true || notifierCfg.policy?.reviewNotifications === true, onChange: (v) => saveNotifierCfg({ telegram: { reviewNotifications: v } }) })
3373
+ )
3374
+ };
3375
+ return (0, import_react.createElement)(
3376
+ "div",
3377
+ { "data-maestro-settings-card": "", style: { display: "flex", flexDirection: "column", gap: 8, width: "100%", maxWidth: 640, minWidth: 0, boxSizing: "border-box" } },
2991
3378
  (0, import_react.createElement)(
2992
3379
  "div",
2993
- { style: sectionStyle },
2994
- (0, import_react.createElement)("h4", { style: headingStyle }, "Supervisor LLM"),
2995
- (0, import_react.createElement)("p", { style: captionStyle }, "Model used by the supervisor debug-agent to auto-fix DSH Web crashes. Empty = DSH default (or Review model if set). Uses the same provider catalog as Review."),
2996
- (0, import_react.createElement)(ReviewModelSelector, {
2997
- value: config.supervisorModel ?? null,
2998
- catalog,
2999
- fallbackValue: catalog?.current ?? null,
3000
- fallbackLabel: "Use DSH default",
3001
- onChange: (v) => saveField("supervisorModel", v),
3002
- label: "Supervisor model"
3003
- })
3380
+ { style: { padding: "2px 2px 8px" } },
3381
+ (0, import_react.createElement)("div", { style: { fontSize: 15, fontWeight: 600, color: t.labelPrimary, lineHeight: "22px" } }, "Maestro"),
3382
+ (0, import_react.createElement)("div", { style: { fontSize: 12, color: t.labelSecondary, lineHeight: "16px", marginTop: 2 } }, "Tunnel, access, review & guard \u2014 all via the shared Maestro store. Uses the same tokens and primitives as DSH settings.")
3004
3383
  ),
3005
3384
  (0, import_react.createElement)(
3006
3385
  "div",
3007
- { style: sectionStyle },
3008
- (0, import_react.createElement)("h4", { style: headingStyle }, "Projects"),
3009
- (0, import_react.createElement)(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: (mappings) => saveField("projectMappings", mappings), catalog, globalReviewModel: config.reviewModel ?? null })
3386
+ { "data-maestro-tabs": "", role: "tablist", "aria-label": "Maestro settings sections" },
3387
+ ...TABS.map((tab) => (0, import_react.createElement)("button", { key: tab.id, "data-maestro-tab": "", "data-active": String(activeTab === tab.id), role: "tab", "aria-selected": activeTab === tab.id, onClick: () => setActiveTab(tab.id) }, tab.label))
3010
3388
  ),
3011
- error && (0, import_react.createElement)("p", { style: errorStyle }, error)
3389
+ (0, import_react.createElement)("div", { "data-maestro-panel": activeTab, style: { display: "flex", flexDirection: "column", gap: 10, minWidth: 0 } }, tabContents[activeTab]),
3390
+ error ? (0, import_react.createElement)("p", { style: { color: t.stateError, fontSize: 12, margin: "8px 0 0", padding: "8px 10px", borderRadius: 8, background: "color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent)", border: `1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent)` } }, error) : null
3012
3391
  );
3013
3392
  }
3014
3393
 
@@ -3049,9 +3428,9 @@ function registerSettingsNavIcon(label, root) {
3049
3428
  }
3050
3429
 
3051
3430
  // src/client/index.tsx
3052
- var SETTINGS_NAV_CSS2 = `
3431
+ var SETTINGS_NAV_CSS = `
3053
3432
 
3054
- /* maestro: replace the settings-nav fallback gear with the maestro glyph */
3433
+ /* maestro: replace the settings-nav fallback gear with the Maestro M-logo glyph */
3055
3434
  [${SETTINGS_NAV_MARKER}] > svg:first-child {
3056
3435
  display: none;
3057
3436
  }
@@ -3062,15 +3441,16 @@ var SETTINGS_NAV_CSS2 = `
3062
3441
  width: 16px;
3063
3442
  height: 16px;
3064
3443
  background: currentColor;
3065
- -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 10v3'/%3E%3Cpath d='M6 4v16'/%3E%3Cpath d='M10 8v8'/%3E%3Cpath d='M14 4v16'/%3E%3Cpath d='M18 6v12'/%3E%3Cpath d='M22 10v3'/%3E%3C/svg%3E") center / contain no-repeat;
3066
- mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 10v3'/%3E%3Cpath d='M6 4v16'/%3E%3Cpath d='M10 8v8'/%3E%3Cpath d='M14 4v16'/%3E%3Cpath d='M18 6v12'/%3E%3Cpath d='M22 10v3'/%3E%3C/svg%3E") center / contain no-repeat;
3444
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 11 L5 4 L8 9 L11 4 L14 11'/%3E%3C/svg%3E") center / contain no-repeat;
3445
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 11 L5 4 L8 9 L11 4 L14 11'/%3E%3C/svg%3E") center / contain no-repeat;
3067
3446
  }
3068
3447
  `;
3448
+ var inject = ["slots", "connection"];
3069
3449
  function installNavIconStyle() {
3070
3450
  const tag = document.createElement("style");
3071
3451
  tag.dataset.plugin = "@ddtcorex/dsh-maestro-config";
3072
3452
  tag.dataset.pluginCss = "maestro/settings-nav.css";
3073
- tag.textContent = SETTINGS_NAV_CSS2;
3453
+ tag.textContent = SETTINGS_NAV_CSS;
3074
3454
  document.head.appendChild(tag);
3075
3455
  return () => {
3076
3456
  document.querySelector('style[data-plugin-css="maestro/settings-nav.css"]')?.remove();