dsh-agy-link 0.4.67 → 0.4.70

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/dist/client.js CHANGED
@@ -604,6 +604,58 @@ window.__ModuleLoader__.load({
604
604
  "status.disabled": "Deshabilitado"
605
605
  };
606
606
  //#endregion
607
+ //#region src/client/scroll-guard.ts
608
+ let isScrolling = false;
609
+ let scrollTimer = null;
610
+ let listenerAttached = false;
611
+ const settleListeners = /* @__PURE__ */ new Set();
612
+ function onScrollActivity() {
613
+ isScrolling = true;
614
+ if (scrollTimer !== null) clearTimeout(scrollTimer);
615
+ scrollTimer = setTimeout(() => {
616
+ isScrolling = false;
617
+ scrollTimer = null;
618
+ for (const fn of settleListeners) try {
619
+ fn();
620
+ } catch {}
621
+ }, 150);
622
+ }
623
+ function isScrollActive() {
624
+ return isScrolling;
625
+ }
626
+ /** Register a callback to run once scrolling has settled. Returns an unregister function. */
627
+ function onScrollSettle(fn) {
628
+ ensureScrollListener();
629
+ settleListeners.add(fn);
630
+ return () => {
631
+ settleListeners.delete(fn);
632
+ };
633
+ }
634
+ function ensureScrollListener() {
635
+ if (typeof window === "undefined" || typeof window.addEventListener !== "function" || listenerAttached) return;
636
+ listenerAttached = true;
637
+ window.addEventListener("scroll", onScrollActivity, {
638
+ passive: true,
639
+ capture: true
640
+ });
641
+ window.addEventListener("wheel", onScrollActivity, {
642
+ passive: true,
643
+ capture: true
644
+ });
645
+ }
646
+ function disposeScrollListener() {
647
+ if (typeof window === "undefined" || typeof window.removeEventListener !== "function" || !listenerAttached) return;
648
+ window.removeEventListener("scroll", onScrollActivity, true);
649
+ window.removeEventListener("wheel", onScrollActivity, true);
650
+ listenerAttached = false;
651
+ if (scrollTimer !== null) {
652
+ clearTimeout(scrollTimer);
653
+ scrollTimer = null;
654
+ }
655
+ settleListeners.clear();
656
+ isScrolling = false;
657
+ }
658
+ //#endregion
607
659
  //#region src/client/decisions.ts
608
660
  const DECISION_CSS = `
609
661
  .agy-decision-container {
@@ -740,14 +792,34 @@ window.__ModuleLoader__.load({
740
792
  return options;
741
793
  }
742
794
  /**
743
- * Scan DOM for decision prompts and mount interactive choice chips
795
+ * Scan DOM for decision prompts and mount interactive choice chips.
796
+ *
797
+ * IDEMPOTENCE (incident 2026-10-23 — "scroll back and forth = insane flashing"):
798
+ * DSH renders these containers inside a TanStack-Virtual list with dynamic
799
+ * measurement (`measureElement` + `anchorTo:"start"`). Injecting a toolbar
800
+ * changes the measured item height, which makes the virtualizer rewrite
801
+ * scrollTop; the resulting re-render rebuilds the markdown/heading nodes
802
+ * (dropping the per-HEADING guard) and this scanner injected the toolbar
803
+ * again — growth on every scroll pass, i.e. a flashing flywheel.
804
+ *
805
+ * Two independent guards now make injection strictly once-per-container:
806
+ * 1. `data-agy-decisions-scanned` on the container, written on EVERY pass
807
+ * (it used to be read here but never assigned anywhere in the codebase),
808
+ * 2. "a toolbar already exists in this container" — survives React
809
+ * rebuilding the heading nodes during a virtualizer remount.
744
810
  */
745
811
  function scanAndEnhanceDecisions() {
746
812
  if (typeof document === "undefined") return;
813
+ if (isScrollActive()) return;
747
814
  const markdownContainers = document.querySelectorAll("div[class*=\"_markdown_\"], [data-markdown-variant]");
748
815
  for (let i = 0; i < markdownContainers.length; i++) {
749
816
  const container = markdownContainers[i];
750
817
  if (!container || container.dataset.agyDecisionsScanned === "true") continue;
818
+ if (container.querySelector(".agy-decision-container")) {
819
+ container.dataset.agyDecisionsScanned = "true";
820
+ continue;
821
+ }
822
+ container.dataset.agyDecisionsScanned = "true";
751
823
  const headings = container.querySelectorAll("h2, h3, h4, p > strong");
752
824
  for (let j = 0; j < headings.length; j++) {
753
825
  const heading = headings[j];
@@ -804,12 +876,26 @@ window.__ModuleLoader__.load({
804
876
  }
805
877
  }
806
878
  let decisionChipsDispose = null;
879
+ let decisionScanTimer = null;
880
+ let unbindDecisionSettle = null;
881
+ function scheduleDecisionScan() {
882
+ if (isScrollActive()) return;
883
+ if (decisionScanTimer !== null) return;
884
+ decisionScanTimer = setTimeout(() => {
885
+ decisionScanTimer = null;
886
+ if (isScrollActive()) return;
887
+ scanAndEnhanceDecisions();
888
+ }, 100);
889
+ }
807
890
  function installDecisionChips() {
808
891
  if (typeof window === "undefined" || typeof document === "undefined") return () => {};
809
892
  decisionChipsDispose?.();
810
893
  injectStyles$3();
811
894
  const observer = new MutationObserver(() => {
812
- scanAndEnhanceDecisions();
895
+ scheduleDecisionScan();
896
+ });
897
+ unbindDecisionSettle = onScrollSettle(() => {
898
+ scheduleDecisionScan();
813
899
  });
814
900
  const setup = () => {
815
901
  if (document.body) {
@@ -824,6 +910,14 @@ window.__ModuleLoader__.load({
824
910
  else setup();
825
911
  const dispose = () => {
826
912
  observer.disconnect();
913
+ if (unbindDecisionSettle !== null) {
914
+ unbindDecisionSettle();
915
+ unbindDecisionSettle = null;
916
+ }
917
+ if (decisionScanTimer !== null) {
918
+ clearTimeout(decisionScanTimer);
919
+ decisionScanTimer = null;
920
+ }
827
921
  window.removeEventListener("DOMContentLoaded", setup);
828
922
  if (decisionChipsDispose === dispose) decisionChipsDispose = null;
829
923
  };
@@ -1194,6 +1288,7 @@ window.__ModuleLoader__.load({
1194
1288
  */
1195
1289
  function scanAndEnhanceImages() {
1196
1290
  if (typeof document === "undefined") return;
1291
+ if (isScrollActive()) return;
1197
1292
  const origin = getOrigin();
1198
1293
  if (origin && !origin.includes("127.0.0.1") && !origin.includes("localhost")) {
1199
1294
  const images = document.querySelectorAll("img[src*=\"/plugins/agy-link/media\"]");
@@ -1283,12 +1378,26 @@ window.__ModuleLoader__.load({
1283
1378
  }
1284
1379
  }
1285
1380
  let imageEnhancerDispose = null;
1381
+ let imageScanTimer = null;
1382
+ let unbindImageSettle = null;
1383
+ function scheduleImageScan() {
1384
+ if (isScrollActive()) return;
1385
+ if (imageScanTimer !== null) return;
1386
+ imageScanTimer = setTimeout(() => {
1387
+ imageScanTimer = null;
1388
+ if (isScrollActive()) return;
1389
+ scanAndEnhanceImages();
1390
+ }, 100);
1391
+ }
1286
1392
  function installImageEnhancer() {
1287
1393
  if (typeof window === "undefined" || typeof document === "undefined") return () => {};
1288
1394
  imageEnhancerDispose?.();
1289
1395
  injectStyles$2();
1290
1396
  const observer = new MutationObserver(() => {
1291
- scanAndEnhanceImages();
1397
+ scheduleImageScan();
1398
+ });
1399
+ unbindImageSettle = onScrollSettle(() => {
1400
+ scheduleImageScan();
1292
1401
  });
1293
1402
  const setup = () => {
1294
1403
  if (document.body) {
@@ -1303,6 +1412,14 @@ window.__ModuleLoader__.load({
1303
1412
  else setup();
1304
1413
  const dispose = () => {
1305
1414
  observer.disconnect();
1415
+ if (unbindImageSettle !== null) {
1416
+ unbindImageSettle();
1417
+ unbindImageSettle = null;
1418
+ }
1419
+ if (imageScanTimer !== null) {
1420
+ clearTimeout(imageScanTimer);
1421
+ imageScanTimer = null;
1422
+ }
1306
1423
  window.removeEventListener("DOMContentLoaded", setup);
1307
1424
  if (imageEnhancerDispose === dispose) imageEnhancerDispose = null;
1308
1425
  };
@@ -1316,12 +1433,23 @@ window.__ModuleLoader__.load({
1316
1433
  if (mod === void 0 || mod === null) throw new Error("dsh-agy-link toolview requires react at render time (browser host)");
1317
1434
  return mod;
1318
1435
  }
1319
- function makeToggle(setValue) {
1320
- return () => setValue((prev) => !prev);
1321
- }
1322
- function useToggle(initial = false) {
1323
- const [value, setValue] = react().useState(initial);
1324
- return [value, makeToggle(setValue)];
1436
+ /** Cache user-expanded tool cards across TanStack Virtualizer remount cycles */
1437
+ const expandedToolBlockKeys = /* @__PURE__ */ new Set();
1438
+ function useToggle(initial = false, key) {
1439
+ const R2 = react();
1440
+ const init = key ? expandedToolBlockKeys.has(key) : initial;
1441
+ const [value, setValue] = R2.useState(init);
1442
+ const toggle = () => {
1443
+ setValue((prev) => {
1444
+ const next = !prev;
1445
+ if (key) {
1446
+ if (next) expandedToolBlockKeys.add(key);
1447
+ else expandedToolBlockKeys.delete(key);
1448
+ }
1449
+ return next;
1450
+ });
1451
+ };
1452
+ return [value, toggle];
1325
1453
  }
1326
1454
  function hx(type, props, ...children) {
1327
1455
  return react().createElement(type, props, ...children);
@@ -2096,12 +2224,12 @@ window.__ModuleLoader__.load({
2096
2224
  }
2097
2225
  }
2098
2226
  function AgyMirrorToolView(props) {
2099
- const [open, setOpen] = useToggle(false);
2100
2227
  const h = hx;
2101
- const { block, cwd, home, openFile, inspect, t } = props;
2228
+ const { block, cwd, home, openFile, inspect, t, callId } = props;
2102
2229
  const settle = isSettled(block);
2103
2230
  const err = isError(block);
2104
2231
  const model = mirrorCardModel(block, cwd, home);
2232
+ const [open, setOpen] = useToggle(false, callId ? `call:${callId}` : `${model.kind}:${model.tool ?? ""}:${model.title}:${model.path ?? model.command ?? ""}`);
2105
2233
  const state = !settle ? "run" : err ? "err" : "ok";
2106
2234
  const title = displayTitle(model, t);
2107
2235
  const summary = displaySummary(model, cwd, home);
@@ -2217,10 +2345,10 @@ window.__ModuleLoader__.load({
2217
2345
  * friends) so an ordinary Code Mode call reads exactly like a native tool row.
2218
2346
  */
2219
2347
  function AgyCodeToolView(props) {
2220
- const [open, setOpen] = useToggle(false);
2221
2348
  const h = hx;
2222
- const { block, inspect, t } = props ?? {};
2349
+ const { block, inspect, t, callId } = props ?? {};
2223
2350
  const model = runCodeModel(block);
2351
+ const [open, setOpen] = useToggle(false, callId ? `call:${callId}` : `code:${model.title}`);
2224
2352
  const settle = block !== void 0 && isSettled(block);
2225
2353
  const err = block !== void 0 && isError(block);
2226
2354
  const state = !settle ? "run" : err ? "err" : "ok";
@@ -3207,14 +3335,17 @@ body > [id^="iagy-mmd"] .error-icon {
3207
3335
  }
3208
3336
  let scanTimer$1 = null;
3209
3337
  function scheduleScan$1() {
3338
+ if (isScrollActive()) return;
3210
3339
  if (scanTimer$1 !== null) return;
3211
3340
  scanTimer$1 = setTimeout(() => {
3212
3341
  scanTimer$1 = null;
3342
+ if (isScrollActive()) return;
3213
3343
  scan();
3214
3344
  }, 150);
3215
3345
  }
3216
3346
  function scan() {
3217
3347
  if (typeof document === "undefined") return;
3348
+ if (isScrollActive()) return;
3218
3349
  cleanupTempElements();
3219
3350
  const blocks = document.querySelectorAll(".md-code-block");
3220
3351
  for (let i = 0; i < blocks.length; i++) {
@@ -3232,6 +3363,7 @@ body > [id^="iagy-mmd"] .error-icon {
3232
3363
  }
3233
3364
  let activeObserver$1 = null;
3234
3365
  let domContentLoadedHandler$1 = null;
3366
+ let unbindMermaidSettle = null;
3235
3367
  /**
3236
3368
  * Install the mermaid overlay. Returns a disposer that disconnects the
3237
3369
  * MutationObserver and cancels pending setup — wrap it in ctx.effect so a
@@ -3246,6 +3378,9 @@ body > [id^="iagy-mmd"] .error-icon {
3246
3378
  scheduleScan$1();
3247
3379
  });
3248
3380
  activeObserver$1 = observer;
3381
+ unbindMermaidSettle = onScrollSettle(() => {
3382
+ scheduleScan$1();
3383
+ });
3249
3384
  const setup = () => {
3250
3385
  if (document.body) {
3251
3386
  cleanupTempElements();
@@ -3268,6 +3403,10 @@ body > [id^="iagy-mmd"] .error-icon {
3268
3403
  activeObserver$1.disconnect();
3269
3404
  activeObserver$1 = null;
3270
3405
  }
3406
+ if (unbindMermaidSettle !== null) {
3407
+ unbindMermaidSettle();
3408
+ unbindMermaidSettle = null;
3409
+ }
3271
3410
  if (domContentLoadedHandler$1 !== null) {
3272
3411
  window.removeEventListener("DOMContentLoaded", domContentLoadedHandler$1);
3273
3412
  domContentLoadedHandler$1 = null;
@@ -3334,42 +3473,57 @@ div[data-variant="think"] [class*="thinkBody"] {
3334
3473
  }
3335
3474
  }
3336
3475
  /**
3476
+ * Persistent cache of handled thinking turns (keyed by turn fingerprint).
3477
+ * Survived across TanStack Virtualizer unmount/remount cycles.
3478
+ */
3479
+ const handledTurns = /* @__PURE__ */ new Set();
3480
+ function getTurnKey(root, text) {
3481
+ const parent = root.closest("[data-message-id], [data-item-index], [data-index], [data-role=\"message-content\"]");
3482
+ return `${parent?.getAttribute("data-message-id") ?? parent?.getAttribute("data-index") ?? parent?.getAttribute("data-item-index") ?? ""}:${text.slice(0, 100)}`;
3483
+ }
3484
+ /**
3337
3485
  * Try to expand a thinking row once when its body contains real prose
3338
3486
  * (not a bare [agy thinking turn · N tokens] chip). Uses the host's own
3339
3487
  * disclosure control when present — never mutates thinkBody children.
3340
- *
3341
- * A row the user manually COLLAPSED is recorded in a WeakSet keyed by the
3342
- * row element; React keeps the same element identity across its own
3343
- * re-renders, so the scanner stops re-expanding it (the old data attribute
3344
- * survived only until React rebuilt the node).
3345
3488
  */
3346
- const userCollapsed = typeof WeakSet !== "undefined" ? /* @__PURE__ */ new WeakSet() : null;
3347
3489
  function tryExpandThinkRow(root) {
3348
3490
  if (root.dataset.agyExpanded === "true") return;
3349
- if (userCollapsed?.has(root) === true) return;
3350
3491
  const body = root.querySelector("[class*=\"thinkBody\"]");
3351
3492
  if (!body) return;
3352
3493
  const text = (body.textContent ?? "").trim();
3353
3494
  if (text === "") return;
3354
3495
  if (/^\[agy(?: thinking turn)?(?: · \d+ (?:thinking )?tokens)?\]\s*$/.test(text)) return;
3355
- const style = typeof getComputedStyle === "function" ? getComputedStyle(body) : null;
3356
- if (style && style.display !== "none" && body.clientHeight > 8) {
3496
+ const turnKey = getTurnKey(root, text);
3497
+ if (handledTurns.has(turnKey)) {
3357
3498
  root.dataset.agyExpanded = "true";
3358
3499
  return;
3359
3500
  }
3360
3501
  const toggle = root.querySelector("[data-disclosure-row]") ?? root.querySelector("[class*=\"disclosure\"]") ?? root.querySelector("button");
3361
- if (toggle) {
3362
- userCollapsed?.add(root);
3363
- try {
3364
- toggle.click();
3365
- root.dataset.agyExpanded = "true";
3366
- } catch {
3367
- userCollapsed?.delete(root);
3368
- }
3502
+ if (!toggle) return;
3503
+ if (!toggle.dataset.agyClickBound) {
3504
+ toggle.dataset.agyClickBound = "true";
3505
+ toggle.addEventListener("click", () => {
3506
+ handledTurns.add(turnKey);
3507
+ }, {
3508
+ capture: true,
3509
+ passive: true
3510
+ });
3511
+ }
3512
+ if (isScrollActive()) return;
3513
+ if (body.clientHeight > 8) {
3514
+ handledTurns.add(turnKey);
3515
+ root.dataset.agyExpanded = "true";
3516
+ return;
3369
3517
  }
3518
+ handledTurns.add(turnKey);
3519
+ root.dataset.agyExpanded = "true";
3520
+ try {
3521
+ toggle.click();
3522
+ } catch {}
3370
3523
  }
3371
3524
  function scanThinkingRows() {
3372
3525
  if (typeof document === "undefined") return;
3526
+ if (isScrollActive()) return;
3373
3527
  const rows = document.querySelectorAll("div[data-variant=\"think\"]");
3374
3528
  for (let i = 0; i < rows.length; i++) {
3375
3529
  const row = rows[i];
@@ -3378,14 +3532,17 @@ div[data-variant="think"] [class*="thinkBody"] {
3378
3532
  }
3379
3533
  let scanTimer = null;
3380
3534
  function scheduleScan() {
3535
+ if (isScrollActive()) return;
3381
3536
  if (scanTimer !== null) return;
3382
3537
  scanTimer = setTimeout(() => {
3383
3538
  scanTimer = null;
3539
+ if (isScrollActive()) return;
3384
3540
  scanThinkingRows();
3385
- }, 200);
3541
+ }, 150);
3386
3542
  }
3387
3543
  let activeObserver = null;
3388
3544
  let domContentLoadedHandler = null;
3545
+ let unbindSettle = null;
3389
3546
  /**
3390
3547
  * Install the auto-expand reasoning overlay. Returns a disposer that
3391
3548
  * disconnects the MutationObserver — wrap it in ctx.effect so a plugin
@@ -3399,6 +3556,9 @@ div[data-variant="think"] [class*="thinkBody"] {
3399
3556
  scheduleScan();
3400
3557
  });
3401
3558
  activeObserver = observer;
3559
+ unbindSettle = onScrollSettle(() => {
3560
+ scheduleScan();
3561
+ });
3402
3562
  const setupObserver = () => {
3403
3563
  if (document.body) {
3404
3564
  observer.observe(document.body, {
@@ -3420,6 +3580,10 @@ div[data-variant="think"] [class*="thinkBody"] {
3420
3580
  activeObserver.disconnect();
3421
3581
  activeObserver = null;
3422
3582
  }
3583
+ if (unbindSettle !== null) {
3584
+ unbindSettle();
3585
+ unbindSettle = null;
3586
+ }
3423
3587
  if (domContentLoadedHandler !== null) {
3424
3588
  window.removeEventListener("DOMContentLoaded", domContentLoadedHandler);
3425
3589
  domContentLoadedHandler = null;
@@ -3859,8 +4023,12 @@ div[data-variant="think"] [class*="thinkBody"] {
3859
4023
  scheduled = false;
3860
4024
  if (disposed) return;
3861
4025
  const wanted = resolveLabel();
3862
- for (const row of Array.from(document.querySelectorAll(NAV_ROW_SELECTOR))) if (isOwnNavRow(row.textContent, wanted)) row.setAttribute(NAV_ICON_MARKER, "");
3863
- else row.removeAttribute(NAV_ICON_MARKER);
4026
+ for (const row of Array.from(document.querySelectorAll(NAV_ROW_SELECTOR))) {
4027
+ const shouldMark = isOwnNavRow(row.textContent, wanted);
4028
+ if (shouldMark === row.hasAttribute(NAV_ICON_MARKER)) continue;
4029
+ if (shouldMark) row.setAttribute(NAV_ICON_MARKER, "");
4030
+ else row.removeAttribute(NAV_ICON_MARKER);
4031
+ }
3864
4032
  };
3865
4033
  const schedule = () => {
3866
4034
  if (scheduled || disposed) return;
@@ -3872,7 +4040,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3872
4040
  observer.observe(document.body, {
3873
4041
  childList: true,
3874
4042
  subtree: true,
3875
- characterData: true
4043
+ attributeFilter: [NAV_ICON_MARKER]
3876
4044
  });
3877
4045
  return () => {
3878
4046
  disposed = true;
@@ -4871,6 +5039,9 @@ body.dark,
4871
5039
  const accounts = pool?.accounts ?? [];
4872
5040
  const isAuthed = authPhase === "ok" || accounts.length > 0;
4873
5041
  const isBusy = loadingAction !== null;
5042
+ /** Busy for a per-account action; also blocks while a global action runs. */
5043
+ const accountActionBusy = (acc, action) => loadingAction === `${action}:${acc.id}`;
5044
+ isBusy && loadingAction?.includes(":");
4874
5045
  const renderSpinner = () => h("span", { className: "agy-spinner" });
4875
5046
  const renderToastBanner = () => {
4876
5047
  if (!toast) return null;
@@ -4920,8 +5091,7 @@ body.dark,
4920
5091
  "aria-label": t("aria.closeToast")
4921
5092
  }, uiIcon("x", 12)));
4922
5093
  };
4923
- const isRefreshingQuota = loadingAction === "refresh-quota";
4924
- const renderQuotaBar = (label, familyKey, acc) => renderFamilyQuotaBar(t, label, familyKey, acc, isRefreshingQuota);
5094
+ const renderQuotaBar = (label, familyKey, acc) => renderFamilyQuotaBar(t, label, familyKey, acc, loadingAction === "refresh-quota" || loadingAction === `refresh:${acc.id}`);
4925
5095
  const authoritativeActiveId = pool?.activeAccountId ?? pool?.lastActiveAccountId ?? pool?.activeAccountIds?.google ?? pool?.primaryAccountId;
4926
5096
  const activeAccountId = optimisticActiveId ?? authoritativeActiveId;
4927
5097
  const renderedAccountCards = accounts.map((acc) => {
@@ -5132,7 +5302,7 @@ body.dark,
5132
5302
  padding: "2px 6px"
5133
5303
  },
5134
5304
  title: acc.enabled ? t("account.disableTitle") : t("account.enableTitle"),
5135
- disabled: isBusy,
5305
+ disabled: accountActionBusy(acc, "enable"),
5136
5306
  onClick: () => void toggleAccountEnabled(acc)
5137
5307
  }, loadingAction === `enable:${acc.id}` ? [renderSpinner(), acc.enabled ? t("account.disable") : t("account.enable")] : [uiIcon("power", 10, acc.enabled ? "var(--agy-text-secondary)" : "#ffffff"), ` ${acc.enabled ? t("account.disable") : t("account.enable")}`]), h("button", {
5138
5308
  type: "button",
@@ -5144,7 +5314,7 @@ body.dark,
5144
5314
  padding: "2px 6px"
5145
5315
  },
5146
5316
  title: t("account.syncTitle"),
5147
- disabled: isBusy,
5317
+ disabled: accountActionBusy(acc, "refresh"),
5148
5318
  onClick: () => void refreshQuota(acc.id)
5149
5319
  }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 10), ` ${t("account.sync")}`]), acc.enabled && !isInUse ? h("button", {
5150
5320
  type: "button",
@@ -5156,7 +5326,7 @@ body.dark,
5156
5326
  padding: "2px 6px"
5157
5327
  },
5158
5328
  title: t("account.setActive"),
5159
- disabled: isBusy,
5329
+ disabled: accountActionBusy(acc, "active"),
5160
5330
  onClick: () => void setActiveAccount(acc.id)
5161
5331
  }, loadingAction === `active:${acc.id}` ? [renderSpinner(), t("account.settingActive")] : [uiIcon("zap", 10), ` ${t("account.setActive")}`]) : null, h("button", {
5162
5332
  type: "button",
@@ -5172,7 +5342,7 @@ body.dark,
5172
5342
  fontSize: "11px",
5173
5343
  padding: "2px 6px"
5174
5344
  },
5175
- disabled: isBusy,
5345
+ disabled: accountActionBusy(acc, "proxy"),
5176
5346
  onClick: () => setEditingProxyId(isEditingProxy ? null : acc.id)
5177
5347
  }, [uiIcon("globe", 10), ` ${t("proxy.button")}`]), accounts.length > 1 ? h("button", {
5178
5348
  type: "button",
@@ -5184,7 +5354,7 @@ body.dark,
5184
5354
  },
5185
5355
  title: t("account.removeTitle"),
5186
5356
  "aria-label": t("account.removeTitle"),
5187
- disabled: isBusy,
5357
+ disabled: accountActionBusy(acc, "remove"),
5188
5358
  onClick: () => void removeAccount(acc.id, displayAlias)
5189
5359
  }, loadingAction === `remove:${acc.id}` ? renderSpinner() : uiIcon("trash", 11, "var(--agy-danger-text)")) : null)), h("div", {
5190
5360
  key: "agy-body",
@@ -5273,7 +5443,7 @@ body.dark,
5273
5443
  background: "var(--agy-warn-btn-bg)",
5274
5444
  gap: "4px"
5275
5445
  },
5276
- disabled: isBusy,
5446
+ disabled: accountActionBusy(acc, "clearCooldown"),
5277
5447
  onClick: () => void clearCooldown(acc.id)
5278
5448
  }, loadingAction === `clearCooldown:${acc.id}` ? [renderSpinner(), ""] : [uiIcon("zap", 11, "var(--agy-warn-btn-text)"), ` ${t("account.clearCooldown")}`])) : null, isEditingProxy ? h("div", {
5279
5449
  key: "proxy-body",
@@ -5300,7 +5470,7 @@ body.dark,
5300
5470
  type: "button",
5301
5471
  className: "agy-btn",
5302
5472
  style: S.btnPrimary,
5303
- disabled: isBusy,
5473
+ disabled: accountActionBusy(acc, "proxy"),
5304
5474
  onClick: () => void saveProxy(acc.id)
5305
5475
  }, loadingAction === `proxy:${acc.id}` ? [renderSpinner(), t("proxy.save")] : t("proxy.save")), h("button", {
5306
5476
  type: "button",
@@ -5745,6 +5915,10 @@ body.dark,
5745
5915
  return h(AgySettingsSection, { isModal: false });
5746
5916
  });
5747
5917
  });
5918
+ ctx.effect(() => {
5919
+ ensureScrollListener();
5920
+ return disposeScrollListener;
5921
+ });
5748
5922
  installAgyToolView(ctx);
5749
5923
  ctx.effect(() => installAutoExpandReasoning());
5750
5924
  ctx.effect(() => installMermaidRendering());