@ddtcorex/dsh-maestro-config 0.1.2 → 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;
@@ -2122,7 +2122,7 @@ __export(index_exports, {
2122
2122
  });
2123
2123
  module.exports = __toCommonJS(index_exports);
2124
2124
 
2125
- // src/client/maestro-card.jsx
2125
+ // src/client/MaestroSettings.tsx
2126
2126
  var import_react = require("react");
2127
2127
  var import_qrcode = __toESM(require_browser(), 1);
2128
2128
 
@@ -2156,95 +2156,252 @@ function gitlabWebhookUrl(hostname) {
2156
2156
  return `https://${authority}/hooks/gitlab-mr`;
2157
2157
  }
2158
2158
 
2159
- // src/client/maestro-card.jsx
2160
- var inputStyle = {
2161
- height: 34,
2162
- padding: "0 12px",
2163
- border: "1px solid var(--dsw-alias-border-l2)",
2164
- borderRadius: 8,
2165
- background: "var(--dsw-alias-bg-layer-3)",
2166
- font: "inherit",
2167
- fontSize: 13,
2168
- color: "var(--dsw-alias-label-primary)",
2169
- width: "100%",
2170
- 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)"
2171
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
+ }
2172
2320
  var fieldLabelStyle = {
2173
- display: "block",
2174
- fontSize: 12,
2175
- lineHeight: 1.5,
2176
- color: "var(--dsw-alias-label-secondary)",
2177
- margin: "10px 0 4px"
2178
- };
2179
- var secondaryButtonStyle = {
2180
- height: 32,
2181
- padding: "0 14px",
2182
- borderRadius: 16,
2183
- border: "1px solid var(--dsw-alias-border-l2)",
2184
- background: "transparent",
2185
- color: "var(--dsw-alias-label-primary)",
2186
- font: "inherit",
2187
- fontSize: 13,
2188
- cursor: "pointer",
2189
- flex: "none"
2190
- };
2191
- var primaryButtonStyle = {
2192
- ...secondaryButtonStyle,
2193
- border: "none",
2194
- background: "var(--dsw-alias-button-primary-fill)",
2195
- color: "var(--dsw-alias-label-primary-foreground)"
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"
2196
2329
  };
2197
2330
  var captionStyle = {
2198
- fontSize: 12,
2199
- lineHeight: 1.5,
2200
- margin: "4px 0",
2201
- color: "var(--dsw-alias-label-secondary)"
2202
- };
2203
- var sectionStyle = {
2204
- marginTop: 20,
2205
- paddingTop: 16,
2206
- borderTop: "1px solid var(--dsw-alias-border-l2)"
2331
+ fontSize: "12px",
2332
+ lineHeight: "16px",
2333
+ color: t.labelSecondary,
2334
+ margin: "4px 0"
2207
2335
  };
2208
- var headingStyle = {
2209
- margin: "0 0 6px",
2210
- fontSize: 13,
2211
- fontWeight: 600,
2212
- color: "var(--dsw-alias-label-primary)"
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
2213
2344
  };
2214
- var errorStyle = {
2215
- color: "var(--dsw-alias-state-error-primary)",
2216
- fontSize: 12,
2217
- margin: "8px 0 0"
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"
2218
2352
  };
2219
- var codeStyle = {
2220
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2221
- fontSize: 12,
2222
- color: "var(--dsw-alias-label-primary)",
2223
- wordBreak: "break-all"
2353
+ var rowTextStyle = {
2354
+ flex: "1",
2355
+ minWidth: "0",
2356
+ display: "flex",
2357
+ flexDirection: "column",
2358
+ gap: "4px",
2359
+ paddingRight: "48px"
2224
2360
  };
2225
- var textareaStyle = {
2226
- ...inputStyle,
2227
- height: 120,
2228
- padding: "8px 12px",
2229
- resize: "vertical"
2361
+ var rowTitleStyle = {
2362
+ fontSize: "14px",
2363
+ fontWeight: "400",
2364
+ lineHeight: "22px",
2365
+ color: t.labelPrimary
2230
2366
  };
2231
- var tabBarStyle = {
2232
- display: "flex",
2233
- gap: 8,
2234
- borderBottom: "1px solid var(--dsw-alias-border-l2)",
2235
- marginBottom: 16
2367
+ var rowDescStyle = {
2368
+ fontSize: "12px",
2369
+ fontWeight: "400",
2370
+ lineHeight: "18px",
2371
+ color: t.labelTertiary
2236
2372
  };
2237
- var tabButtonStyle = (active) => ({
2238
- padding: "8px 14px",
2373
+ var pillSelectorStyle = {
2374
+ display: "inline-flex",
2375
+ alignItems: "center",
2376
+ gap: "12px",
2377
+ height: "36px",
2378
+ padding: "0 14px",
2239
2379
  border: "none",
2240
- borderBottom: active ? "2px solid var(--dsw-alias-button-primary-fill)" : "2px solid transparent",
2241
- background: "transparent",
2242
- color: active ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-secondary)",
2380
+ borderRadius: "18px",
2381
+ background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
2243
2382
  font: "inherit",
2244
- fontSize: 13,
2245
- fontWeight: active ? 600 : 400,
2246
- cursor: "pointer"
2247
- });
2383
+ fontSize: "14px",
2384
+ lineHeight: "22px",
2385
+ color: t.labelPrimary,
2386
+ cursor: "pointer",
2387
+ whiteSpace: "nowrap"
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
+ }
2248
2405
  function QrImage({ url, size = 104 }) {
2249
2406
  const [dataUrl, setDataUrl] = (0, import_react.useState)(null);
2250
2407
  (0, import_react.useEffect)(() => {
@@ -2270,34 +2427,24 @@ function QrImage({ url, size = 104 }) {
2270
2427
  alignItems: "center",
2271
2428
  justifyContent: "center",
2272
2429
  lineHeight: 0,
2273
- flex: "none",
2274
- alignSelf: "flex-start"
2430
+ flex: "none"
2275
2431
  }
2276
2432
  },
2277
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" } })
2278
2434
  );
2279
2435
  }
2280
- function NamedTunnelSetupNote() {
2281
- return (0, import_react.createElement)(
2282
- "div",
2283
- { style: captionStyle },
2284
- (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):"),
2285
- (0, import_react.createElement)(
2286
- "ol",
2287
- { style: { margin: "4px 0", paddingLeft: 20 } },
2288
- (0, import_react.createElement)("li", null, "cloudflared tunnel login"),
2289
- (0, import_react.createElement)("li", null, "cloudflared tunnel create dsh-maestro-webhook"),
2290
- (0, import_react.createElement)("li", null, "cloudflared tunnel route dns dsh-maestro-webhook <your-hostname>"),
2291
- (0, import_react.createElement)("li", null, "Paste the printed Tunnel ID, the credentials file path (~/.cloudflared/<id>.json), and the hostname below.")
2292
- )
2293
- );
2294
- }
2295
- function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onChange, label }) {
2436
+ function ReviewModelSelector({
2437
+ value,
2438
+ catalog,
2439
+ fallbackValue,
2440
+ fallbackLabel,
2441
+ onChange,
2442
+ label
2443
+ }) {
2296
2444
  const groups = catalog?.groups ?? [];
2297
2445
  const providers = groups.map((g) => g.provider);
2298
2446
  const selectedProvider = value?.provider ?? "";
2299
2447
  const providerGroup = groups.find((g) => g.provider === selectedProvider);
2300
- const models = providerGroup?.models ?? [];
2301
2448
  const selectedEffort = value?.reasoningEffort ?? "";
2302
2449
  const [open, setOpen] = (0, import_react.useState)(false);
2303
2450
  const [pane, setPane] = (0, import_react.useState)("root");
@@ -2345,7 +2492,7 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2345
2492
  if (filtered.length > 0) return filtered;
2346
2493
  return ["low", "medium", "high"];
2347
2494
  })();
2348
- 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;
2349
2496
  const update = (field, newVal) => {
2350
2497
  if (newVal === "" && field === "provider") {
2351
2498
  onChange(null);
@@ -2359,51 +2506,30 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2359
2506
  const first = g?.models[0];
2360
2507
  next.provider = newVal;
2361
2508
  next.model = first !== void 0 ? getModelId(first) : "";
2362
- } else if (field === "model") {
2363
- next.model = newVal;
2364
- } else if (field === "reasoningEffort") {
2509
+ } else if (field === "model") next.model = newVal;
2510
+ else if (field === "reasoningEffort") {
2365
2511
  if (newVal === "") delete next.reasoningEffort;
2366
2512
  else next.reasoningEffort = newVal;
2367
2513
  }
2368
- if (!next.provider || !next.model) {
2369
- onChange(null);
2370
- } else {
2371
- onChange(next);
2372
- }
2514
+ if (!next.provider || !next.model) onChange(null);
2515
+ else onChange(next);
2373
2516
  };
2374
2517
  const effectiveFallback = fallbackValue !== void 0 ? fallbackValue : catalog?.current ?? null;
2375
- const effectiveFallbackLabel = fallbackLabel ?? "Use DSH default";
2376
- 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;
2377
- const triggerStyle = {
2378
- height: 32,
2379
- padding: "0 12px 0 14px",
2380
- borderRadius: 20,
2381
- border: "1px solid var(--dsw-alias-border-l2)",
2382
- background: "var(--dsw-alias-bg-layer-2)",
2383
- color: "var(--dsw-alias-label-primary)",
2384
- font: "inherit",
2385
- fontSize: 13,
2386
- display: "inline-flex",
2387
- alignItems: "center",
2388
- gap: 8,
2389
- cursor: "pointer",
2390
- maxWidth: 320,
2391
- whiteSpace: "nowrap"
2392
- };
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;
2393
2519
  const menuStyle = {
2394
2520
  position: "absolute",
2395
2521
  top: "calc(100% + 8px)",
2396
- left: 0,
2397
- minWidth: 300,
2398
- maxWidth: 360,
2399
- background: "var(--dsw-alias-bg-layer-1)",
2400
- border: "1px solid var(--dsw-alias-border-l2)",
2401
- borderRadius: 12,
2402
- boxShadow: "0 8 24px rgba(0,0,0,.12)",
2403
- zIndex: 20,
2404
- 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"
2405
2531
  };
2406
- const rowStyle = {
2532
+ const rowStyle2 = {
2407
2533
  width: "100%",
2408
2534
  display: "flex",
2409
2535
  alignItems: "center",
@@ -2413,66 +2539,87 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2413
2539
  borderRadius: 8,
2414
2540
  border: "none",
2415
2541
  background: "transparent",
2416
- color: "var(--dsw-alias-label-primary)",
2417
- font: "inherit",
2542
+ color: t.labelPrimary,
2543
+ fontFamily: "inherit",
2418
2544
  fontSize: 13,
2419
2545
  cursor: "pointer",
2420
2546
  textAlign: "left"
2421
2547
  };
2422
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" } });
2423
- 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" }));
2424
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" }));
2425
2550
  const effortLabel = selectedEffort === "" ? "Default effort" : selectedEffort;
2426
2551
  const modelLabel = selectedProvider === "" ? "Select model" : value?.model ?? "Select model";
2427
2552
  return (0, import_react.createElement)(
2428
2553
  "div",
2429
- { ref: rootRef, style: { position: "relative", display: "inline-block", maxWidth: "100%" } },
2430
- 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,
2431
2556
  (0, import_react.createElement)(
2432
2557
  "button",
2433
- { type: "button", style: triggerStyle, onClick: () => {
2434
- setOpen((v) => !v);
2435
- setPane("root");
2436
- }, "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
+ },
2437
2584
  (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, triggerLabel),
2438
- 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" }))
2439
2586
  ),
2440
- open && (0, import_react.createElement)(
2587
+ open ? (0, import_react.createElement)(
2441
2588
  "div",
2442
- { style: menuStyle, role: "menu" },
2443
- pane === "root" && (0, import_react.createElement)(
2589
+ { "data-maestro-menu": "", style: menuStyle, role: "menu" },
2590
+ pane === "root" ? (0, import_react.createElement)(
2444
2591
  "div",
2445
2592
  null,
2446
2593
  (0, import_react.createElement)(
2447
2594
  "button",
2448
- { 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: () => {
2449
2596
  onChange(null);
2450
2597
  setOpen(false);
2451
2598
  } },
2452
- (0, import_react.createElement)("span", null, effectiveFallbackLabel),
2599
+ (0, import_react.createElement)("span", null, fallbackLabel),
2453
2600
  check(!value)
2454
2601
  ),
2455
- (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" } }),
2456
2603
  (0, import_react.createElement)(
2457
2604
  "button",
2458
- { type: "button", style: rowStyle, onClick: () => setPane("model") },
2605
+ { type: "button", style: rowStyle2, onClick: () => setPane("model") },
2459
2606
  (0, import_react.createElement)("span", null, "Model"),
2460
- (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)
2461
2608
  ),
2462
2609
  (0, import_react.createElement)(
2463
2610
  "button",
2464
- { type: "button", style: rowStyle, onClick: () => setPane("effort") },
2611
+ { type: "button", style: rowStyle2, onClick: () => setPane("effort") },
2465
2612
  (0, import_react.createElement)("span", null, "Effort"),
2466
- (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)
2467
2614
  ),
2468
- value && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, `Selected: ${value.provider} / ${value.model}${value.reasoningEffort ? ` (${value.reasoningEffort})` : ""}`),
2469
- !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})` : ""}`),
2470
- warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "4px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
2471
- ),
2472
- 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)(
2473
2620
  "div",
2474
2621
  null,
2475
- (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")),
2476
2623
  (0, import_react.createElement)(
2477
2624
  "div",
2478
2625
  { style: { maxHeight: 260, overflowY: "auto", marginTop: 4 } },
@@ -2482,33 +2629,43 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2482
2629
  return (0, import_react.createElement)(
2483
2630
  "div",
2484
2631
  { key: p, style: { marginBottom: 8 } },
2485
- (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),
2486
2633
  ms.length === 0 ? (0, import_react.createElement)("p", { style: { ...captionStyle, padding: "2px 10px 2px 28px" } }, "No models") : (0, import_react.createElement)(
2487
2634
  "div",
2488
- { 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 } },
2489
2636
  ms.map((m) => {
2490
2637
  const mid = getModelId(m);
2491
2638
  const mname = getModelName(m);
2492
2639
  const active = value?.provider === p && value?.model === mid;
2493
- return (0, import_react.createElement)("button", { key: mid, type: "button", style: { ...rowStyle, paddingLeft: 10, background: active ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2494
- update("model", mid);
2495
- if (value?.provider !== p) update("provider", p);
2496
- else {
2497
- const next = { provider: p, model: mid, ...selectedEffort ? { reasoningEffort: selectedEffort } : {} };
2498
- onChange(next);
2499
- setPane("root");
2500
- }
2501
- } }, (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
+ );
2502
2659
  })
2503
2660
  )
2504
2661
  );
2505
2662
  })
2506
2663
  )
2507
- ),
2508
- pane === "effort" && (0, import_react.createElement)(
2664
+ ) : null,
2665
+ pane === "effort" ? (0, import_react.createElement)(
2509
2666
  "div",
2510
2667
  null,
2511
- (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")),
2512
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)(
2513
2670
  "div",
2514
2671
  null,
@@ -2516,107 +2673,227 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2516
2673
  (0, import_react.createElement)(
2517
2674
  "div",
2518
2675
  { style: { marginTop: 4 } },
2519
- [{ 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: () => {
2520
- update("reasoningEffort", e.id);
2521
- setPane("root");
2522
- } }, (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
+ )
2523
2687
  ),
2524
- 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
2525
2689
  ) : (0, import_react.createElement)(
2526
2690
  "div",
2527
2691
  null,
2528
2692
  (0, import_react.createElement)(
2529
2693
  "div",
2530
2694
  { style: { marginTop: 4 } },
2531
- [{ 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: () => {
2532
- update("reasoningEffort", e.id);
2533
- setPane("root");
2534
- } }, (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
+ )
2535
2706
  ),
2536
- 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
2537
2708
  )
2538
- )
2539
- )
2709
+ ) : null
2710
+ ) : null
2540
2711
  );
2541
2712
  }
2542
2713
  function ProjectMappingsEditor({ mappings, onChange, catalog, globalReviewModel }) {
2543
- const rows = mappings.length > 0 ? mappings : [{ projectPath: "", localRepoPath: "", reviewProfile: "magento2" }];
2714
+ const rows = mappings;
2544
2715
  const updateRow = (index, field, value) => {
2545
2716
  const next = rows.map((row, i) => i === index ? { ...row, [field]: value } : row);
2546
- onChange(next.filter((r) => r.projectPath !== "" || r.localRepoPath !== ""));
2717
+ onChange(next.filter((r) => (r.projectPath ?? "") !== "" || (r.localRepoPath ?? "") !== ""));
2547
2718
  };
2548
2719
  const removeRow = (index) => onChange(rows.filter((_, i) => i !== index));
2549
2720
  const addRow = () => onChange([...rows, { projectPath: "", localRepoPath: "", reviewProfile: "magento2" }]);
2550
2721
  return (0, import_react.createElement)(
2551
2722
  "div",
2552
- null,
2553
- (0, import_react.createElement)("span", { style: fieldLabelStyle }, "Tracked projects (GitLab path \u2192 local repo checkout \u2192 review profile \u2192 review model override)"),
2554
- 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)(
2555
2726
  "div",
2556
- { key: i, "data-maestro-mapping-row": "", style: { display: "flex", gap: 8, marginBottom: 8, alignItems: "center", flexWrap: "wrap" } },
2557
- (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) }),
2558
- (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) }),
2559
- (0, import_react.createElement)(
2560
- "select",
2561
- { style: { ...inputStyle, flex: "0 0 130px" }, value: row.reviewProfile ?? "magento2", onChange: (e) => updateRow(i, "reviewProfile", e.target.value) },
2562
- (0, import_react.createElement)("option", { value: "magento2" }, "Magento 2"),
2563
- (0, import_react.createElement)("option", { value: "generic" }, "Generic")
2564
- ),
2727
+ { style: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 12, padding: "4px 0 4px" } },
2565
2728
  (0, import_react.createElement)(
2566
2729
  "div",
2567
- { style: { flex: "0 0 auto" } },
2568
- (0, import_react.createElement)(ReviewModelSelector, {
2569
- value: row.reviewModel ?? null,
2570
- catalog,
2571
- fallbackValue: globalReviewModel ?? catalog?.current ?? null,
2572
- fallbackLabel: globalReviewModel ? "Use Global" : "Use DSH default",
2573
- onChange: (v) => updateRow(i, "reviewModel", v),
2574
- label: null
2575
- })
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")
2576
2733
  ),
2577
- (0, import_react.createElement)("button", { onClick: () => removeRow(i), style: secondaryButtonStyle, title: "Remove mapping" }, "\u2715")
2578
- )),
2579
- (0, import_react.createElement)("button", { onClick: addRow, style: secondaryButtonStyle }, "+ Add mapping")
2580
- );
2581
- }
2582
- function SecretInput({ label, placeholder, hasSaved, onSave }) {
2583
- const [draft, setDraft] = (0, import_react.useState)("");
2584
- const clear = () => {
2585
- setDraft("");
2586
- onSave("");
2587
- };
2588
- return (0, import_react.createElement)(
2589
- "div",
2590
- null,
2591
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, label),
2592
- (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)(
2593
2737
  "div",
2594
- { style: { display: "flex", gap: 8 } },
2595
- (0, import_react.createElement)("input", {
2596
- placeholder: hasSaved === true ? "saved \u2014 leave blank to keep" : placeholder,
2597
- type: "password",
2598
- autoComplete: "off",
2599
- style: inputStyle,
2600
- value: draft,
2601
- onChange: (e) => setDraft(e.target.value),
2602
- onBlur: () => {
2603
- 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"
2604
2750
  }
2605
- }),
2606
- hasSaved === true && (0, import_react.createElement)("button", { type: "button", style: secondaryButtonStyle, onClick: clear }, "Clear")
2607
- )
2608
- );
2609
- }
2610
- function ToggleField({ label, caption, checked, onChange }) {
2611
- return (0, import_react.createElement)(
2612
- "label",
2613
- { style: { display: "flex", alignItems: "flex-start", gap: 8, margin: "8px 0", cursor: "pointer" } },
2614
- (0, import_react.createElement)("input", { type: "checkbox", checked: checked === true, onChange: (e) => onChange(e.target.checked), style: { marginTop: 3 } }),
2615
- (0, import_react.createElement)(
2616
- "span",
2617
- null,
2618
- (0, import_react.createElement)("div", { style: { fontSize: 13 } }, label),
2619
- caption != null && (0, import_react.createElement)("div", { style: captionStyle }, caption)
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
+ )
2896
+ )
2620
2897
  )
2621
2898
  );
2622
2899
  }
@@ -2625,40 +2902,26 @@ function LanAccess({ proxyStatus, lanPin }) {
2625
2902
  const [selected, setSelected] = (0, import_react.useState)(0);
2626
2903
  const index = Math.min(selected, Math.max(urls.length - 1, 0));
2627
2904
  if (!proxyStatus?.running) {
2628
- 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");
2629
2906
  }
2630
2907
  return (0, import_react.createElement)(
2631
2908
  "div",
2632
2909
  null,
2633
- (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."),
2634
- 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)(
2635
2912
  "div",
2636
2913
  { style: { display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 12 } },
2637
- urls.map((url, i) => (0, import_react.createElement)("button", {
2638
- key: url,
2639
- onClick: () => setSelected(i),
2640
- style: {
2641
- ...secondaryButtonStyle,
2642
- height: 26,
2643
- padding: "0 10px",
2644
- fontSize: 12,
2645
- borderRadius: 13,
2646
- ...i === index ? { background: "var(--dsw-alias-button-primary-fill)", borderColor: "transparent", color: "var(--dsw-alias-label-primary-foreground)" } : {}
2647
- }
2648
- }, url.replace(/^http:\/\//, "")))
2649
- ),
2650
- 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)(
2651
2919
  "div",
2652
- { style: { display: "flex", gap: 14, alignItems: "center" } },
2920
+ { "data-maestro-qr-row": "", style: { display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" } },
2653
2921
  (0, import_react.createElement)(QrImage, { url: urls[index], size: 116 }),
2654
- (0, import_react.createElement)(
2655
- "div",
2656
- null,
2657
- (0, import_react.createElement)("div", { style: codeStyle }, urls[index]),
2658
- (0, import_react.createElement)("p", { style: captionStyle }, "Scan with a phone connected to the same network.")
2659
- )
2660
- ),
2661
- 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
2662
2925
  );
2663
2926
  }
2664
2927
  function LanPinRow({ lanPin }) {
@@ -2668,22 +2931,17 @@ function LanPinRow({ lanPin }) {
2668
2931
  (0, import_react.createElement)(
2669
2932
  "label",
2670
2933
  { style: { display: "flex", gap: 8, alignItems: "center", cursor: "pointer" } },
2671
- (0, import_react.createElement)("input", {
2672
- type: "checkbox",
2673
- checked: lanPin.enabled,
2674
- onChange: (e) => lanPin.onToggle(e.target.checked),
2675
- style: { width: 15, height: 15, accentColor: "var(--dsw-alias-button-primary-fill)" }
2676
- }),
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 } }),
2677
2935
  (0, import_react.createElement)("span", { style: { ...fieldLabelStyle, margin: 0 } }, "Require a PIN on the LAN")
2678
2936
  ),
2679
- lanPin.enabled && (0, import_react.createElement)(
2937
+ lanPin.enabled ? (0, import_react.createElement)(
2680
2938
  "div",
2681
- { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 8 } },
2939
+ { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 8, flexWrap: "wrap" } },
2682
2940
  (0, import_react.createElement)("span", { style: { ...fieldLabelStyle, margin: 0 } }, "LAN PIN"),
2683
- (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"),
2684
- lanPin.show ? (0, import_react.createElement)("button", { onClick: lanPin.onHide, style: secondaryButtonStyle }, "Hide") : (0, import_react.createElement)("button", { onClick: lanPin.onShow, style: secondaryButtonStyle }, "Show"),
2685
- (0, import_react.createElement)("button", { onClick: lanPin.onRotate, style: secondaryButtonStyle }, "Rotate")
2686
- )
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
2687
2945
  );
2688
2946
  }
2689
2947
  function PublicAccess({ status, pin, showPin, onRevealPin, onHidePin, onRotatePin }) {
@@ -2695,27 +2953,30 @@ function PublicAccess({ status, pin, showPin, onRevealPin, onHidePin, onRotatePi
2695
2953
  null,
2696
2954
  (0, import_react.createElement)(
2697
2955
  "div",
2698
- { 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" } },
2699
2957
  (0, import_react.createElement)(QrImage, { url: status.publicUrl, size: 116 }),
2700
- (0, import_react.createElement)(
2701
- "div",
2702
- null,
2703
- (0, import_react.createElement)("div", { style: codeStyle }, status.publicUrl),
2704
- (0, import_react.createElement)("p", { style: captionStyle }, "Works from anywhere; visitors enter the PIN below.")
2705
- )
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."))
2706
2959
  )
2707
2960
  ) : (0, import_react.createElement)("p", { style: captionStyle }, "Start the tunnel to get a public address."),
2708
2961
  (0, import_react.createElement)(
2709
2962
  "div",
2710
- { style: { display: "flex", gap: 8, alignItems: "center" } },
2963
+ { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" } },
2711
2964
  (0, import_react.createElement)("span", { style: { ...fieldLabelStyle, margin: 0 } }, "Access PIN"),
2712
- (0, import_react.createElement)("code", { style: { ...codeStyle, fontSize: 15, letterSpacing: 2 } }, showPin && pin !== null ? pin : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"),
2713
- showPin ? (0, import_react.createElement)("button", { onClick: onHidePin, style: secondaryButtonStyle }, "Hide") : (0, import_react.createElement)("button", { onClick: onRevealPin, style: secondaryButtonStyle }, "Show"),
2714
- (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")
2715
2968
  ),
2716
2969
  (0, import_react.createElement)("p", { style: captionStyle }, "Stays the same across tunnel and DSH restarts; use Rotate when you need a new PIN.")
2717
2970
  );
2718
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
+ }
2719
2980
  function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2720
2981
  const [status, setStatus] = (0, import_react.useState)(null);
2721
2982
  const [proxyStatus, setProxyStatus] = (0, import_react.useState)(null);
@@ -2728,12 +2989,58 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2728
2989
  const [lanPinEnabled, setLanPinEnabled] = (0, import_react.useState)(false);
2729
2990
  const [lanPin, setLanPin] = (0, import_react.useState)(null);
2730
2991
  const [showLanPin, setShowLanPin] = (0, import_react.useState)(false);
2731
- const [activeTab, setActiveTab] = (0, import_react.useState)("guard");
2732
2992
  const [guard, setGuard] = (0, import_react.useState)({});
2733
2993
  const [patternsText, setPatternsText] = (0, import_react.useState)("");
2734
2994
  const [placeholdersText, setPlaceholdersText] = (0, import_react.useState)("");
2735
2995
  const [supervisorCfg, setSupervisorCfg] = (0, import_react.useState)({});
2736
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
+ }, []);
2737
3044
  const call = async (endpoint, payload) => {
2738
3045
  const res = await rpcCall(endpoint, payload);
2739
3046
  if (!res?.ok) throw new Error(res?.error?.message ?? "RPC failed");
@@ -2832,17 +3139,10 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2832
3139
  });
2833
3140
  if (configRpcCall) {
2834
3141
  configRpcCall("get", { domain: "supervisor" }).then((res) => {
2835
- if (res?.ok && res.value?.model) {
2836
- setConfig((prev) => ({ ...prev, supervisorModel: res.value.model }));
2837
- }
3142
+ if (res?.ok && res.value?.model) setConfig((prev) => ({ ...prev, supervisorModel: res.value.model }));
2838
3143
  }).catch(() => {
2839
3144
  });
2840
- Promise.all([
2841
- cfgGet("guard").catch(() => ({})),
2842
- cfgGet("guardBlacklist").catch(() => ({ patterns: [], placeholders: {} })),
2843
- cfgGet("supervisor").catch(() => ({})),
2844
- cfgGet("notifier").catch(() => ({}))
2845
- ]).then(([g, bl, sup, not]) => {
3145
+ Promise.all([cfgGet("guard").catch(() => ({})), cfgGet("guardBlacklist").catch(() => ({ patterns: [], placeholders: {} })), cfgGet("supervisor").catch(() => ({})), cfgGet("notifier").catch(() => ({}))]).then(([g, bl, sup, not]) => {
2846
3146
  setGuard(g ?? {});
2847
3147
  const pats = Array.isArray(bl?.patterns) ? bl.patterns : [];
2848
3148
  const ph = bl?.placeholders && typeof bl.placeholders === "object" ? bl.placeholders : {};
@@ -2863,8 +3163,8 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2863
3163
  }, []);
2864
3164
  (0, import_react.useEffect)(() => {
2865
3165
  refresh();
2866
- const t = setInterval(refresh, 3e3);
2867
- return () => clearInterval(t);
3166
+ const t2 = setInterval(refresh, 3e3);
3167
+ return () => clearInterval(t2);
2868
3168
  }, []);
2869
3169
  const revealPin = async () => {
2870
3170
  if (pin === null) {
@@ -2954,7 +3254,7 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2954
3254
  try {
2955
3255
  const res = await configRpcCall("set", { domain: "supervisor", patch: { model: value } });
2956
3256
  if (res?.ok) return;
2957
- } catch (e) {
3257
+ } catch {
2958
3258
  }
2959
3259
  }
2960
3260
  try {
@@ -2963,292 +3263,131 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2963
3263
  setError(err.message);
2964
3264
  }
2965
3265
  };
2966
- return (0, import_react.createElement)(
2967
- "div",
2968
- { "data-maestro-settings-card": "", style: { maxWidth: 520 } },
2969
- (0, import_react.createElement)("h3", { style: { ...headingStyle, fontSize: 15, margin: "0 0 12px" } }, "Maestro"),
2970
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Tunnel mode"),
2971
- (0, import_react.createElement)(
2972
- "select",
2973
- { value: config.tunnelMode, style: inputStyle, onChange: (e) => saveField("tunnelMode", e.target.value) },
2974
- (0, import_react.createElement)("option", { value: "quick" }, "Quick (no setup, URL changes on restart)"),
2975
- (0, import_react.createElement)("option", { value: "named" }, "Named (stable URL, one-time setup)")
2976
- ),
2977
- config.tunnelMode === "named" && (0, import_react.createElement)(NamedTunnelSetupNote),
2978
- 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)(
2979
3278
  "div",
2980
- null,
2981
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Tunnel ID"),
2982
- (0, import_react.createElement)("input", { placeholder: "Tunnel ID", style: inputStyle, value: config.tunnelId ?? "", onChange: (e) => saveField("tunnelId", e.target.value) }),
2983
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Credentials file path"),
2984
- (0, import_react.createElement)("input", { placeholder: "~/.cloudflared/<id>.json", style: inputStyle, value: config.tunnelCredentialsFile ?? "", onChange: (e) => saveField("tunnelCredentialsFile", e.target.value) }),
2985
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Hostname"),
2986
- (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 }))
2987
3292
  ),
2988
- (0, import_react.createElement)(
3293
+ notify: (0, import_react.createElement)(
2989
3294
  "div",
2990
- { style: { marginTop: 12 } },
2991
- 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) })
2992
3300
  ),
2993
- (0, import_react.createElement)(
3301
+ gitlab: (0, import_react.createElement)(
2994
3302
  "div",
2995
- { style: sectionStyle },
2996
- (0, import_react.createElement)("h4", { style: headingStyle }, "Remote access (LAN)"),
2997
- (0, import_react.createElement)(LanAccess, {
2998
- proxyStatus,
2999
- lanPin: lanPinEnabled === null ? null : {
3000
- enabled: lanPinEnabled,
3001
- pin: lanPin,
3002
- show: showLanPin,
3003
- onShow: revealLanPin,
3004
- onHide: () => setShowLanPin(false),
3005
- onRotate: rotateLanPin,
3006
- onToggle: toggleLanPin
3007
- }
3008
- })
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
+ )
3009
3314
  ),
3010
- (0, import_react.createElement)(
3315
+ review: (0, import_react.createElement)(
3011
3316
  "div",
3012
- { style: sectionStyle },
3013
- (0, import_react.createElement)("h4", { style: headingStyle }, "Public access"),
3014
- (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 })
3015
3322
  ),
3016
- (0, import_react.createElement)(
3323
+ guard: (0, import_react.createElement)(
3017
3324
  "div",
3018
- { style: sectionStyle },
3019
- (0, import_react.createElement)("h4", { style: headingStyle }, "Telegram notifications"),
3020
- (0, import_react.createElement)("p", { style: captionStyle }, "Send one protected startup update with the current public-access PIN to a single Telegram chat."),
3021
- (0, import_react.createElement)(SecretInput, {
3022
- label: "Bot token",
3023
- placeholder: "123456:ABC-DEF...",
3024
- hasSaved: config.hasTelegramBotToken,
3025
- onSave: (value) => saveField("telegramBotToken", value)
3026
- }),
3027
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Chat ID"),
3028
- (0, import_react.createElement)("input", { placeholder: "-1001234567890", autoComplete: "off", style: inputStyle, value: config.telegramChatId ?? "", onChange: (e) => saveField("telegramChatId", e.target.value) }),
3029
- (0, import_react.createElement)(ToggleField, {
3030
- label: "Also notify about finished reviews",
3031
- caption: "One message per review run with its outcome and a short summary.",
3032
- checked: config.telegramReviewNotifications,
3033
- onChange: (checked) => saveField("telegramReviewNotifications", checked)
3034
- }),
3035
- (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 } }) })
3036
3331
  ),
3037
- (0, import_react.createElement)(
3332
+ blacklist: (0, import_react.createElement)(
3038
3333
  "div",
3039
- { style: sectionStyle },
3040
- (0, import_react.createElement)("h4", { style: headingStyle }, "GitLab"),
3041
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "GitLab base URL"),
3042
- (0, import_react.createElement)("input", { placeholder: "https://gitlab.example.com", style: inputStyle, value: config.gitlabBaseUrl ?? "", onChange: (e) => saveField("gitlabBaseUrl", e.target.value) }),
3043
- (0, import_react.createElement)(SecretInput, {
3044
- label: "GitLab token",
3045
- placeholder: "GitLab token",
3046
- hasSaved: config.hasGitlabToken,
3047
- onSave: (value) => saveField("gitlabToken", value)
3048
- }),
3049
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "Bot username"),
3050
- (0, import_react.createElement)("input", { placeholder: "maestro-bot", style: inputStyle, value: config.botUsername ?? "", onChange: (e) => saveField("botUsername", e.target.value) }),
3051
- (0, import_react.createElement)(SecretInput, {
3052
- label: "Webhook secret",
3053
- placeholder: "Webhook secret",
3054
- hasSaved: config.hasWebhookSecret,
3055
- onSave: (value) => saveField("webhookSecret", value)
3056
- }),
3057
- (0, import_react.createElement)("button", { type: "button", style: { ...secondaryButtonStyle, marginTop: 10 }, onClick: () => saveField("webhookSecret", generateWebhookSecret()) }, "Generate new secret"),
3058
- (0, import_react.createElement)("p", { style: captionStyle }, "In GitLab: Settings \u2192 Webhooks, set Secret token to this value and enable Merge request events."),
3059
- (0, import_react.createElement)("p", { style: captionStyle }, "Webhook URL:"),
3060
- (0, import_react.createElement)("code", { style: codeStyle }, gitlabWebhookUrl(config.tunnelHostname)),
3061
- (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
+ )
3062
3353
  ),
3063
- (0, import_react.createElement)(
3354
+ supervisor: (0, import_react.createElement)(
3064
3355
  "div",
3065
- { style: sectionStyle },
3066
- (0, import_react.createElement)("h4", { style: headingStyle }, "Review automation"),
3067
- (0, import_react.createElement)(ToggleField, {
3068
- label: "Re-review when new commits are pushed",
3069
- caption: "After a completed review, further pushes to the same MR trigger an automatic quick re-review.",
3070
- checked: config.autoRereviewOnPush,
3071
- onChange: (checked) => saveField("autoRereviewOnPush", checked)
3072
- })
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 }) })
3073
3366
  ),
3074
- (0, import_react.createElement)(
3367
+ notifier: (0, import_react.createElement)(
3075
3368
  "div",
3076
- { style: sectionStyle },
3077
- (0, import_react.createElement)("h4", { style: headingStyle }, "Review model"),
3078
- (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."),
3079
- (0, import_react.createElement)(ReviewModelSelector, {
3080
- value: config.reviewModel ?? null,
3081
- catalog,
3082
- fallbackValue: catalog?.current ?? null,
3083
- fallbackLabel: "Use DSH default",
3084
- onChange: (v) => saveField("reviewModel", v),
3085
- label: "Global review model"
3086
- })
3087
- ),
3088
- (0, import_react.createElement)(
3089
- "div",
3090
- { style: sectionStyle },
3091
- (0, import_react.createElement)("h4", { style: headingStyle }, "Supervisor LLM"),
3092
- (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."),
3093
- (0, import_react.createElement)(ReviewModelSelector, {
3094
- value: config.supervisorModel ?? null,
3095
- catalog,
3096
- fallbackValue: catalog?.current ?? null,
3097
- fallbackLabel: "Use DSH default",
3098
- onChange: (v) => saveField("supervisorModel", v),
3099
- label: "Supervisor model"
3100
- })
3101
- ),
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" } },
3102
3378
  (0, import_react.createElement)(
3103
3379
  "div",
3104
- { style: sectionStyle },
3105
- (0, import_react.createElement)("h4", { style: headingStyle }, "Projects"),
3106
- (0, import_react.createElement)(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: (mappings) => saveField("projectMappings", mappings), catalog, globalReviewModel: config.reviewModel ?? null })
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.")
3107
3383
  ),
3108
- // Task 3: Guard/Blacklist/Supervisor/Notifier tabs — data-driven over guard domains
3109
3384
  (0, import_react.createElement)(
3110
3385
  "div",
3111
- { style: sectionStyle },
3112
- (0, import_react.createElement)("h4", { style: headingStyle }, "Guard / Blacklist / Supervisor / Notifier"),
3113
- (0, import_react.createElement)(
3114
- "div",
3115
- { style: tabBarStyle },
3116
- (0, import_react.createElement)("button", { type: "button", style: tabButtonStyle(activeTab === "guard"), onClick: () => setActiveTab("guard") }, "Guard"),
3117
- (0, import_react.createElement)("button", { type: "button", style: tabButtonStyle(activeTab === "blacklist"), onClick: () => setActiveTab("blacklist") }, "Blacklist"),
3118
- (0, import_react.createElement)("button", { type: "button", style: tabButtonStyle(activeTab === "supervisor"), onClick: () => setActiveTab("supervisor") }, "Supervisor"),
3119
- (0, import_react.createElement)("button", { type: "button", style: tabButtonStyle(activeTab === "notifier"), onClick: () => setActiveTab("notifier") }, "Notifier")
3120
- ),
3121
- activeTab === "guard" && (0, import_react.createElement)(
3122
- "div",
3123
- { "data-tab": "guard" },
3124
- (0, import_react.createElement)("p", { style: captionStyle }, "Enforce publish block, git protection and cwd containment."),
3125
- (0, import_react.createElement)(ToggleField, {
3126
- label: "publishBlocked",
3127
- caption: "Block publish-related commands when enabled.",
3128
- checked: guard.publishBlocked === true,
3129
- onChange: (v) => saveGuard({ publishBlocked: v })
3130
- }),
3131
- (0, import_react.createElement)(ToggleField, {
3132
- label: "gitProtection.enabled",
3133
- caption: "Protect pushes to protected branches.",
3134
- checked: guard.gitProtection?.enabled === true,
3135
- onChange: (v) => saveGuard({ gitProtection: { enabled: v, branches: guard.gitProtection?.branches ?? ["master", "main"] } })
3136
- }),
3137
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "gitProtection.branches (comma separated)"),
3138
- (0, import_react.createElement)("input", {
3139
- style: inputStyle,
3140
- value: (guard.gitProtection?.branches ?? ["master", "main"]).join(", "),
3141
- placeholder: "master, main",
3142
- onChange: (e) => {
3143
- const branches = e.target.value.split(",").map((s) => s.trim()).filter(Boolean);
3144
- saveGuard({ gitProtection: { enabled: guard.gitProtection?.enabled ?? true, branches } });
3145
- }
3146
- }),
3147
- (0, import_react.createElement)(ToggleField, {
3148
- label: "cwdContainment",
3149
- caption: "Contain file operations to the session cwd.",
3150
- checked: guard.cwdContainment === true,
3151
- onChange: (v) => saveGuard({ cwdContainment: v })
3152
- }),
3153
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "credentialPaths (comma separated)"),
3154
- (0, import_react.createElement)("input", {
3155
- style: inputStyle,
3156
- value: (guard.credentialPaths ?? []).join(", "),
3157
- placeholder: "~/.config/credentials.yaml, ~/.config/cloudflared",
3158
- onChange: (e) => {
3159
- const credentialPaths = e.target.value.split(",").map((s) => s.trim()).filter(Boolean);
3160
- saveGuard({ credentialPaths });
3161
- }
3162
- })
3163
- ),
3164
- activeTab === "blacklist" && (0, import_react.createElement)(
3165
- "div",
3166
- { "data-tab": "blacklist" },
3167
- (0, import_react.createElement)("p", { style: captionStyle }, "One pattern per line. These are blocked from being committed or published."),
3168
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "patterns (one per line)"),
3169
- (0, import_react.createElement)("textarea", {
3170
- style: textareaStyle,
3171
- value: patternsText,
3172
- placeholder: "example-project\nacme-shop",
3173
- onChange: (e) => setPatternsText(e.target.value),
3174
- onBlur: (e) => commitBlacklistPatterns(e.target.value)
3175
- }),
3176
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "placeholders JSON"),
3177
- (0, import_react.createElement)("textarea", {
3178
- style: { ...textareaStyle, height: 90 },
3179
- value: placeholdersText,
3180
- placeholder: '{"example-project":"my-project"}',
3181
- onChange: (e) => setPlaceholdersText(e.target.value),
3182
- onBlur: () => commitPlaceholders()
3183
- }),
3184
- (0, import_react.createElement)("p", { style: captionStyle }, "Map blocked patterns to their placeholder suggestions."),
3185
- (0, import_react.createElement)("button", { type: "button", style: { ...secondaryButtonStyle, marginTop: 8 }, onClick: () => {
3186
- commitBlacklistPatterns(patternsText);
3187
- commitPlaceholders();
3188
- } }, "Save Blacklist")
3189
- ),
3190
- activeTab === "supervisor" && (0, import_react.createElement)(
3191
- "div",
3192
- { "data-tab": "supervisor" },
3193
- (0, import_react.createElement)("p", { style: captionStyle }, "Background daemon that auto-resumes crashed sessions."),
3194
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "intervalMs"),
3195
- (0, import_react.createElement)("input", {
3196
- type: "number",
3197
- style: inputStyle,
3198
- value: supervisorCfg.intervalMs ?? "",
3199
- placeholder: "5000",
3200
- onChange: (e) => {
3201
- const v = e.target.value === "" ? void 0 : Number(e.target.value);
3202
- saveSupervisorCfg({ intervalMs: v });
3203
- }
3204
- }),
3205
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "downThreshold"),
3206
- (0, import_react.createElement)("input", {
3207
- type: "number",
3208
- style: inputStyle,
3209
- value: supervisorCfg.downThreshold ?? "",
3210
- placeholder: "3",
3211
- onChange: (e) => {
3212
- const v = e.target.value === "" ? void 0 : Number(e.target.value);
3213
- saveSupervisorCfg({ downThreshold: v });
3214
- }
3215
- }),
3216
- (0, import_react.createElement)(ToggleField, {
3217
- label: "autoResumeEnabled",
3218
- caption: "Automatically resume down sessions.",
3219
- checked: supervisorCfg.autoResumeEnabled === true,
3220
- onChange: (v) => saveSupervisorCfg({ autoResumeEnabled: v })
3221
- })
3222
- ),
3223
- activeTab === "notifier" && (0, import_react.createElement)(
3224
- "div",
3225
- { "data-tab": "notifier" },
3226
- (0, import_react.createElement)("p", { style: captionStyle }, "Telegram notifications for Maestro events."),
3227
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "telegram.botToken"),
3228
- (0, import_react.createElement)("input", {
3229
- type: "password",
3230
- autoComplete: "off",
3231
- style: inputStyle,
3232
- value: notifierCfg.telegram?.botToken ?? "",
3233
- placeholder: "123456:ABC-DEF...",
3234
- onChange: (e) => saveNotifierCfg({ telegram: { botToken: e.target.value } })
3235
- }),
3236
- (0, import_react.createElement)("label", { style: fieldLabelStyle }, "telegram.chatId"),
3237
- (0, import_react.createElement)("input", {
3238
- style: inputStyle,
3239
- value: notifierCfg.telegram?.chatId ?? "",
3240
- placeholder: "-1001234567890",
3241
- onChange: (e) => saveNotifierCfg({ telegram: { chatId: e.target.value } })
3242
- }),
3243
- (0, import_react.createElement)(ToggleField, {
3244
- label: "telegram.reviewNotifications",
3245
- caption: "Also notify about finished reviews.",
3246
- checked: notifierCfg.telegram?.reviewNotifications === true || notifierCfg.policy?.reviewNotifications === true,
3247
- onChange: (v) => saveNotifierCfg({ telegram: { reviewNotifications: v } })
3248
- })
3249
- )
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))
3250
3388
  ),
3251
- 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
3252
3391
  );
3253
3392
  }
3254
3393
 
@@ -3289,7 +3428,7 @@ function registerSettingsNavIcon(label, root) {
3289
3428
  }
3290
3429
 
3291
3430
  // src/client/index.tsx
3292
- var SETTINGS_NAV_CSS2 = `
3431
+ var SETTINGS_NAV_CSS = `
3293
3432
 
3294
3433
  /* maestro: replace the settings-nav fallback gear with the Maestro M-logo glyph */
3295
3434
  [${SETTINGS_NAV_MARKER}] > svg:first-child {
@@ -3311,7 +3450,7 @@ function installNavIconStyle() {
3311
3450
  const tag = document.createElement("style");
3312
3451
  tag.dataset.plugin = "@ddtcorex/dsh-maestro-config";
3313
3452
  tag.dataset.pluginCss = "maestro/settings-nav.css";
3314
- tag.textContent = SETTINGS_NAV_CSS2;
3453
+ tag.textContent = SETTINGS_NAV_CSS;
3315
3454
  document.head.appendChild(tag);
3316
3455
  return () => {
3317
3456
  document.querySelector('style[data-plugin-css="maestro/settings-nav.css"]')?.remove();