dsh-context 0.54.4 → 0.56.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
@@ -78,6 +78,7 @@ window.__ModuleLoader__.load({
78
78
  "timing.other": "其他开销",
79
79
  "timing.callTimes": "{n}次",
80
80
  "timing.toolTimes": "{n}次",
81
+ "timing.blockTimes": "{n}次",
81
82
  "timing.empty": "暂无耗时数据 · 对话开始后这里会显示时长分布",
82
83
  "timing.tps": "{tps} tok/s",
83
84
  "timing.tpsTip": "输出速度(TPS):累计解码输出 tokens ÷ 累计解码时长,与 DeepSeek Harness 会话统计同口径。",
@@ -90,7 +91,7 @@ window.__ModuleLoader__.load({
90
91
  "plugin.name": "插件",
91
92
  "plugin.github": "GitHub",
92
93
  "plugin.settings": "设置",
93
- "plugin.settingsOpen": "打开设置",
94
+ "plugin.settingsOpen": "打开插件设置",
94
95
  "trend.title": "上下文趋势",
95
96
  "gran.step": "步骤",
96
97
  "gran.turn": "轮次",
@@ -362,9 +363,15 @@ window.__ModuleLoader__.load({
362
363
  "ov.heat.sub": "按日期筛选",
363
364
  "ov.heat.empty": "暂无活跃数据 — 会话产生模型请求后,这里会显示每日用量",
364
365
  "ov.heat.sessions": "{n} 个活跃会话",
365
- "ov.heat.wd.1": "一",
366
- "ov.heat.wd.3": "三",
367
- "ov.heat.wd.5": "五",
366
+ "ov.heat.less": "少",
367
+ "ov.heat.more": "多",
368
+ "ov.heat.metric": "指标",
369
+ "ov.heat.metric.sessions": "会话",
370
+ "ov.heat.metric.steps": "步数",
371
+ "ov.heat.steps": "{n} 步",
372
+ "ov.heat.wd.1": "星期一",
373
+ "ov.heat.wd.3": "星期三",
374
+ "ov.heat.wd.5": "星期五",
368
375
  "ov.heat.mon.01": "1月",
369
376
  "ov.heat.mon.02": "2月",
370
377
  "ov.heat.mon.03": "3月",
@@ -378,7 +385,7 @@ window.__ModuleLoader__.load({
378
385
  "ov.heat.mon.11": "11月",
379
386
  "ov.heat.mon.12": "12月",
380
387
  "ov.list.title": "会话",
381
- "ov.list.search": "搜索标题或目录…",
388
+ "ov.list.search": "搜索标题 / 目录 / 最后消息…",
382
389
  "ov.list.sortLabel": "排序",
383
390
  "ov.list.sort.recent": "最近更新",
384
391
  "ov.list.sort.tokens": "Token 总用量",
@@ -450,6 +457,7 @@ window.__ModuleLoader__.load({
450
457
  "timing.other": "Overhead",
451
458
  "timing.callTimes": "{n} calls",
452
459
  "timing.toolTimes": "{n} runs",
460
+ "timing.blockTimes": "{n} blocks",
453
461
  "timing.empty": "No timing data yet — durations appear as the conversation runs",
454
462
  "timing.tps": "{tps} tok/s",
455
463
  "timing.tpsTip": "Output speed (TPS): summed decoded output tokens over summed decode time — the same pairing as the harness session stats.",
@@ -462,7 +470,7 @@ window.__ModuleLoader__.load({
462
470
  "plugin.name": "Plugin",
463
471
  "plugin.github": "GitHub",
464
472
  "plugin.settings": "Settings",
465
- "plugin.settingsOpen": "Open in Settings",
473
+ "plugin.settingsOpen": "Open plugin settings",
466
474
  "trend.title": "Context Trend",
467
475
  "gran.step": "Step",
468
476
  "gran.turn": "Turn",
@@ -734,9 +742,15 @@ window.__ModuleLoader__.load({
734
742
  "ov.heat.sub": "Filter by date",
735
743
  "ov.heat.empty": "No activity yet — daily usage appears once sessions make model requests",
736
744
  "ov.heat.sessions": "{n} active sessions",
737
- "ov.heat.wd.1": "M",
738
- "ov.heat.wd.3": "W",
739
- "ov.heat.wd.5": "F",
745
+ "ov.heat.less": "Less",
746
+ "ov.heat.more": "More",
747
+ "ov.heat.metric": "Metric",
748
+ "ov.heat.metric.sessions": "Sessions",
749
+ "ov.heat.metric.steps": "Steps",
750
+ "ov.heat.steps": "{n} steps",
751
+ "ov.heat.wd.1": "Mon",
752
+ "ov.heat.wd.3": "Wed",
753
+ "ov.heat.wd.5": "Fri",
740
754
  "ov.heat.mon.01": "Jan",
741
755
  "ov.heat.mon.02": "Feb",
742
756
  "ov.heat.mon.03": "Mar",
@@ -750,7 +764,7 @@ window.__ModuleLoader__.load({
750
764
  "ov.heat.mon.11": "Nov",
751
765
  "ov.heat.mon.12": "Dec",
752
766
  "ov.list.title": "Sessions",
753
- "ov.list.search": "Search title or path…",
767
+ "ov.list.search": "Search title / path / last message…",
754
768
  "ov.list.sortLabel": "Sort",
755
769
  "ov.list.sort.recent": "Last updated",
756
770
  "ov.list.sort.tokens": "Total Tokens",
@@ -776,20 +790,23 @@ window.__ModuleLoader__.load({
776
790
  "ov.time.d": "{n}d ago"
777
791
  };
778
792
  //#endregion
793
+ //#region icon.svg?raw
794
+ var icon_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\">\n <path d=\"M899.984 19.873h-3.452c-26.123 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.173 47.298 47.296 47.298h3.452c26.119 0 47.297-21.171 47.297-47.298V67.169c0-26.124-21.177-47.296-47.297-47.296z\" fill=\"#4A5699\"/>\n <path d=\"M132.643 19.873h-3.449c-26.12 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.177 47.298 47.296 47.298h3.449c26.123 0 47.299-21.171 47.299-47.298V67.169c0-26.124-21.176-47.296-47.299-47.296z\" fill=\"#C45FA0\"/>\n <path d=\"M899.463 19.873H129.194c-26.12 0-47.296 21.172-47.296 47.296v3.377c0 26.12 21.177 47.299 47.296 47.299h770.269c26.123 0 47.296-21.179 47.296-47.299v-3.377c0-26.124-21.173-47.296-47.296-47.296z\" fill=\"#6277BA\"/>\n <path d=\"M899.463 905.006H129.194c-26.12 0-47.296 21.17-47.296 47.29v3.381c0 26.127 21.177 47.298 47.296 47.298h770.269c26.123 0 47.296-21.171 47.296-47.298v-3.381c0-26.12-21.173-47.29-47.296-47.29z\" fill=\"#C45FA0\"/>\n <path d=\"M717.962 543.153H542.047c-26.121 0-47.298 21.175-47.298 47.297v3.724c0 26.123 21.177 47.293 47.298 47.293h175.915c26.121 0 47.297-21.17 47.297-47.293v-3.724c0-26.122-21.176-47.297-47.297-47.297z\" fill=\"#E5594F\"/>\n <path d=\"M689.268 198.849H513.355c-26.122 0-47.298 21.175-47.298 47.297v3.722c0 26.12 21.176 47.297 47.298 47.297h175.912c26.122 0 47.298-21.177 47.298-47.297v-3.722c0-26.122-21.175-47.297-47.297-47.297z\" fill=\"#F0D043\"/>\n <path d=\"M757.789 353.081H261.17c-26.121 0-47.297 21.172-47.297 47.296v3.377c0 26.121 21.177 47.299 47.297 47.299h496.619c26.121 0 47.296-21.178 47.296-47.299v-3.377c0-26.125-21.175-47.296-47.296-47.296z\" fill=\"#E5594F\"/>\n <path d=\"M762.638 726.225h-496.62c-26.12 0-47.294 21.18-47.294 47.301v3.377c0 26.12 21.174 47.3 47.294 47.3h496.62c26.122 0 47.296-21.18 47.296-47.3v-3.377c0-26.122-21.174-47.301-47.296-47.301z\" fill=\"#6277BA\"/>\n <path d=\"M355.734 543.328H281.41c-26.122 0-47.297 21.17-47.297 47.293v3.378c0 26.118 21.175 47.297 47.297 47.297h74.324c26.123 0 47.296-21.179 47.296-47.297v-3.378c0-26.123-21.174-47.293-47.296-47.293z\" fill=\"#F39A2B\"/>\n <path d=\"M334.85 248.006m-48.986 0a48.986 48.986 0 1 0 97.972 0 48.986 48.986 0 1 0-97.972 0Z\" fill=\"#F39A2B\"/>\n</svg>\n";
795
+ //#endregion
779
796
  //#region src/client/icon.tsx
780
- /** The sheet's strokes, in paint order, as `[path, fill]` pairs. */
781
- const SHEET_PATHS = [
782
- ["M899.984 19.873h-3.452c-26.123 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.173 47.298 47.296 47.298h3.452c26.119 0 47.297-21.171 47.297-47.298V67.169c0-26.124-21.177-47.296-47.297-47.296z", "#4A5699"],
783
- ["M132.643 19.873h-3.449c-26.12 0-47.296 21.172-47.296 47.296v888.508c0 26.127 21.177 47.298 47.296 47.298h3.449c26.123 0 47.299-21.171 47.299-47.298V67.169c0-26.124-21.176-47.296-47.299-47.296z", "#C45FA0"],
784
- ["M899.463 19.873H129.194c-26.12 0-47.296 21.172-47.296 47.296v3.377c0 26.12 21.177 47.299 47.296 47.299h770.269c26.123 0 47.296-21.179 47.296-47.299v-3.377c0-26.124-21.173-47.296-47.296-47.296z", "#6277BA"],
785
- ["M899.463 905.006H129.194c-26.12 0-47.296 21.17-47.296 47.29v3.381c0 26.127 21.177 47.298 47.296 47.298h770.269c26.123 0 47.296-21.171 47.296-47.298v-3.381c0-26.12-21.173-47.29-47.296-47.29z", "#C45FA0"],
786
- ["M717.962 543.153H542.047c-26.121 0-47.298 21.175-47.298 47.297v3.724c0 26.123 21.177 47.293 47.298 47.293h175.915c26.121 0 47.297-21.17 47.297-47.293v-3.724c0-26.122-21.176-47.297-47.297-47.297z", "#E5594F"],
787
- ["M689.268 198.849H513.355c-26.122 0-47.298 21.175-47.298 47.297v3.722c0 26.12 21.176 47.297 47.298 47.297h175.912c26.122 0 47.298-21.177 47.298-47.297v-3.722c0-26.122-21.175-47.297-47.297-47.297z", "#F0D043"],
788
- ["M757.789 353.081H261.17c-26.121 0-47.297 21.172-47.297 47.296v3.377c0 26.121 21.177 47.299 47.297 47.299h496.619c26.121 0 47.296-21.178 47.296-47.299v-3.377c0-26.125-21.175-47.296-47.296-47.296z", "#E5594F"],
789
- ["M762.638 726.225h-496.62c-26.12 0-47.294 21.18-47.294 47.301v3.377c0 26.12 21.174 47.3 47.294 47.3h496.62c26.122 0 47.296-21.18 47.296-47.3v-3.377c0-26.122-21.174-47.301-47.296-47.301z", "#6277BA"],
790
- ["M355.734 543.328H281.41c-26.122 0-47.297 21.17-47.297 47.293v3.378c0 26.118 21.175 47.297 47.297 47.297h74.324c26.123 0 47.296-21.179 47.296-47.297v-3.378c0-26.123-21.174-47.293-47.296-47.293z", "#F39A2B"],
791
- ["M334.85 248.006m-48.986 0a48.986 48.986 0 1 0 97.972 0 48.986 48.986 0 1 0-97.972 0Z", "#F39A2B"]
792
- ];
797
+ /**
798
+ * The Context emblem: the colourful document sheet, bundled rather than read
799
+ * off the harness primitives so the plugin's identity is self-contained.
800
+ *
801
+ * The single graphic source is the package-root `icon.svg` — the same file
802
+ * package.json `icon` hands to the Host's package-meta reader, which serves
803
+ * it to the Plugins page's package cards. The client bundle inlines the
804
+ * file's markup at build time (the `?raw` channel in tsdown.config.ts) and
805
+ * re-renders it at every requested size. Fixed fills, never `currentColor` —
806
+ * the sheet is deliberately polychrome on both light and dark chrome.
807
+ */
808
+ /** Everything between the file's `<svg>` tags: the sheet's strokes in paint order, whitespace-folded. */
809
+ const SHEET_MARKUP = icon_default.slice(icon_default.indexOf(">") + 1, icon_default.lastIndexOf("</svg>")).trim().replace(/>\s+</g, "><");
793
810
  /** The colourful document sheet at the requested square edge. */
794
811
  function ContextIcon({ size = 20, className }) {
795
812
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
@@ -799,10 +816,7 @@ window.__ModuleLoader__.load({
799
816
  className,
800
817
  "aria-hidden": "true",
801
818
  xmlns: "http://www.w3.org/2000/svg",
802
- children: SHEET_PATHS.map(([d, fill]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
803
- d,
804
- fill
805
- }, d))
819
+ dangerouslySetInnerHTML: { __html: SHEET_MARKUP }
806
820
  });
807
821
  }
808
822
  /**
@@ -1462,11 +1476,12 @@ window.__ModuleLoader__.load({
1462
1476
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
1463
1477
  }
1464
1478
  /**
1465
- * The OPTIONAL timing scalars (the generation split): a real non-negative
1466
- * number passes, anything else — including absence — reads as undefined so the
1467
- * field stays absent on the narrowed value (see `timingOf`).
1479
+ * The OPTIONAL timing scalars (the generation split's spans and block counts):
1480
+ * a real non-negative number passes, anything else — including absence — reads
1481
+ * as undefined so the field stays absent on the narrowed value (see
1482
+ * `timingOf`).
1468
1483
  */
1469
- function optMsNumOf(value) {
1484
+ function optNumOf(value) {
1470
1485
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1471
1486
  }
1472
1487
  /**
@@ -1490,7 +1505,10 @@ window.__ModuleLoader__.load({
1490
1505
  for (const k of [
1491
1506
  "reasoningMs",
1492
1507
  "textMs",
1493
- "toolArgMs"
1508
+ "toolArgMs",
1509
+ "reasoningBlocks",
1510
+ "textBlocks",
1511
+ "toolArgBlocks"
1494
1512
  ]) {
1495
1513
  const v = t[k];
1496
1514
  if (v !== void 0 && (typeof v !== "number" || !Number.isFinite(v) || v < 0)) return false;
@@ -1538,12 +1556,18 @@ window.__ModuleLoader__.load({
1538
1556
  toolCalls: msNumOf(data.toolCalls),
1539
1557
  tools
1540
1558
  };
1541
- const reasoning = optMsNumOf(data.reasoningMs);
1559
+ const reasoning = optNumOf(data.reasoningMs);
1542
1560
  if (reasoning !== void 0) totals.reasoningMs = reasoning;
1543
- const textMs = optMsNumOf(data.textMs);
1561
+ const reasoningBlocks = optNumOf(data.reasoningBlocks);
1562
+ if (reasoningBlocks !== void 0) totals.reasoningBlocks = reasoningBlocks;
1563
+ const textMs = optNumOf(data.textMs);
1544
1564
  if (textMs !== void 0) totals.textMs = textMs;
1545
- const toolArgMs = optMsNumOf(data.toolArgMs);
1565
+ const textBlocks = optNumOf(data.textBlocks);
1566
+ if (textBlocks !== void 0) totals.textBlocks = textBlocks;
1567
+ const toolArgMs = optNumOf(data.toolArgMs);
1546
1568
  if (toolArgMs !== void 0) totals.toolArgMs = toolArgMs;
1569
+ const toolArgBlocks = optNumOf(data.toolArgBlocks);
1570
+ if (toolArgBlocks !== void 0) totals.toolArgBlocks = toolArgBlocks;
1547
1571
  return totals;
1548
1572
  }
1549
1573
  /**
@@ -2021,12 +2045,11 @@ window.__ModuleLoader__.load({
2021
2045
  }
2022
2046
  /**
2023
2047
  * Map one raw durable event into the epoch content the browser renders. A
2024
- * `request/header` yields the full system prompt text (V0/V2 envelope) plus
2025
- * each tool's producer description and raw schema; a V3 `system/message`
2026
- * yields the prompt text alone (its tools live in the request header). Both
2027
- * mirror the host fold's per-entry guards — a null or primitive tool entry
2028
- * degrades to an unnamed row instead of throwing the read. Null when the
2029
- * envelope carries neither.
2048
+ * `request/header` yields each tool's producer description and raw schema; a
2049
+ * `system/message` yields the prompt text alone (its tools live in the
2050
+ * request header). Both mirror the host fold's per-entry guards — a null or
2051
+ * primitive tool entry degrades to an unnamed row instead of throwing the
2052
+ * read. Null when the event carries neither.
2030
2053
  */
2031
2054
  function headerContentOf(event) {
2032
2055
  const { type, data } = event;
@@ -2049,18 +2072,15 @@ window.__ModuleLoader__.load({
2049
2072
  schema: t
2050
2073
  });
2051
2074
  }
2052
- return {
2053
- ...typeof rawHeader.system === "string" && rawHeader.system !== "" ? { system: rawHeader.system } : {},
2054
- tools
2055
- };
2075
+ return { tools };
2056
2076
  }
2057
2077
  /**
2058
2078
  * The on-demand CONTENT fetch for the browser's System and Tools sections —
2059
2079
  * the lazy counterpart of the node fetcher above. One seq-anchored history
2060
2080
  * read off the requested seq returns the page holding that event (non-message
2061
2081
  * events ride the page verbatim); a `request/header` maps to the epoch's
2062
- * tools (and its V0/V2 system text), a `system/message` to a V3 prompt's
2063
- * text. Landed content caches per seq (history is immutable), and OLDER
2082
+ * tools, a `system/message` to the prompt's text. Landed content caches per
2083
+ * seq (history is immutable), and OLDER
2064
2084
  * epochs sharing the page cache for free — stepping back through epochs walks
2065
2085
  * the same pages. Undefined when no history face exists — the browser keeps a
2066
2086
  * metadata-only degradation instead.
@@ -2362,8 +2382,9 @@ window.__ModuleLoader__.load({
2362
2382
  * node at or before it carrying tokens, which is exactly the host fold's
2363
2383
  * "last nonempty surviving system" rule and therefore agrees with the
2364
2384
  * per-step `system` figure the fold recorded. Rows folded before `systems`
2365
- * existed carry none; the header epoch's envelope figure stands in for them
2366
- * (the pre-V3 wire shape).
2385
+ * existed carry none; the header epoch's own figure stands in for them (the
2386
+ * legacy wire shape older plugin builds served — current epochs fold
2387
+ * metadata-only, so the fallback only ever fires on those cached rows).
2367
2388
  */
2368
2389
  function systemAt(data, header, seq) {
2369
2390
  const systems = data.systems;
@@ -2468,6 +2489,16 @@ window.__ModuleLoader__.load({
2468
2489
  return kindOfTool(tool);
2469
2490
  }
2470
2491
  /**
2492
+ * Whether a settled call's arguments can yield an op at all: the file tools
2493
+ * plus `str_replace_editor`, whose purpose follows its `command`. The public
2494
+ * face of the gate {@link opsOfCall} short-circuits on — callers that decide
2495
+ * whether to parse or to KEEP a call's raw arguments ask here instead of
2496
+ * restating the tool list.
2497
+ */
2498
+ function opBearingTool(tool) {
2499
+ return tool === "str_replace_editor" || kindOfTool(tool) !== null;
2500
+ }
2501
+ /**
2471
2502
  * The operation's target path: the path-ish argument of read/write tools;
2472
2503
  * for searches the narrowing `path`, else the pattern itself (a pathless
2473
2504
  * grep/glob's target IS the pattern — the workspace-wide search text).
@@ -2606,7 +2637,7 @@ window.__ModuleLoader__.load({
2606
2637
  * resolve no target) rows nothing.
2607
2638
  */
2608
2639
  function opsOfCall(input) {
2609
- const args = parseCallArgs(input.argsRaw);
2640
+ const args = opBearingTool(input.tool) ? parseCallArgs(input.argsRaw) : null;
2610
2641
  const kind = kindOfCall(input.tool, args);
2611
2642
  if (kind === null) return [];
2612
2643
  const stamp = {
@@ -6072,8 +6103,8 @@ window.__ModuleLoader__.load({
6072
6103
  /**
6073
6104
  * The panel's row pipeline: range (by last-activity), then the heatmap's
6074
6105
  * picked day (sessions contributing to that day's merged ledger), then the
6075
- * search box (title or directory substring). Each stage keeps the rows it
6076
- * cannot prove out of the result — never an exception.
6106
+ * search box (title, directory, or last-message substring). Each stage keeps
6107
+ * the rows it cannot prove out of the result — never an exception.
6077
6108
  */
6078
6109
  function filterRows(rows, opts, now) {
6079
6110
  const start = rangeStartOf(opts.range, now);
@@ -6087,7 +6118,9 @@ window.__ModuleLoader__.load({
6087
6118
  if (query !== "") {
6088
6119
  const inTitle = row.title.toLowerCase().includes(query);
6089
6120
  const inCwd = row.cwd !== void 0 && row.cwd.toLowerCase().includes(query);
6090
- if (!inTitle && !inCwd) return false;
6121
+ const lastUser = row.timeline?.lastUser;
6122
+ const inLastUser = typeof lastUser === "string" && lastUser.toLowerCase().includes(query);
6123
+ if (!inTitle && !inCwd && !inLastUser) return false;
6091
6124
  }
6092
6125
  return true;
6093
6126
  });
@@ -6268,6 +6301,114 @@ window.__ModuleLoader__.load({
6268
6301
  return t("ov.time.d", { n: Math.floor(hours / 24) });
6269
6302
  }
6270
6303
  //#endregion
6304
+ //#region src/client/settingsJump.ts
6305
+ /**
6306
+ * Best-effort jump to this plugin's preferences. Two generations of chrome:
6307
+ * - dsh 0.1.7+ (Config-form generation): the preferences live on the
6308
+ * bundle's page of the Plugins main panel — the jump clicks the panel's
6309
+ * sidebar entry (its shipped aria-label), then follows the page with a
6310
+ * bounded poll for the bundle card's open control, found by the shipped,
6311
+ * locale-independent `data-plugin-package` list identity: the card lists
6312
+ * once the page's package load settles, and inside it the one button
6313
+ * that is not the enable switch is the title that opens the page. A
6314
+ * poll that never finds the card (still loading, plugin absent, or the
6315
+ * page already open on the detail view — the list unmounts there)
6316
+ * degrades to the card list, and every step stays silent.
6317
+ * - older lines: Settings → Plugins → Plugin configuration — the settings
6318
+ * panel keeps its open state inside the shell component (no plugin-facing
6319
+ * open face exists), so the jump drives the real chrome: click the
6320
+ * sidebar's settings trigger, then the Plugins section nav row, both
6321
+ * found by their shipped attributes and labels.
6322
+ * The last leg — the card itself — is ours: the module also carries a
6323
+ * short-lived in-bundle expand request that the settings-section card
6324
+ * consumes on mount, so that jump lands on the configuration already open.
6325
+ * EVERY step is individually guarded: a shell that doesn't match (different
6326
+ * layout, missing sidebar) degrades the whole jump to a silent no-op — this
6327
+ * path must never throw into the caller's render.
6328
+ */
6329
+ let requestedAt = 0;
6330
+ /** Raise a short-lived request for the plugin's settings card to mount expanded. */
6331
+ function requestCardExpand(now = Date.now()) {
6332
+ requestedAt = now;
6333
+ }
6334
+ /** Consume the request once; true only while it is still fresh (younger than maxAgeMs). */
6335
+ function consumeCardExpand(now = Date.now(), maxAgeMs = 5e3) {
6336
+ const fresh = requestedAt > 0 && now - requestedAt < maxAgeMs;
6337
+ requestedAt = 0;
6338
+ return fresh;
6339
+ }
6340
+ /**
6341
+ * The shipped Plugins label in both locales — the main-panel entry's
6342
+ * aria-label and the settings section's nav row text are the same word.
6343
+ */
6344
+ const PLUGIN_LABELS = /* @__PURE__ */ new Set(["Plugins", "插件"]);
6345
+ /** The bundle card's shipped list identity (locale-independent). */
6346
+ const PACKAGE_KEY = "dsh-context";
6347
+ /** How long the card poll follows the panel: 40 ticks at the 100ms tick, enough for the page's package load. */
6348
+ const POLL_TICKS = 40;
6349
+ /** Buttons the jump may operate on, in document order. */
6350
+ function buttonsOf(doc) {
6351
+ return [...doc.querySelectorAll("button")];
6352
+ }
6353
+ /** The Plugins main-panel entry, matched by its shipped aria-label. */
6354
+ function findPanelEntry(doc) {
6355
+ return buttonsOf(doc).find((b) => PLUGIN_LABELS.has(b.getAttribute("aria-label") ?? ""));
6356
+ }
6357
+ /**
6358
+ * The bundle card's open control: the one button inside the card that is not
6359
+ * the enable switch (the primitives switch carries `role="switch"`); the
6360
+ * title button's own aria-label is localized.
6361
+ */
6362
+ function findCardOpenControl(doc) {
6363
+ const card = doc.querySelector(`[data-plugin-package="${PACKAGE_KEY}"]`);
6364
+ if (card === null) return void 0;
6365
+ const open = [...card.querySelectorAll("button")].filter((b) => b.getAttribute("role") !== "switch");
6366
+ return open.length === 1 ? open[0] : void 0;
6367
+ }
6368
+ /** The sidebar's settings triggers: dialog semantics plus an expanded flag. */
6369
+ function findTriggers(doc) {
6370
+ return buttonsOf(doc).filter((b) => b.getAttribute("aria-haspopup") === "dialog" && b.hasAttribute("aria-expanded"));
6371
+ }
6372
+ /** The Plugins section's nav row, matched by its shipped label text. */
6373
+ function findSectionRow(doc) {
6374
+ return buttonsOf(doc).find((b) => PLUGIN_LABELS.has(b.textContent.trim()));
6375
+ }
6376
+ function openPluginSettings(doc = document, schedule = (run, ms) => {
6377
+ window.setTimeout(run, ms);
6378
+ }) {
6379
+ try {
6380
+ const panel = findPanelEntry(doc);
6381
+ if (panel !== void 0) {
6382
+ panel.click();
6383
+ const poll = (left) => {
6384
+ try {
6385
+ const open = findCardOpenControl(doc);
6386
+ if (open !== void 0) {
6387
+ open.click();
6388
+ return;
6389
+ }
6390
+ } catch {}
6391
+ if (left > 0) schedule(() => {
6392
+ poll(left - 1);
6393
+ }, 100);
6394
+ };
6395
+ schedule(() => {
6396
+ poll(POLL_TICKS);
6397
+ }, 200);
6398
+ return;
6399
+ }
6400
+ const triggers = findTriggers(doc);
6401
+ if (triggers.find((b) => b.getAttribute("aria-expanded") === "true") === void 0 && triggers.length === 0) return;
6402
+ triggers.find((b) => b.getAttribute("aria-expanded") !== "true")?.click();
6403
+ requestCardExpand();
6404
+ schedule(() => {
6405
+ try {
6406
+ findSectionRow(doc)?.click();
6407
+ } catch {}
6408
+ }, 80);
6409
+ } catch {}
6410
+ }
6411
+ //#endregion
6271
6412
  //#region src/client/balance.ts
6272
6413
  const BALANCE_ROUTE = "/api/dsh-context/balance";
6273
6414
  /** Where the last live figure is remembered across opens, per browser. */
@@ -6498,51 +6639,57 @@ window.__ModuleLoader__.load({
6498
6639
  date.setDate(date.getDate() + delta);
6499
6640
  return dayKeyOf(date.getTime());
6500
6641
  }
6501
- /** The Monday (local) of the week containing `key` — the heatmap's column anchor. */
6502
- function mondayOfWeek(key) {
6642
+ /** The Sunday (local) of the week containing `key` — the heatmap's column anchor. */
6643
+ function sundayOfWeek(key) {
6503
6644
  if (!DAY_KEY_RE.test(key)) return null;
6504
6645
  const y = Number(key.slice(0, 4));
6505
6646
  const m = Number(key.slice(5, 7));
6506
6647
  const d = Number(key.slice(8, 10));
6507
6648
  const date = new Date(y, m - 1, d);
6508
6649
  if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) return null;
6509
- return shiftDayKey(key, -((date.getDay() + 6) % 7));
6650
+ return shiftDayKey(key, -date.getDay());
6510
6651
  }
6511
6652
  //#endregion
6512
6653
  //#region src/client/components/heatmap.tsx
6513
6654
  /**
6514
6655
  * The Context Dashboard's activity heatmap: a GitHub-style contribution grid
6515
- * (weeks as columns, Monday-first weekdays as rows, a month label over the
6516
- * column where each month begins) over the merged daily
6517
- * ledger (overview.ts). Cell depth is the day's billed-token share of the
6518
- * window's maximum, in four steps; a day with data is a button whose click
6656
+ * (weeks as columns, Sunday-first weekdays as rows, a month label over each
6657
+ * column where a month begins — the window's opening column always labeled —
6658
+ * and a Less→More key for the depth steps) over the merged daily
6659
+ * ledger (overview.ts). Cell depth is the selected metric's share of the
6660
+ * window's maximum — distinct active sessions or settlement steps; a session
6661
+ * can spread its steps thinly across many days while another day stacks a
6662
+ * run, so each metric scales on its OWN maximum — in four steps; a day with
6663
+ * data is a button whose click
6519
6664
  * pins the session list to that day (click again to release). Cells tip
6520
6665
  * through the harness's own Tooltip primitive (instant on hover; native
6521
- * `title` lags a second behind), the bubble carrying the date and the day's
6522
- * active sessions. The grid is computed from the injected `today` key, so
6666
+ * `title` lags a second behind), the bubble carrying the date, the day's
6667
+ * active sessions, and its steps. The grid is computed from the injected
6668
+ * `today` key, so
6523
6669
  * the layout is deterministic in tests and follows the browser's local
6524
6670
  * calendar at runtime.
6525
6671
  */
6526
6672
  /**
6527
6673
  * Lay out the grid: `weeks` columns ending at today's week, each seven
6528
- * Monday-first cells. Returns null when `today` (or its week math) fails —
6529
- * a corrupt injected key degrades the card to its empty note.
6674
+ * Sunday-first cells (row 0 = Sunday). Returns null when `today` (or its
6675
+ * week math) fails — a corrupt injected key degrades the card to its empty
6676
+ * note.
6530
6677
  */
6531
6678
  function gridOf(today, weeks) {
6532
- const lastMonday = mondayOfWeek(today);
6533
- if (lastMonday === null || weeks < 1) return null;
6534
- const firstMonday = shiftDayKey(lastMonday, -7 * (weeks - 1));
6535
- if (firstMonday === null) return null;
6679
+ const lastSunday = sundayOfWeek(today);
6680
+ if (lastSunday === null || weeks < 1) return null;
6681
+ const firstSunday = shiftDayKey(lastSunday, -7 * (weeks - 1));
6682
+ if (firstSunday === null) return null;
6536
6683
  const columns = [];
6537
6684
  for (let w = 0; w < weeks; w++) {
6538
- const monday = shiftDayKey(firstMonday, 7 * w);
6539
- /* v8 ignore next -- monday is bounded by firstMonday and lastMonday (both
6685
+ const sunday = shiftDayKey(firstSunday, 7 * w);
6686
+ /* v8 ignore next -- sunday is bounded by firstSunday and lastSunday (both
6540
6687
  * proven valid), so it can never leave the representable range; kept as a
6541
6688
  * guard so a future shiftDayKey change cannot leak a null into the grid. */
6542
- if (monday === null) return null;
6689
+ if (sunday === null) return null;
6543
6690
  const column = [];
6544
6691
  for (let d = 0; d < 7; d++) {
6545
- const key = shiftDayKey(monday, d);
6692
+ const key = shiftDayKey(sunday, d);
6546
6693
  if (key === null) return null;
6547
6694
  column.push({
6548
6695
  key,
@@ -6567,88 +6714,116 @@ window.__ModuleLoader__.load({
6567
6714
  const { t } = kit;
6568
6715
  return function Heatmap(props) {
6569
6716
  const weeks = props.weeks ?? 8;
6717
+ const metric = props.metric ?? "steps";
6570
6718
  const columns = gridOf(props.today, weeks);
6571
6719
  if (columns === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6572
6720
  className: "lc-empty",
6573
6721
  children: t("ov.heat.empty")
6574
6722
  });
6575
6723
  const byKey = props.days;
6576
- let max = 0;
6724
+ let maxSessions = 0;
6725
+ let maxSteps = 0;
6577
6726
  let any = false;
6578
6727
  for (const column of columns) for (const cell of column) {
6579
6728
  const entry = byKey[cell.key];
6580
6729
  if (entry === void 0 || entry.tokens <= 0 && entry.requests <= 0) continue;
6581
6730
  cell.entry = entry;
6582
6731
  any = true;
6583
- if (entry.tokens > max) max = entry.tokens;
6732
+ if (entry.sessions > maxSessions) maxSessions = entry.sessions;
6733
+ if (entry.requests > maxSteps) maxSteps = entry.requests;
6584
6734
  }
6585
6735
  if (!any) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6586
6736
  className: "lc-empty",
6587
6737
  children: t("ov.heat.empty")
6588
6738
  });
6589
- const weekdayLabels = [
6590
- t("ov.heat.wd.1"),
6591
- t("ov.heat.wd.3"),
6592
- t("ov.heat.wd.5")
6593
- ];
6739
+ const max = metric === "sessions" ? maxSessions : maxSteps;
6740
+ const metricValueOf = (entry) => metric === "sessions" ? entry.sessions : entry.requests;
6741
+ const weekdayLabels = {
6742
+ 1: t("ov.heat.wd.1"),
6743
+ 3: t("ov.heat.wd.3"),
6744
+ 5: t("ov.heat.wd.5")
6745
+ };
6594
6746
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6595
- className: "lc-heat",
6596
- role: "group",
6597
- "aria-label": t("ov.heat.title"),
6598
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6599
- className: "lc-heat-wds",
6747
+ className: "lc-heat-wrap",
6748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6749
+ className: "lc-heat",
6750
+ role: "group",
6751
+ "aria-label": t("ov.heat.title"),
6752
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6753
+ className: "lc-heat-wds",
6754
+ "aria-hidden": "true",
6755
+ children: [
6756
+ 0,
6757
+ 1,
6758
+ 2,
6759
+ 3,
6760
+ 4,
6761
+ 5,
6762
+ 6
6763
+ ].map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6764
+ className: "lc-heat-wd",
6765
+ children: weekdayLabels[row] ?? ""
6766
+ }, row))
6767
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6768
+ className: "lc-heat-cols",
6769
+ children: columns.map((column, wi) => {
6770
+ const opens = wi === 0 || column[0].key.slice(0, 7) !== column[6].key.slice(0, 7) || column[0].key.slice(8, 10) === "01";
6771
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6772
+ className: "lc-heat-col",
6773
+ children: [opens && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6774
+ className: "lc-heat-mon",
6775
+ "aria-hidden": "true",
6776
+ children: t("ov.heat.mon." + column[6].key.slice(5, 7))
6777
+ }), column.map((cell) => {
6778
+ if (cell.future) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6779
+ className: "lc-heat-cell lc-heat-future",
6780
+ "aria-hidden": "true"
6781
+ }, cell.key);
6782
+ const level = cell.entry === void 0 ? 0 : levelOf(metricValueOf(cell.entry), max);
6783
+ if (cell.entry === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6784
+ label: cell.key,
6785
+ side: "top",
6786
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "lc-heat-cell lc-heat-0" })
6787
+ }, cell.key);
6788
+ const picked = props.selected === cell.key;
6789
+ const label = `${cell.key}\n${t("ov.heat.sessions", { n: cell.entry.sessions })}\n${t("ov.heat.steps", { n: cell.entry.requests })}`;
6790
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6791
+ label,
6792
+ side: "top",
6793
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6794
+ type: "button",
6795
+ className: `lc-heat-cell lc-heat-${String(level)}${picked ? " lc-heat-on" : ""}`,
6796
+ "aria-label": label,
6797
+ "aria-pressed": picked,
6798
+ onClick: () => {
6799
+ if (props.onSelect !== void 0) props.onSelect(picked ? null : cell.key);
6800
+ }
6801
+ })
6802
+ }, cell.key);
6803
+ })]
6804
+ }, wi);
6805
+ })
6806
+ })]
6807
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6808
+ className: "lc-heat-legend",
6600
6809
  "aria-hidden": "true",
6601
6810
  children: [
6602
- 0,
6603
- 1,
6604
- 2,
6605
- 3,
6606
- 4,
6607
- 5,
6608
- 6
6609
- ].map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6610
- className: "lc-heat-wd",
6611
- children: row === 1 ? weekdayLabels[0] : row === 3 ? weekdayLabels[1] : row === 5 ? weekdayLabels[2] : ""
6612
- }, row))
6613
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6614
- className: "lc-heat-cols",
6615
- children: columns.map((column, wi) => {
6616
- const opens = column[0].key.slice(0, 7) !== column[6].key.slice(0, 7) || column[0].key.slice(8, 10) === "01";
6617
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6618
- className: "lc-heat-col",
6619
- children: [opens && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6620
- className: "lc-heat-mon",
6621
- "aria-hidden": "true",
6622
- children: t("ov.heat.mon." + column[6].key.slice(5, 7))
6623
- }), column.map((cell) => {
6624
- if (cell.future) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6625
- className: "lc-heat-cell lc-heat-future",
6626
- "aria-hidden": "true"
6627
- }, cell.key);
6628
- const level = cell.entry === void 0 ? 0 : levelOf(cell.entry.tokens, max);
6629
- if (cell.entry === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6630
- label: cell.key,
6631
- side: "top",
6632
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "lc-heat-cell lc-heat-0" })
6633
- }, cell.key);
6634
- const picked = props.selected === cell.key;
6635
- const label = `${cell.key}\n${t("ov.heat.sessions", { n: cell.entry.sessions })}`;
6636
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
6637
- label,
6638
- side: "top",
6639
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6640
- type: "button",
6641
- className: `lc-heat-cell lc-heat-${String(level)}${picked ? " lc-heat-on" : ""}`,
6642
- "aria-label": label,
6643
- "aria-pressed": picked,
6644
- onClick: () => {
6645
- if (props.onSelect !== void 0) props.onSelect(picked ? null : cell.key);
6646
- }
6647
- })
6648
- }, cell.key);
6649
- })]
6650
- }, wi);
6651
- })
6811
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6812
+ className: "lc-heat-key",
6813
+ children: t("ov.heat.less")
6814
+ }),
6815
+ [
6816
+ 0,
6817
+ 1,
6818
+ 2,
6819
+ 3,
6820
+ 4
6821
+ ].map((level) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `lc-heat-cell lc-heat-${String(level)}` }, level)),
6822
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6823
+ className: "lc-heat-key",
6824
+ children: t("ov.heat.more")
6825
+ })
6826
+ ]
6652
6827
  })]
6653
6828
  });
6654
6829
  };
@@ -6895,11 +7070,11 @@ window.__ModuleLoader__.load({
6895
7070
  * opening one log.
6896
7071
  *
6897
7072
  * The body is a 3:7 column pair: the insight column (the KPI 2×3 block over
6898
- * the activity heatmap) beside the session column (search, group chips, and
6899
- * the card grid); the heatmap keeps its own fixed 8-week window and PINs the
6900
- * list to a picked day (the panel's drill-down gesture). A session card
6901
- * click jumps to that session through the harness's own `sessions.open` and
6902
- * closes the panel.
7073
+ * the activity heatmap, then the preferences entry row) beside the session
7074
+ * column (search, group chips, and the card grid); the heatmap keeps its own
7075
+ * fixed 8-week window and PINs the list to a picked day (the panel's
7076
+ * drill-down gesture). A session card click jumps to that session through
7077
+ * the harness's own `sessions.open` and closes the panel.
6903
7078
  */
6904
7079
  const RANGES = [
6905
7080
  "24h",
@@ -6912,6 +7087,8 @@ window.__ModuleLoader__.load({
6912
7087
  "tokens",
6913
7088
  "context"
6914
7089
  ];
7090
+ /** The heatmap's depth metrics, in toggle order (steps is the default). */
7091
+ const METRICS = ["sessions", "steps"];
6915
7092
  function makeOverviewPanel(ctx, kit) {
6916
7093
  const { t, fmtDuration } = kit;
6917
7094
  const Heatmap = makeHeatmap(kit);
@@ -6933,6 +7110,7 @@ window.__ModuleLoader__.load({
6933
7110
  const [query, setQuery] = (0, react.useState)("");
6934
7111
  const [group, setGroup] = (0, react.useState)(null);
6935
7112
  const [sort, setSort] = (0, react.useState)("recent");
7113
+ const [metric, setMetric] = (0, react.useState)("steps");
6936
7114
  const [page, setPage] = (0, react.useState)(0);
6937
7115
  const close = () => {
6938
7116
  overviewStore.set(false);
@@ -7030,134 +7208,168 @@ window.__ModuleLoader__.load({
7030
7208
  className: "lc-ov-body",
7031
7209
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7032
7210
  className: "lc-ov-left",
7033
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7034
- className: "lc-ov-kpis",
7035
- children: [
7036
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7037
- className: "lc-stat lc-ov-kpi",
7038
- children: [
7039
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7040
- className: "lc-stat-label",
7041
- children: t("ov.kpi.sessions")
7042
- }),
7043
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7044
- className: "lc-stat-value",
7045
- children: kpi.sessions
7046
- }),
7047
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7048
- className: "lc-stat-sub",
7049
- children: t("ov.kpi.ofTotal", { n: kpi.listed })
7050
- })
7051
- ]
7052
- }),
7053
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7054
- className: "lc-stat lc-ov-kpi",
7055
- children: [
7056
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7057
- className: "lc-stat-label",
7058
- children: t("ov.kpi.tokens")
7059
- }),
7060
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7061
- className: "lc-stat-value",
7062
- children: fmt(kpi.tokens)
7063
- }),
7064
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7065
- className: "lc-stat-sub",
7066
- children: [
7067
- t("stats.turns"),
7068
- " ",
7069
- fmt(kpi.turns)
7070
- ]
7071
- })
7072
- ]
7073
- }),
7074
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7075
- className: "lc-stat lc-ov-kpi",
7076
- children: [
7077
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7078
- className: "lc-stat-label",
7079
- children: t("stats.cost")
7080
- }),
7081
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7082
- className: "lc-stat-value",
7083
- children: kpi.cost === null ? "—" : formatCost(kpi.cost, currency)
7084
- }),
7085
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7086
- className: "lc-stat-sub",
7087
- children: t("ov.kpi.sessionsSub", { n: kpi.costSessions })
7088
- })
7089
- ]
7090
- }),
7091
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7092
- className: "lc-stat lc-ov-kpi",
7093
- children: [
7094
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7095
- className: "lc-stat-label",
7096
- children: t("stats.cacheHit")
7097
- }),
7098
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7099
- className: "lc-stat-value",
7100
- children: kpi.cacheHit === null ? "—" : kpi.cacheHit + "%"
7101
- }),
7102
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7103
- className: "lc-stat-sub",
7104
- children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
7105
- })
7106
- ]
7107
- }),
7108
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7109
- className: "lc-stat lc-ov-kpi",
7110
- children: [
7111
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7112
- className: "lc-stat-label",
7113
- children: t("stats.toolCalls")
7114
- }),
7115
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7116
- className: "lc-stat-value",
7117
- children: fmt(kpi.toolCalls)
7118
- }),
7119
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7120
- className: "lc-stat-sub",
7121
- children: t("ov.kpi.toolSub", { dur: fmtDuration(kpi.toolsMs) })
7122
- })
7123
- ]
7124
- }),
7125
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7126
- className: "lc-stat lc-ov-kpi",
7127
- children: [
7128
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7129
- className: "lc-stat-label",
7130
- children: t("timing.total")
7131
- }),
7132
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7133
- className: "lc-stat-value",
7134
- children: fmtDuration(kpi.wallMs)
7135
- }),
7136
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7137
- className: "lc-stat-sub",
7138
- children: t("ov.kpi.wallSub", { n: fmt(kpi.calls) })
7139
- })
7140
- ]
7141
- })
7142
- ]
7143
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7144
- className: "lc-card lc-ov-heat-card",
7145
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7146
- className: "lc-card-title",
7147
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7148
- className: "lc-card-title-text",
7149
- children: t("ov.heat.title")
7211
+ children: [
7212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7213
+ className: "lc-ov-kpis",
7214
+ children: [
7215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7216
+ className: "lc-stat lc-ov-kpi",
7217
+ children: [
7218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7219
+ className: "lc-stat-label",
7220
+ children: t("ov.kpi.sessions")
7221
+ }),
7222
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7223
+ className: "lc-stat-value",
7224
+ children: kpi.sessions
7225
+ }),
7226
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7227
+ className: "lc-stat-sub",
7228
+ children: t("ov.kpi.ofTotal", { n: kpi.listed })
7229
+ })
7230
+ ]
7231
+ }),
7232
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7233
+ className: "lc-stat lc-ov-kpi",
7234
+ children: [
7235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7236
+ className: "lc-stat-label",
7237
+ children: t("ov.kpi.tokens")
7238
+ }),
7239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7240
+ className: "lc-stat-value",
7241
+ children: fmt(kpi.tokens)
7242
+ }),
7243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7244
+ className: "lc-stat-sub",
7245
+ children: [
7246
+ t("stats.turns"),
7247
+ " ",
7248
+ fmt(kpi.turns)
7249
+ ]
7250
+ })
7251
+ ]
7252
+ }),
7253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7254
+ className: "lc-stat lc-ov-kpi",
7255
+ children: [
7256
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7257
+ className: "lc-stat-label",
7258
+ children: t("stats.cost")
7259
+ }),
7260
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7261
+ className: "lc-stat-value",
7262
+ children: kpi.cost === null ? "—" : formatCost(kpi.cost, currency)
7263
+ }),
7264
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7265
+ className: "lc-stat-sub",
7266
+ children: t("ov.kpi.sessionsSub", { n: kpi.costSessions })
7267
+ })
7268
+ ]
7269
+ }),
7270
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7271
+ className: "lc-stat lc-ov-kpi",
7272
+ children: [
7273
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7274
+ className: "lc-stat-label",
7275
+ children: t("stats.cacheHit")
7276
+ }),
7277
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7278
+ className: "lc-stat-value",
7279
+ children: kpi.cacheHit === null ? "—" : kpi.cacheHit + "%"
7280
+ }),
7281
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7282
+ className: "lc-stat-sub",
7283
+ children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
7284
+ })
7285
+ ]
7286
+ }),
7287
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7288
+ className: "lc-stat lc-ov-kpi",
7289
+ children: [
7290
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7291
+ className: "lc-stat-label",
7292
+ children: t("stats.toolCalls")
7293
+ }),
7294
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7295
+ className: "lc-stat-value",
7296
+ children: fmt(kpi.toolCalls)
7297
+ }),
7298
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7299
+ className: "lc-stat-sub",
7300
+ children: t("ov.kpi.toolSub", { dur: fmtDuration(kpi.toolsMs) })
7301
+ })
7302
+ ]
7303
+ }),
7304
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7305
+ className: "lc-stat lc-ov-kpi",
7306
+ children: [
7307
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7308
+ className: "lc-stat-label",
7309
+ children: t("timing.total")
7310
+ }),
7311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7312
+ className: "lc-stat-value",
7313
+ children: fmtDuration(kpi.wallMs)
7314
+ }),
7315
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7316
+ className: "lc-stat-sub",
7317
+ children: t("ov.kpi.wallSub", { n: fmt(kpi.calls) })
7318
+ })
7319
+ ]
7320
+ })
7321
+ ]
7322
+ }),
7323
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7324
+ className: "lc-card lc-ov-heat-card",
7325
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7326
+ className: "lc-card-title",
7327
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7328
+ className: "lc-card-title-text",
7329
+ children: t("ov.heat.title")
7330
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7331
+ className: "lc-heat-ctl",
7332
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7333
+ className: "lc-card-sub",
7334
+ children: t("ov.heat.sub")
7335
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7336
+ className: "lc-gran",
7337
+ role: "group",
7338
+ "aria-label": t("ov.heat.metric"),
7339
+ children: METRICS.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7340
+ type: "button",
7341
+ className: "lc-gran-btn" + (metric === m ? " lc-gran-on" : ""),
7342
+ onClick: () => {
7343
+ setMetric(m);
7344
+ },
7345
+ children: t("ov.heat.metric." + m)
7346
+ }, m))
7347
+ })]
7348
+ })]
7349
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Heatmap, {
7350
+ days,
7351
+ metric,
7352
+ selected: day,
7353
+ onSelect: setDay,
7354
+ today: todayKey()
7355
+ })]
7356
+ }),
7357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7358
+ type: "button",
7359
+ className: "lc-ov-settings",
7360
+ onClick: () => {
7361
+ openPluginSettings();
7362
+ close();
7363
+ },
7364
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7365
+ className: "lc-ov-settings-label",
7366
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSettings, { size: 14 }), t("plugin.settings")]
7150
7367
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7151
- className: "lc-card-sub",
7152
- children: t("ov.heat.sub")
7368
+ className: "lc-ov-settings-hint",
7369
+ children: t("plugin.settingsOpen")
7153
7370
  })]
7154
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Heatmap, {
7155
- days,
7156
- selected: day,
7157
- onSelect: setDay,
7158
- today: todayKey()
7159
- })]
7160
- })]
7371
+ })
7372
+ ]
7161
7373
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7162
7374
  className: "lc-ov-right",
7163
7375
  children: [
@@ -7295,71 +7507,19 @@ window.__ModuleLoader__.load({
7295
7507
  return cost === null ? "—" : formatCost(cost, currency);
7296
7508
  }
7297
7509
  //#endregion
7298
- //#region src/client/settingsJump.ts
7299
- /**
7300
- * Best-effort jump to this plugin's settings page (Settings → Plugins →
7301
- * Plugin configuration). The harness settings panel keeps its open state
7302
- * inside the shell component — no plugin-facing open face exists — so the
7303
- * jump drives the real chrome: click the sidebar's settings trigger, then
7304
- * the Plugins section nav row, both found by their shipped attributes and
7305
- * labels. EVERY step is individually guarded: a shell that doesn't match
7306
- * (older host, different layout, missing sidebar) degrades the whole jump
7307
- * to a silent no-op — this path must never throw into the caller's render.
7308
- *
7309
- * The last leg — the card itself — is ours: the module also carries a
7310
- * short-lived in-bundle expand request that the plugin's settings card
7311
- * consumes on mount, so the jump lands on the configuration already open.
7312
- */
7313
- let requestedAt = 0;
7314
- /** Raise a short-lived request for the plugin's settings card to mount expanded. */
7315
- function requestCardExpand(now = Date.now()) {
7316
- requestedAt = now;
7317
- }
7318
- /** Consume the request once; true only while it is still fresh (younger than maxAgeMs). */
7319
- function consumeCardExpand(now = Date.now(), maxAgeMs = 5e3) {
7320
- const fresh = requestedAt > 0 && now - requestedAt < maxAgeMs;
7321
- requestedAt = 0;
7322
- return fresh;
7323
- }
7324
- /** The Plugins settings section's shipped nav labels (en/zh locales). */
7325
- const SECTION_LABELS = /* @__PURE__ */ new Set(["Plugins", "插件"]);
7326
- /** Buttons the jump may operate on, in document order. */
7327
- function buttonsOf(doc) {
7328
- return [...doc.querySelectorAll("button")];
7329
- }
7330
- /** The sidebar's settings triggers: dialog semantics plus an expanded flag. */
7331
- function findTriggers(doc) {
7332
- return buttonsOf(doc).filter((b) => b.getAttribute("aria-haspopup") === "dialog" && b.hasAttribute("aria-expanded"));
7333
- }
7334
- /** The Plugins section's nav row, matched by its shipped label. */
7335
- function findSectionRow(doc) {
7336
- return buttonsOf(doc).find((b) => SECTION_LABELS.has(b.textContent.trim()));
7337
- }
7338
- function openPluginSettings(doc = document, schedule = (run, ms) => {
7339
- window.setTimeout(run, ms);
7340
- }) {
7341
- try {
7342
- const triggers = findTriggers(doc);
7343
- if (triggers.find((b) => b.getAttribute("aria-expanded") === "true") === void 0 && triggers.length === 0) return;
7344
- triggers.find((b) => b.getAttribute("aria-expanded") !== "true")?.click();
7345
- requestCardExpand();
7346
- schedule(() => {
7347
- try {
7348
- findSectionRow(doc)?.click();
7349
- } catch {}
7350
- }, 80);
7351
- } catch {}
7352
- }
7353
- //#endregion
7354
7510
  //#region src/client/components/settingsCard.tsx
7355
7511
  /**
7356
- * The dsh-context card in Settings → Plugins → Plugin configuration, registered on the framework's `settings.plugin.item` slot keyed on
7357
- * the
7358
- * Host-served `dsh-context` settings namespace — the section itself supplies nothing; it renders nothing while the namespace is
7359
- * unavailable
7360
- * (a deployment without the Host half, or a remote browser, shows no trace).
7361
- * Mounts expanded when the Plugin Info card's "Open in Settings" jump left a
7362
- * fresh expand request (settingsJump.ts), scrolling itself into view.
7512
+ * The dsh-context preference cards — two seats over the same six rows. The
7513
+ * settings-section card (`settings.plugin.item`, the older harness lines)
7514
+ * renders a collapsible list item in Settings → Plugins → Plugin
7515
+ * configuration; the Plugins-page card (`plugins.bundle.config`, the
7516
+ * Config-form generation) renders the rows flat inside the section chrome the
7517
+ * Plugins page draws for the bundle. Both are keyed on the Host-served
7518
+ * `dsh-context` namespace and render nothing while it is unavailable (a
7519
+ * deployment without the Host half, or a remote browser, shows no trace).
7520
+ * The settings-section card mounts expanded when the Plugin Info card's
7521
+ * "Open plugin settings" jump left a fresh expand request (settingsJump.ts),
7522
+ * scrolling itself into view.
7363
7523
  */
7364
7524
  function PrefRow(props) {
7365
7525
  const [open, setOpen] = (0, react.useState)(false);
@@ -7396,6 +7556,124 @@ window.__ModuleLoader__.load({
7396
7556
  })]
7397
7557
  });
7398
7558
  }
7559
+ /** The six preference rows shared by both seats. */
7560
+ function PreferenceRows(props) {
7561
+ const { t, state, set } = props;
7562
+ const disabled = state.status !== "ready" || !state.writable;
7563
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7564
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7565
+ label: t("settings.placement"),
7566
+ value: state.placement,
7567
+ disabled,
7568
+ options: [
7569
+ {
7570
+ id: "all",
7571
+ label: t("placement.all")
7572
+ },
7573
+ {
7574
+ id: "tab",
7575
+ label: t("placement.tab")
7576
+ },
7577
+ {
7578
+ id: "sidebar",
7579
+ label: t("placement.sidebar")
7580
+ }
7581
+ ],
7582
+ onPick: (id) => {
7583
+ set?.("defaultPlacement", id);
7584
+ }
7585
+ }),
7586
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7587
+ label: t("settings.insightsEntry"),
7588
+ value: state.insightsEntry,
7589
+ disabled,
7590
+ options: [{
7591
+ id: "show",
7592
+ label: t("insightsEntry.show")
7593
+ }, {
7594
+ id: "hide",
7595
+ label: t("insightsEntry.hide")
7596
+ }],
7597
+ onPick: (id) => {
7598
+ set?.("insightsEntry", id);
7599
+ }
7600
+ }),
7601
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7602
+ label: t("settings.gran"),
7603
+ value: state.granularity,
7604
+ disabled,
7605
+ options: [{
7606
+ id: "step",
7607
+ label: t("gran.step")
7608
+ }, {
7609
+ id: "turn",
7610
+ label: t("gran.turn")
7611
+ }],
7612
+ onPick: (id) => {
7613
+ set?.("defaultGranularity", id);
7614
+ }
7615
+ }),
7616
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7617
+ label: t("settings.mode"),
7618
+ value: state.mode,
7619
+ disabled,
7620
+ options: [{
7621
+ id: "total",
7622
+ label: t("gran.total")
7623
+ }, {
7624
+ id: "delta",
7625
+ label: t("gran.delta")
7626
+ }],
7627
+ onPick: (id) => {
7628
+ set?.("defaultTrendMode", id);
7629
+ }
7630
+ }),
7631
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7632
+ label: t("settings.toolSort"),
7633
+ value: state.toolSort,
7634
+ disabled,
7635
+ options: [
7636
+ {
7637
+ id: "size",
7638
+ label: t("tool.sort.size")
7639
+ },
7640
+ {
7641
+ id: "count",
7642
+ label: t("tool.sort.count")
7643
+ },
7644
+ {
7645
+ id: "name",
7646
+ label: t("tool.sort.name")
7647
+ }
7648
+ ],
7649
+ onPick: (id) => {
7650
+ set?.("defaultToolSort", id);
7651
+ }
7652
+ }),
7653
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7654
+ label: t("settings.fileSort"),
7655
+ value: state.fileSort,
7656
+ disabled,
7657
+ options: [
7658
+ {
7659
+ id: "count",
7660
+ label: t("files.sort.count")
7661
+ },
7662
+ {
7663
+ id: "latest",
7664
+ label: t("files.sort.latest")
7665
+ },
7666
+ {
7667
+ id: "path",
7668
+ label: t("files.sort.path")
7669
+ }
7670
+ ],
7671
+ onPick: (id) => {
7672
+ set?.("defaultFileSort", id);
7673
+ }
7674
+ })
7675
+ ] });
7676
+ }
7399
7677
  function makeSettingsCard(kit) {
7400
7678
  const { t } = kit;
7401
7679
  return function SettingsCard(props) {
@@ -7410,7 +7688,6 @@ window.__ModuleLoader__.load({
7410
7688
  }, []);
7411
7689
  const state = typeof props.useContextSettings === "function" ? props.useContextSettings((s) => s) : void 0;
7412
7690
  if (state === void 0 || state.status === "unavailable") return null;
7413
- const disabled = state.status !== "ready" || !state.writable;
7414
7691
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
7415
7692
  ref: itemRef,
7416
7693
  className: "lc-settings-card" + (open ? " lc-settings-open" : ""),
@@ -7434,128 +7711,44 @@ window.__ModuleLoader__.load({
7434
7711
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "lc-settings-chevron" })]
7435
7712
  }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7436
7713
  className: "lc-settings-body",
7437
- children: [
7438
- !state.writable && state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7439
- className: "lc-settings-note",
7440
- role: "status",
7441
- children: t("settings.readOnly")
7442
- }) : null,
7443
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7444
- label: t("settings.placement"),
7445
- value: state.placement,
7446
- disabled,
7447
- options: [
7448
- {
7449
- id: "all",
7450
- label: t("placement.all")
7451
- },
7452
- {
7453
- id: "tab",
7454
- label: t("placement.tab")
7455
- },
7456
- {
7457
- id: "sidebar",
7458
- label: t("placement.sidebar")
7459
- }
7460
- ],
7461
- onPick: (id) => {
7462
- props.set?.("defaultPlacement", id);
7463
- }
7464
- }),
7465
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7466
- label: t("settings.insightsEntry"),
7467
- value: state.insightsEntry,
7468
- disabled,
7469
- options: [{
7470
- id: "show",
7471
- label: t("insightsEntry.show")
7472
- }, {
7473
- id: "hide",
7474
- label: t("insightsEntry.hide")
7475
- }],
7476
- onPick: (id) => {
7477
- props.set?.("insightsEntry", id);
7478
- }
7479
- }),
7480
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7481
- label: t("settings.gran"),
7482
- value: state.granularity,
7483
- disabled,
7484
- options: [{
7485
- id: "step",
7486
- label: t("gran.step")
7487
- }, {
7488
- id: "turn",
7489
- label: t("gran.turn")
7490
- }],
7491
- onPick: (id) => {
7492
- props.set?.("defaultGranularity", id);
7493
- }
7494
- }),
7495
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7496
- label: t("settings.mode"),
7497
- value: state.mode,
7498
- disabled,
7499
- options: [{
7500
- id: "total",
7501
- label: t("gran.total")
7502
- }, {
7503
- id: "delta",
7504
- label: t("gran.delta")
7505
- }],
7506
- onPick: (id) => {
7507
- props.set?.("defaultTrendMode", id);
7508
- }
7509
- }),
7510
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7511
- label: t("settings.toolSort"),
7512
- value: state.toolSort,
7513
- disabled,
7514
- options: [
7515
- {
7516
- id: "size",
7517
- label: t("tool.sort.size")
7518
- },
7519
- {
7520
- id: "count",
7521
- label: t("tool.sort.count")
7522
- },
7523
- {
7524
- id: "name",
7525
- label: t("tool.sort.name")
7526
- }
7527
- ],
7528
- onPick: (id) => {
7529
- props.set?.("defaultToolSort", id);
7530
- }
7531
- }),
7532
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
7533
- label: t("settings.fileSort"),
7534
- value: state.fileSort,
7535
- disabled,
7536
- options: [
7537
- {
7538
- id: "count",
7539
- label: t("files.sort.count")
7540
- },
7541
- {
7542
- id: "latest",
7543
- label: t("files.sort.latest")
7544
- },
7545
- {
7546
- id: "path",
7547
- label: t("files.sort.path")
7548
- }
7549
- ],
7550
- onPick: (id) => {
7551
- props.set?.("defaultFileSort", id);
7552
- }
7553
- })
7554
- ]
7714
+ children: [!state.writable && state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7715
+ className: "lc-settings-note",
7716
+ role: "status",
7717
+ children: t("settings.readOnly")
7718
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferenceRows, {
7719
+ t,
7720
+ state,
7721
+ set: props.set
7722
+ })]
7555
7723
  }) : null]
7556
7724
  });
7557
7725
  };
7558
7726
  }
7727
+ /**
7728
+ * The Plugins-page card (the Config-form generation's `plugins.bundle.config`
7729
+ * seat, `view: 'page'`): the page owns the bundle's page and section chrome,
7730
+ * so the rows render flat. Same unserved/absent degradation as the
7731
+ * settings-section card.
7732
+ */
7733
+ function makePluginConfigCard(kit) {
7734
+ const { t } = kit;
7735
+ return function PluginConfigCard(props) {
7736
+ const state = typeof props.useContextSettings === "function" ? props.useContextSettings((s) => s) : void 0;
7737
+ if (state === void 0 || state.status === "unavailable") return null;
7738
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7739
+ className: "lc-settings-prefs",
7740
+ children: [!state.writable && state.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7741
+ className: "lc-settings-note",
7742
+ role: "status",
7743
+ children: t("settings.readOnly")
7744
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PreferenceRows, {
7745
+ t,
7746
+ state,
7747
+ set: props.set
7748
+ })]
7749
+ });
7750
+ };
7751
+ }
7559
7752
  //#endregion
7560
7753
  //#region src/client/settings.ts
7561
7754
  function prefsOf(value) {
@@ -9793,7 +9986,7 @@ window.__ModuleLoader__.load({
9793
9986
  return function PluginInfo() {
9794
9987
  const [latest, setLatest] = (0, react.useState)(null);
9795
9988
  (0, react.useEffect)(() => {
9796
- if ("0.54.4".includes("-dev")) return;
9989
+ if ("0.56.0".includes("-dev")) return;
9797
9990
  let on = true;
9798
9991
  fetchLatestVersion().then((v) => {
9799
9992
  if (on && v) setLatest(v);
@@ -9802,8 +9995,8 @@ window.__ModuleLoader__.load({
9802
9995
  on = false;
9803
9996
  };
9804
9997
  }, []);
9805
- const update = latest !== null && isNewerVersion(latest, "0.54.4") ? latest : null;
9806
- const nameText = "dsh-context (v0.54.4)";
9998
+ const update = latest !== null && isNewerVersion(latest, "0.56.0") ? latest : null;
9999
+ const nameText = "dsh-context (v0.56.0)";
9807
10000
  const nameValue = [nameText];
9808
10001
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9809
10002
  className: "lc-pi-update",
@@ -10519,10 +10712,10 @@ window.__ModuleLoader__.load({
10519
10712
  * lands in the residue. When the host folded the generation split (see
10520
10713
  * TimingTotals), the generation slice expands into what was being decoded:
10521
10714
  * thinking, answer text, and tool-call arguments. The slice rows lead with
10522
- * the true duration and qualify it with the call count on the secondary line —
10523
- * except the decode slices, which count BLOCKS (zero to many per call, so a
10524
- * call count would be a false tally) and therefore carry no qualifier. A slice
10525
- * that never happened (zero time) is omitted entirely — the ring already skips
10715
+ * the true duration and qualify it with a tally on the secondary line — the
10716
+ * model calls (TTFT), the tool runs, and the decode slices' own BLOCK counts
10717
+ * (zero to many per call, so a call count would be a false tally there). A
10718
+ * slice that never happened (zero time) is omitted entirely — the ring already skips
10526
10719
  * its arc, and a zero row would otherwise borrow the session's call count and
10527
10720
  * read as "no time, many calls". The donut and the rows sit side by side so
10528
10721
  * the head row stays half-height. Parallel tool calls each count, so the tools
@@ -10564,31 +10757,36 @@ window.__ModuleLoader__.load({
10564
10757
  ms: timing.ttftMs,
10565
10758
  times: callTimes
10566
10759
  }];
10760
+ const blockTimes = (blocks) => blocks !== void 0 && blocks > 0 ? t("timing.blockTimes", { n: fmt(blocks) }) : void 0;
10567
10761
  const buckets = [
10568
10762
  [
10569
10763
  "reasoning",
10570
10764
  COLOR.reasoning,
10571
10765
  t("timing.reasoning"),
10572
- timing.reasoningMs ?? 0
10766
+ timing.reasoningMs ?? 0,
10767
+ blockTimes(timing.reasoningBlocks)
10573
10768
  ],
10574
10769
  [
10575
10770
  "text",
10576
10771
  COLOR.text,
10577
10772
  t("timing.text"),
10578
- timing.textMs ?? 0
10773
+ timing.textMs ?? 0,
10774
+ blockTimes(timing.textBlocks)
10579
10775
  ],
10580
10776
  [
10581
10777
  "toolarg",
10582
10778
  COLOR.toolarg,
10583
10779
  t("timing.toolArgs"),
10584
- timing.toolArgMs ?? 0
10780
+ timing.toolArgMs ?? 0,
10781
+ blockTimes(timing.toolArgBlocks)
10585
10782
  ]
10586
10783
  ];
10587
- if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms] of buckets) modelSlices.push({
10784
+ if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms, times] of buckets) modelSlices.push({
10588
10785
  key,
10589
10786
  color,
10590
10787
  label,
10591
- ms
10788
+ ms,
10789
+ ...times !== void 0 ? { times } : {}
10592
10790
  });
10593
10791
  else modelSlices.push({
10594
10792
  key: "gen",
@@ -11588,7 +11786,7 @@ window.__ModuleLoader__.load({
11588
11786
  }
11589
11787
  //#endregion
11590
11788
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/base.css.mjs
11591
- const css$14 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}[data-sidebar-right-open] .lc-root,[data-sidebar-right-float-host] .lc-root{height:auto;padding:12px;overflow:visible}.lc-title-icon{flex:none}.lc-title-label{padding-right:30px}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-root .lc-card{box-sizing:border-box;margin-bottom:7px;padding:7px 8px;container:lc-card/inline-size}.lc-root .lc-cols{gap:7px;margin-bottom:7px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-trend-adaptive{flex:none;margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-kind-n{font-variant-numeric:tabular-nums;opacity:.65;margin-left:4px}.lc-gran-on .lc-kind-n{opacity:.85}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head>.lc-card:has(.lc-stats){flex-direction:column;display:flex}.lc-head .lc-stats{flex:1;align-content:stretch}.lc-head .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])),[data-conversation-scroll]:has(.lc-root,.lc-modal-backdrop)~[data-width-handle]{display:none}";
11789
+ const css$14 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}[data-sidebar-right-open] .lc-root,[data-sidebar-right-float-host] .lc-root{height:auto;padding:12px;overflow:visible}.lc-title-icon{flex:none}.lc-title-label{padding-right:30px}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-root .lc-card{box-sizing:border-box;margin-bottom:7px;padding:7px 8px;container:lc-card/inline-size}.lc-root .lc-cols{gap:7px;margin-bottom:7px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl,.lc-heat-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran,.lc-heat-ctl .lc-gran,.lc-heat-ctl .lc-card-sub{margin-left:0}.lc-trend-adaptive{flex:none;margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-kind-n{font-variant-numeric:tabular-nums;opacity:.65;margin-left:4px}.lc-gran-on .lc-kind-n{opacity:.85}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head>.lc-card:has(.lc-stats){flex-direction:column;display:flex}.lc-head .lc-stats{flex:1;align-content:stretch}.lc-head .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])),[data-conversation-scroll]:has(.lc-root,.lc-modal-backdrop)~[data-width-handle]{display:none}";
11592
11790
  const tagId$14 = "dsh-context/base.css";
11593
11791
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$14) + "]") === null) {
11594
11792
  const tag = document.createElement("style");
@@ -11621,7 +11819,7 @@ window.__ModuleLoader__.load({
11621
11819
  }
11622
11820
  //#endregion
11623
11821
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/settings.css.mjs
11624
- const css$11 = ".lc-settings-card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:12px}.lc-settings-card:hover{border-color:var(--dsw-alias-label-dimmed)}.lc-settings-open,.lc-settings-open:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.lc-settings-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.lc-settings-headtext{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.lc-settings-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.lc-settings-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.lc-settings-chevron{color:var(--dsw-alias-label-tertiary);transition:transform var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);flex:none}.lc-settings-open .lc-settings-chevron{transform:rotate(180deg)}.lc-settings-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 12px}.lc-settings-row{align-items:center;gap:8px;padding:8px 0;display:flex}.lc-settings-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:14px}.lc-settings-select{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lc-settings-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 4px;font-size:12px;line-height:1.5}";
11822
+ const css$11 = ".lc-settings-card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:12px}.lc-settings-card:hover{border-color:var(--dsw-alias-label-dimmed)}.lc-settings-open,.lc-settings-open:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.lc-settings-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.lc-settings-headtext{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.lc-settings-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.lc-settings-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.lc-settings-chevron{color:var(--dsw-alias-label-tertiary);transition:transform var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);flex:none}.lc-settings-open .lc-settings-chevron{transform:rotate(180deg)}.lc-settings-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 12px}.lc-settings-prefs{padding:4px 0}.lc-settings-row{align-items:center;gap:8px;padding:8px 0;display:flex}.lc-settings-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:14px}.lc-settings-select{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lc-settings-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 4px;font-size:12px;line-height:1.5}";
11625
11823
  const tagId$11 = "dsh-context/settings.css";
11626
11824
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$11) + "]") === null) {
11627
11825
  const tag = document.createElement("style");
@@ -11742,7 +11940,7 @@ window.__ModuleLoader__.load({
11742
11940
  }
11743
11941
  //#endregion
11744
11942
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/overview.css.mjs
11745
- const css = ".lc-ov-entry{box-sizing:border-box;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;margin:0 -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.lc-ov-entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-ov-entry-rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.lc-ov-entry-icon{flex:none}.lc-ov-entry-label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-ov-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0;container:lc-ov/inline-size}.lc-ov-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(1120px,100vw - 32px);max-height:min(86vh,900px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:16px 18px 18px;font-size:13px;display:flex;overflow:hidden}.lc-ov-head{flex-wrap:wrap;flex:none;align-items:center;gap:10px;margin-bottom:14px;display:flex}.lc-ov-head-icon{flex:none}.lc-ov-title{white-space:nowrap;font-size:15px;font-weight:600}.lc-ov-balance{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:inherit;border-radius:999px;flex:none;align-items:center;gap:6px;padding:2px 10px;font-size:12px;line-height:18px;text-decoration:none;display:inline-flex}.lc-ov-balance:hover .lc-ov-balance-value{text-decoration:underline}.lc-ov-balance-label{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-ov-balance-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-head .lc-modal-close{flex:none;margin-left:0}.lc-ov-body{flex:auto;grid-template-columns:minmax(0,3fr) minmax(0,7fr);gap:10px;min-height:0;display:grid}.lc-ov-left{scrollbar-gutter:stable;flex-direction:column;gap:10px;min-height:0;padding-right:8px;display:flex;overflow-y:auto}.lc-ov-right{scrollbar-gutter:stable;min-height:0;padding-right:8px;overflow-y:auto}@container lc-ov (width<=932px){.lc-ov-card{overflow-y:auto}.lc-ov-body{display:block}.lc-ov-left{margin-bottom:12px}.lc-ov-left,.lc-ov-right{min-height:auto;padding-right:0;overflow:visible}.lc-ov-heat-card{flex:none}}.lc-ov-left>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}.lc-ov-kpis{flex:none;grid-template-rows:repeat(3,1fr);grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;display:grid}.lc-ov-kpi{align-items:center;padding:10px 8px}.lc-ov-kpi .lc-stat-value{font-size:20px}.lc-ov-heat-card{flex-direction:column;flex:1 0 auto;display:flex}.lc-heat{flex:1;justify-content:center;align-items:center;gap:4px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:13px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{width:10px;height:20px;color:var(--dsw-alias-label-secondary);text-align:center;flex:none;justify-content:center;align-items:center;font-size:9px;display:flex}.lc-heat-cols{gap:3px;display:flex}.lc-heat-col{flex-direction:column;gap:3px;display:flex;position:relative}.lc-heat-mon{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:9px;line-height:13px;position:absolute;top:0;left:0}.lc-heat-cell{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);cursor:default;border:0;border-radius:20%;width:20px;height:20px;padding:0}button.lc-heat-cell{cursor:pointer}button.lc-heat-cell:hover{box-shadow:0 0 0 1.5px color-mix(in srgb, var(--color-blue-500) 70%, transparent)}.lc-heat-future{background:0 0}.lc-heat-0{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent)}.lc-heat-1{background:color-mix(in srgb, var(--color-blue-500) 28%, transparent)}.lc-heat-2{background:color-mix(in srgb, var(--color-blue-500) 50%, transparent)}.lc-heat-3{background:color-mix(in srgb, var(--color-blue-500) 72%, transparent)}.lc-heat-4{background:color-mix(in srgb, var(--color-blue-500) 92%, transparent)}.lc-heat-on{box-shadow:0 0 0 1.5px var(--color-blue-500)}.lc-ov-list-head{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:10px;display:flex}.lc-ov-list-title{white-space:nowrap;font-weight:600}.lc-ov-list-count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;background:var(--dsw-alias-bg-layer-2);border-radius:8px;padding:1px 7px;font-size:11px}.lc-ov-day-chip{border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);color:var(--color-blue-500);background:color-mix(in srgb, var(--color-blue-500) 10%, transparent);cursor:pointer;white-space:nowrap;border-radius:999px;padding:1px 10px;font-family:inherit;font-size:11px;line-height:1.4}.lc-ov-search{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:110px;font:inherit;color:var(--dsw-alias-label-primary);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;outline:none;flex:150px;padding:4px 10px;font-size:12px}.lc-ov-search:focus{border-color:var(--dsw-alias-label-secondary)}.lc-ov-search::placeholder{color:var(--dsw-alias-label-secondary)}.lc-ov-groups{flex-wrap:wrap;gap:6px;margin:-2px 0 10px;display:flex}.lc-ov-chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;max-width:220px;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:999px;align-items:baseline;gap:5px;padding:2px 10px;font-size:11px;line-height:1.5;display:inline-flex;overflow:hidden}.lc-ov-chip:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}.lc-ov-chip-on,.lc-ov-chip-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.lc-ov-chip-n{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.lc-ov-chip-on .lc-ov-chip-n{opacity:.85}.lc-ov-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;display:grid}.lc-ov-session{text-align:left;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;font:inherit;color:inherit;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.lc-ov-session:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.lc-ov-session-current{border-color:color-mix(in srgb, var(--color-blue-500) 65%, transparent)}.lc-ov-session-head{align-items:flex-start;gap:6px;min-width:0;display:flex}.lc-ov-running{background:var(--color-green-500);border-radius:50%;flex:none;width:7px;height:7px}.lc-ov-session-title{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;flex:auto;min-width:0;font-weight:600;display:-webkit-box;overflow:hidden}.lc-ov-current{color:var(--color-blue-500);border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);border-radius:4px;flex:none;padding:0 4px;font-size:10px;line-height:1.5}.lc-ov-session-times{flex-direction:column;flex:none;align-items:flex-end;gap:1px;display:flex}.lc-ov-session-time{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:11px}.lc-ov-session-created{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:10px;line-height:1.4}.lc-ov-session-crumb{min-width:0;color:var(--dsw-alias-label-secondary);white-space:nowrap;align-items:center;gap:5px;font-size:11px;display:flex;overflow:hidden}.lc-ov-crumb-group{text-overflow:ellipsis;flex:none;max-width:55%;overflow:hidden}.lc-ov-crumb-sep{opacity:.55;flex:none}.lc-ov-crumb-project{text-overflow:ellipsis;min-width:0;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent);border-radius:999px;flex:0 auto;padding:0 8px;font-size:10px;line-height:1.6;overflow:hidden}.lc-ov-session-body{align-items:center;gap:14px;min-width:0;display:flex}.lc-ov-mini-stats{flex-direction:column;flex:auto;gap:3px;min-width:0;display:flex}.lc-ov-mini-stat{justify-content:space-between;align-items:baseline;gap:10px;font-size:11px;display:flex}.lc-ov-mini-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.lc-ov-mini-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-session-empty{color:var(--dsw-alias-label-secondary);padding:6px 0;font-size:11px;line-height:1.5}.lc-ov-session-preview{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);align-items:baseline;gap:6px;min-width:0;margin-top:auto;padding-top:6px;display:flex}.lc-ov-session-preview-label{color:var(--dsw-alias-label-secondary);flex:none;font-size:10px}.lc-ov-session-preview-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:0 auto;font-size:11px;line-height:1.5;overflow:hidden}.lc-ov-pager{justify-content:center;align-items:center;gap:10px;margin-top:12px;display:flex}.lc-ov-pager-btn{border:1px solid var(--dsw-alias-border-l2);width:26px;height:22px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;line-height:1;display:flex}.lc-ov-pager-btn:hover:enabled{border-color:var(--dsw-alias-label-secondary)}.lc-ov-pager-btn:disabled{opacity:.4;cursor:default}.lc-ov-pager-n{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}";
11943
+ const css = ".lc-ov-entry{box-sizing:border-box;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;margin:0 -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.lc-ov-entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-ov-entry-rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.lc-ov-entry-icon{flex:none}.lc-ov-entry-label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-ov-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0;container:lc-ov/inline-size}.lc-ov-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(1120px,100vw - 32px);height:calc(100% - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:16px 18px 18px;font-size:13px;display:flex;overflow:hidden}.lc-ov-head{flex-wrap:wrap;flex:none;align-items:center;gap:10px;margin-bottom:14px;display:flex}.lc-ov-head-icon{flex:none}.lc-ov-title{white-space:nowrap;font-size:15px;font-weight:600}.lc-ov-balance{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:inherit;border-radius:999px;flex:none;align-items:center;gap:6px;padding:2px 10px;font-size:12px;line-height:18px;text-decoration:none;display:inline-flex}.lc-ov-balance:hover .lc-ov-balance-value{text-decoration:underline}.lc-ov-balance-label{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-ov-balance-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-head .lc-modal-close{flex:none;margin-left:0}.lc-ov-body{flex:auto;grid-template-columns:minmax(0,3fr) minmax(0,7fr);gap:10px;min-height:0;display:grid}.lc-ov-left{scrollbar-gutter:stable;flex-direction:column;gap:10px;min-height:0;padding-right:8px;display:flex;overflow-y:auto}.lc-ov-right{scrollbar-gutter:stable;min-height:0;padding-right:8px;overflow-y:auto}@container lc-ov (width<=932px){.lc-ov-card{overflow-y:auto}.lc-ov-body{display:block}.lc-ov-left{margin-bottom:12px}.lc-ov-left,.lc-ov-right{min-height:auto;padding-right:0;overflow:visible}.lc-ov-heat-card{flex:none}}.lc-ov-left>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}.lc-ov-kpis{flex:none;grid-template-rows:repeat(3,1fr);grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;display:grid}.lc-ov-kpi{align-items:center;padding:10px 8px}.lc-ov-kpi .lc-stat-value{font-size:20px}.lc-ov-heat-card{flex-direction:column;flex:1 0 auto;display:flex}.lc-ov-settings{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex:none;align-items:center;gap:8px;padding:9px 12px;font-size:12px;display:flex}.lc-ov-settings:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.lc-ov-settings-label{white-space:nowrap;flex:none;align-items:center;gap:6px;display:inline-flex}.lc-ov-settings-hint{text-align:right;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-heat-wrap{flex-direction:column;flex:1;display:flex}.lc-heat{flex:1;justify-content:center;align-items:center;gap:8px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:18px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{height:20px;color:var(--dsw-alias-label-secondary);text-align:center;white-space:nowrap;flex:none;justify-content:center;align-items:center;font-size:9px;display:flex}.lc-heat-cols{gap:3px;display:flex}.lc-heat-col{flex-direction:column;gap:3px;display:flex;position:relative}.lc-heat-mon{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:9px;line-height:18px;position:absolute;top:0;left:0}.lc-heat-cell{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);cursor:default;border:0;border-radius:20%;width:20px;height:20px;padding:0}button.lc-heat-cell{cursor:pointer}button.lc-heat-cell:hover{box-shadow:0 0 0 1.5px color-mix(in srgb, var(--color-blue-500) 70%, transparent)}.lc-heat-future{background:0 0}.lc-heat-0{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent)}.lc-heat-1{background:color-mix(in srgb, var(--color-blue-500) 28%, transparent)}.lc-heat-2{background:color-mix(in srgb, var(--color-blue-500) 50%, transparent)}.lc-heat-3{background:color-mix(in srgb, var(--color-blue-500) 72%, transparent)}.lc-heat-4{background:color-mix(in srgb, var(--color-blue-500) 92%, transparent)}.lc-heat-on{box-shadow:0 0 0 1.5px var(--color-blue-500)}.lc-heat-legend{justify-content:flex-end;align-items:center;gap:3px;padding-top:6px;display:flex}.lc-heat-key{color:var(--dsw-alias-label-secondary);font-size:9px}.lc-ov-list-head{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:10px;display:flex}.lc-ov-list-title{white-space:nowrap;font-weight:600}.lc-ov-list-count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;background:var(--dsw-alias-bg-layer-2);border-radius:8px;padding:1px 7px;font-size:11px}.lc-ov-day-chip{border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);color:var(--color-blue-500);background:color-mix(in srgb, var(--color-blue-500) 10%, transparent);cursor:pointer;white-space:nowrap;border-radius:999px;padding:1px 10px;font-family:inherit;font-size:11px;line-height:1.4}.lc-ov-search{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:110px;font:inherit;color:var(--dsw-alias-label-primary);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;outline:none;flex:150px;padding:4px 10px;font-size:12px}.lc-ov-search:focus{border-color:var(--dsw-alias-label-secondary)}.lc-ov-search::placeholder{color:var(--dsw-alias-label-secondary)}.lc-ov-groups{flex-wrap:wrap;gap:6px;margin:-2px 0 10px;display:flex}.lc-ov-chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;max-width:220px;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:999px;align-items:baseline;gap:5px;padding:2px 10px;font-size:11px;line-height:1.5;display:inline-flex;overflow:hidden}.lc-ov-chip:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}.lc-ov-chip-on,.lc-ov-chip-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.lc-ov-chip-n{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.lc-ov-chip-on .lc-ov-chip-n{opacity:.85}.lc-ov-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;display:grid}.lc-ov-session{text-align:left;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;font:inherit;color:inherit;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.lc-ov-session:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.lc-ov-session-current{border-color:color-mix(in srgb, var(--color-blue-500) 65%, transparent)}.lc-ov-session-head{align-items:flex-start;gap:6px;min-width:0;display:flex}.lc-ov-running{background:var(--color-green-500);border-radius:50%;flex:none;width:7px;height:7px}.lc-ov-session-title{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;flex:auto;min-width:0;font-weight:600;display:-webkit-box;overflow:hidden}.lc-ov-current{color:var(--color-blue-500);border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);border-radius:4px;flex:none;padding:0 4px;font-size:10px;line-height:1.5}.lc-ov-session-times{flex-direction:column;flex:none;align-items:flex-end;gap:1px;display:flex}.lc-ov-session-time{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:11px}.lc-ov-session-created{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:10px;line-height:1.4}.lc-ov-session-crumb{min-width:0;color:var(--dsw-alias-label-secondary);white-space:nowrap;align-items:center;gap:5px;font-size:11px;display:flex;overflow:hidden}.lc-ov-crumb-group{text-overflow:ellipsis;flex:none;max-width:55%;overflow:hidden}.lc-ov-crumb-sep{opacity:.55;flex:none}.lc-ov-crumb-project{text-overflow:ellipsis;min-width:0;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent);border-radius:999px;flex:0 auto;padding:0 8px;font-size:10px;line-height:1.6;overflow:hidden}.lc-ov-session-body{align-items:center;gap:14px;min-width:0;display:flex}.lc-ov-mini-stats{flex-direction:column;flex:auto;gap:3px;min-width:0;display:flex}.lc-ov-mini-stat{justify-content:space-between;align-items:baseline;gap:10px;font-size:11px;display:flex}.lc-ov-mini-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.lc-ov-mini-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-session-empty{color:var(--dsw-alias-label-secondary);padding:6px 0;font-size:11px;line-height:1.5}.lc-ov-session-preview{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);align-items:baseline;gap:6px;min-width:0;margin-top:auto;padding-top:6px;display:flex}.lc-ov-session-preview-label{color:var(--dsw-alias-label-secondary);flex:none;font-size:10px}.lc-ov-session-preview-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:0 auto;font-size:11px;line-height:1.5;overflow:hidden}.lc-ov-pager{justify-content:center;align-items:center;gap:10px;margin-top:12px;display:flex}.lc-ov-pager-btn{border:1px solid var(--dsw-alias-border-l2);width:26px;height:22px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;line-height:1;display:flex}.lc-ov-pager-btn:hover:enabled{border-color:var(--dsw-alias-label-secondary)}.lc-ov-pager-btn:disabled{opacity:.4;cursor:default}.lc-ov-pager-n{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}";
11746
11944
  const tagId = "dsh-context/overview.css";
11747
11945
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
11748
11946
  const tag = document.createElement("style");
@@ -11839,6 +12037,14 @@ window.__ModuleLoader__.load({
11839
12037
  locale: NS
11840
12038
  }, (props) => (0, react.createElement)(OverviewPanel, props));
11841
12039
  });
12040
+ /** The injected face both preference cards ride: the settings store as the
12041
+ * framework's hooks-compartment `useContextSettings` seat, plus the set verb. */
12042
+ const cardFace = () => ({
12043
+ hooks: { contextSettings: settings.store },
12044
+ set: (field, value) => {
12045
+ settings.set(field, value);
12046
+ }
12047
+ });
11842
12048
  ctx.inject(["settingsScope"], (raw) => {
11843
12049
  const c = raw;
11844
12050
  const binder = c.settingsScope;
@@ -11850,15 +12056,27 @@ window.__ModuleLoader__.load({
11850
12056
  name: "settings.plugin.item",
11851
12057
  key: NS,
11852
12058
  locale: NS,
11853
- inject: () => ({
11854
- hooks: { contextSettings: settings.store },
11855
- set: (field, value) => {
11856
- settings.set(field, value);
11857
- }
11858
- })
12059
+ inject: cardFace
11859
12060
  }, (props) => (0, react.createElement)(SettingsCard, props));
11860
12061
  });
11861
12062
  });
12063
+ ctx.inject(["configForms"], (raw) => {
12064
+ const c = raw;
12065
+ const forms = c.configForms;
12066
+ if (forms === void 0 || typeof forms.get !== "function" || typeof forms.whileServed !== "function") return;
12067
+ c.effect(() => settings.attach(forms.get(NS)), "dsh-context: config forms");
12068
+ const PluginConfigCard = makePluginConfigCard(kit);
12069
+ c.effect(() => forms.whileServed([NS], () => {
12070
+ return c.slots.inject("plugins.bundle.config", () => {
12071
+ return c.slots.register({
12072
+ name: "plugins.bundle.config",
12073
+ key: NS,
12074
+ locale: NS,
12075
+ inject: cardFace
12076
+ }, (props) => (0, react.createElement)(PluginConfigCard, props));
12077
+ });
12078
+ }), "dsh-context: plugins-page card");
12079
+ });
11862
12080
  }
11863
12081
  module.exports = {
11864
12082
  name: "dsh-context",