dsh-context 0.13.0 → 0.15.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- <p align="center"><img src="https://raw.githubusercontent.com/bowenliang123/dsh-context/main/docs/social-preview.png" width="840" alt="Social preview"></p>
1
+ ![Social preview](https://raw.githubusercontent.com/bowenliang123/dsh-context/main/docs/social-preview.png)
2
2
 
3
3
  # dsh-context
4
4
 
package/lib/client.js CHANGED
@@ -9,17 +9,16 @@ window.__ModuleLoader__.load({
9
9
  // src/client/i18n.ts
10
10
  var DICT_ZH = {
11
11
  "tab": "\u4E0A\u4E0B\u6587",
12
- "cat.system": "\u7CFB\u7EDF\u63D0\u793A",
12
+ "cat.system": "\u7CFB\u7EDF\u63D0\u793A\u8BCD",
13
13
  "cat.tools": "\u5DE5\u5177\u5B9A\u4E49",
14
14
  "cat.user": "\u7528\u6237\u6D88\u606F",
15
- "cat.inject": "\u6CE8\u5165\u4E0A\u4E0B\u6587",
16
- "cat.assistant": "\u52A9\u624B\u56DE\u590D",
15
+ "cat.inject": "\u6CE8\u5165\u5185\u5BB9",
16
+ "cat.assistant": "\u52A9\u624B\u6D88\u606F",
17
17
  "cat.tool": "\u5DE5\u5177\u7ED3\u679C",
18
18
  "overview.title": "\u5F53\u524D\u6784\u6210",
19
- "overview.ofWindow": "tokens\uFF08\u7EA6 {p}%\uFF09",
20
19
  "overview.estimate": "tokens\uFF08\u4F30\u7B97\uFF09",
21
20
  "overview.free": "\u5269\u4F59\u7A97\u53E3",
22
- "overview.splitEst": "\xB7 \u5206\u7C7B\u6309\u4F30\u7B97\u6BD4\u4F8B\u5206\u914D",
21
+ "overview.used": "\u4E0A\u4E0B\u6587\u5DF2\u7528",
23
22
  "overview.ofUsed": "\u5360\u5DF2\u7528\u4E0A\u4E0B\u6587",
24
23
  "stats.title": "\u4E0A\u4E0B\u6587\u7EDF\u8BA1",
25
24
  "stats.hint": "\u7EDF\u8BA1\u5F53\u524D\u4FDD\u7559\u7684\u5386\u53F2\u7A97\u53E3\uFF08\u4E0E\u8D8B\u52BF\u56FE\u4E00\u81F4\uFF09",
@@ -90,50 +89,49 @@ var DICT_ZH = {
90
89
  "browser.missingLive": "\u2026 \u53E6\u6709 {n} \u6761\u66F4\u65E9\u7684\u6D88\u606F\u4E5F\u5728\u4E0A\u4E0B\u6587\u4E2D\uFF08\u8D85\u51FA\u5C55\u793A\u7A97\u53E3\uFF09",
91
90
  "browser.approx": "\u8BE5\u6B65\u9AA4\u6D89\u53CA\u7684\u90E8\u5206\u5DF2\u79FB\u9664\u6D88\u606F\u8D85\u51FA\u4FDD\u7559\u8303\u56F4\uFF0C\u4EE5\u4E0B\u4E3A\u8FD1\u4F3C\u6784\u6210",
92
91
  "browser.noHeader": "\u6B64\u6570\u636E\u6765\u81EA\u65E7\u7248\u63D2\u4EF6\uFF1A\u4EC5\u63D0\u4F9B token \u4F30\u7B97\uFF0C\u65E0\u5B9E\u9645\u5185\u5BB9",
93
- "browser.noEpoch": "\u8BE5\u6B65\u9AA4\u7684\u5934\u90E8\u5185\u5BB9\uFF08\u7CFB\u7EDF\u63D0\u793A / \u5DE5\u5177\u5B9A\u4E49\uFF09\u4E0D\u5728\u4FDD\u7559\u8303\u56F4\u5185",
92
+ "browser.noEpoch": "\u8BE5\u6B65\u9AA4\u7684\u5934\u90E8\u5185\u5BB9\uFF08\u7CFB\u7EDF\u63D0\u793A\u8BCD / \u5DE5\u5177\u5B9A\u4E49\uFF09\u4E0D\u5728\u4FDD\u7559\u8303\u56F4\u5185",
94
93
  "browser.noContent": "\u5B8C\u6574\u5185\u5BB9\u4E0D\u5728\u5F53\u524D\u52A0\u8F7D\u7684\u6D88\u606F\u7A97\u53E3\u5185\uFF08\u5728\u804A\u5929\u9875\u52A0\u8F7D\u66F4\u65E9\u5386\u53F2\u540E\u53EF\u67E5\u770B\uFF09",
95
94
  "browser.loading": "\u6B63\u5728\u4ECE\u66F4\u65E9\u7684\u4F1A\u8BDD\u5386\u53F2\u52A0\u8F7D\u5B8C\u6574\u5185\u5BB9\u2026",
96
95
  "browser.preview": "\u9884\u89C8"
97
96
  };
98
97
  var DICT_EN = {
99
98
  "tab": "Context",
100
- "cat.system": "System",
101
- "cat.tools": "Tool schemas",
102
- "cat.user": "User",
103
- "cat.inject": "Injected",
104
- "cat.assistant": "Assistant",
105
- "cat.tool": "Tool results",
106
- "overview.title": "Current composition",
107
- "overview.ofWindow": "tokens (~{p}%)",
99
+ "cat.system": "System Prompt",
100
+ "cat.tools": "Tool Schemas",
101
+ "cat.user": "User Messages",
102
+ "cat.inject": "Injected Context",
103
+ "cat.assistant": "Assistant Messages",
104
+ "cat.tool": "Tool Results",
105
+ "overview.title": "Current Context",
108
106
  "overview.estimate": "tokens (estimated)",
109
- "overview.free": "Free window",
110
- "overview.splitEst": "\xB7 category split is estimated",
107
+ "overview.free": "Free Window",
108
+ "overview.used": "of context used",
111
109
  "overview.ofUsed": "of used context",
112
- "stats.title": "Context stats",
110
+ "stats.title": "Context Stats",
113
111
  "stats.hint": "Over the retained history window (same as the History chart)",
114
112
  "stats.turns": "Turns",
115
113
  "stats.steps": "Steps",
116
114
  "stats.injects": "Injections",
117
115
  "stats.compactions": "Compactions",
118
116
  "stats.prunes": "Prunes",
119
- "plugin.title": "Plugin info",
117
+ "plugin.title": "Plugin Info",
120
118
  "plugin.hint": "The best DSH context plugin \u2B50",
121
119
  "plugin.name": "Plugin",
122
120
  "plugin.github": "GitHub",
123
- "tools.top": "Top tool schemas:",
121
+ "tools.top": "Top Tool Schemas:",
124
122
  "tools.more": "of {n}",
125
- "trend.title": "History",
123
+ "trend.title": "Context History",
126
124
  "gran.step": "Step",
127
125
  "gran.turn": "Turn",
128
- "trend.hint": "hover a bar for details (click to pin), \u2702 marks compaction/prune; scroll sideways to see earlier; Step/Turn switches granularity",
126
+ "trend.hint": "Hover to view details, \u2702 marks compaction/prune, Step/Turn switches granularity",
129
127
  "trend.empty": "Send a message and each model request\u2019s context makeup shows up here",
130
128
  "detail.step": "Turn {t} \xB7 Step {s}",
131
129
  "detail.turn": "Turn {t} \xB7 {n} steps",
132
- "detail.lastStep": "last step",
133
- "detail.estTotal": "estimated \u2248 {n}",
134
- "detail.actual": "actual prompt {n}",
135
- "detail.output": "output {n}",
136
- "events.title": "Context events",
130
+ "detail.lastStep": "Last Step",
131
+ "detail.estTotal": "Estimated \u2248 {n}",
132
+ "detail.actual": "Actual Prompt {n}",
133
+ "detail.output": "Output {n}",
134
+ "events.title": "Context Events",
137
135
  "events.empty": "No context events yet (compaction, injections, model switches appear here)",
138
136
  "events.at": "Turn {t} \xB7 Step {s}",
139
137
  "events.range": "Turn {t} \xB7 Step {a}\u2192{b}",
@@ -151,37 +149,37 @@ var DICT_EN = {
151
149
  "footer": "Estimate: same fixed-density heuristic as dsh\u2019s built-in tokenMeter (~4 chars \u2248 1 token); \u201Cactual\u201D is provider-reported usage.",
152
150
  "tip.step": "Turn {t} \xB7 Step {s}",
153
151
  "tip.turn": "Turn {t} \xB7 {n} steps",
154
- "tip.total": "total \u2248 {n}",
152
+ "tip.total": "Total \u2248 {n}",
155
153
  "tip.actual": " (actual {n})",
156
154
  "ev.compaction": "Context compacted (summary replaced {n} messages)",
157
155
  "ev.prune": "Tool output pruned",
158
156
  "ev.skill": "Skill injected ({name})",
159
157
  "ev.model": "Model switched: {a} \u2192 {b}",
160
158
  "form.instructions": "Instructions",
161
- "form.catalog": "Catalog update",
162
- "form.snapshot": "State snapshot",
159
+ "form.catalog": "Catalog Update",
160
+ "form.snapshot": "State Snapshot",
163
161
  "form.notice": "Notice",
164
- "form.relay": "Agent relay",
162
+ "form.relay": "Agent Relay",
165
163
  "form.recall": "Recall",
166
- "form.context": "Context injection",
167
- "node.toolResult": "Tool result",
168
- "node.calls": "calls ",
164
+ "form.context": "Context Injection",
165
+ "node.toolResult": "Tool Result",
166
+ "node.calls": "Calls ",
169
167
  "node.empty": "(empty reply)",
170
168
  "node.nonText": "(non-text message)",
171
- "node.snapshot": "snapshot: ",
169
+ "node.snapshot": "Snapshot: ",
172
170
  "cmd.desc": "View context makeup and how it evolves",
173
171
  "cmd.close": "Close",
174
- "browser.title": "Context browser",
175
- "browser.live": "Live (next request)",
176
- "browser.liveNow": "Live \xB7 next request",
177
- "browser.items": "{n} items",
172
+ "browser.title": "Context Browser",
173
+ "browser.live": "Live (Next Request)",
174
+ "browser.liveNow": "Live \xB7 Next Request",
175
+ "browser.items": "{n} Items",
178
176
  "browser.missingLive": "\u2026 {n} earlier messages are also part of the context (outside the served window)",
179
177
  "browser.approx": "Some removed messages of this step exceed retention \u2014 the makeup below is approximate",
180
178
  "browser.noHeader": "Served by an older plugin build: token estimates only, no content",
181
- "browser.noEpoch": "The header content (system prompt / tool schemas) of this step is outside retention",
179
+ "browser.noEpoch": "The header content (System Prompt / Tool Schemas) of this step is outside retention",
182
180
  "browser.noContent": "Full content is outside the loaded message window (load older history in Chat to view)",
183
181
  "browser.loading": "Loading full content from older session history\u2026",
184
- "browser.preview": "preview"
182
+ "browser.preview": "Preview"
185
183
  };
186
184
 
187
185
  // src/client/modalStore.ts
@@ -286,23 +284,19 @@ function headlineOf(data, pressure = null) {
286
284
  const tokens = occupancyTokens ?? current.total;
287
285
  const pct = window2 !== void 0 && window2 > 0 ? Math.min(100, Math.round(tokens / window2 * 100)) : null;
288
286
  const parts = anchoredParts(partsOf(current), occupancyTokens !== null && tokens > 0 ? tokens : null);
289
- return { tokens, window: window2, pct, parts, estimated: occupancyTokens === null };
287
+ return { tokens, window: window2, pct, parts };
290
288
  }
291
289
 
292
290
  // src/client/services.ts
293
- function timelineOf(value) {
294
- if (value === null || value === void 0 || typeof value !== "object") return null;
295
- return value;
296
- }
297
- function contextPressureOf(value) {
291
+ function asRecord(value) {
298
292
  if (value === null || value === void 0 || typeof value !== "object") return null;
299
293
  return value;
300
294
  }
295
+ var timelineOf = (value) => asRecord(value);
296
+ var contextPressureOf = (value) => asRecord(value);
301
297
  function headersOf(value) {
302
- if (value === null || value === void 0 || typeof value !== "object") return null;
303
- const headers = value.headers;
304
- if (!Array.isArray(headers)) return null;
305
- return value;
298
+ const headers = asRecord(value);
299
+ return headers !== null && Array.isArray(headers.headers) ? headers : null;
306
300
  }
307
301
 
308
302
  // src/client/react.ts
@@ -311,15 +305,15 @@ var h = React.createElement;
311
305
 
312
306
  // src/client/components/requestDetail.tsx
313
307
  function makeRequestDetail(kit, StackedBar) {
314
- const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
308
+ const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
315
309
  return function RequestDetail(props) {
316
310
  const req = props.request;
317
311
  if (!req) return null;
318
312
  const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
319
- const head = isTurn ? tr("detail.turn", { t: req.turn ?? 0, n: req.stepCount ?? 0 }) : tr("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 });
313
+ const head = isTurn ? t("detail.turn", { t: req.turn ?? 0, n: req.stepCount ?? 0 }) : t("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 });
320
314
  const marker = props.marker ?? null;
321
315
  const markerAt = marker !== null ? eventAt(marker) : null;
322
- return /* @__PURE__ */ React.createElement("div", { className: "lc-detail" }, /* @__PURE__ */ React.createElement("div", { className: "lc-detail-head" }, /* @__PURE__ */ React.createElement("b", null, head), marker !== null && markerAt !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-marker", title: eventLabel(marker) }, "\u2702 " + markerAt) : null, isTurn ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null, /* @__PURE__ */ React.createElement("span", null, fmtTime2(req.time)), /* @__PURE__ */ React.createElement("span", null, tr("detail.estTotal", { n: fmt3(req.total) })), req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, tr("detail.actual", { n: fmt3(req.prompt) })) : null, req.output !== void 0 ? /* @__PURE__ */ React.createElement("span", null, tr("detail.output", { n: fmt3(req.output) })) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: partsOf(req), height: 10 }), /* @__PURE__ */ React.createElement("div", { className: "lc-detail-rows" }, CATS.map((c) => {
316
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-detail" }, /* @__PURE__ */ React.createElement("div", { className: "lc-detail-head" }, /* @__PURE__ */ React.createElement("b", null, head), marker !== null && markerAt !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-marker", title: eventLabel(marker) }, "\u2702 " + markerAt) : null, isTurn ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null, /* @__PURE__ */ React.createElement("span", null, fmtTime2(req.time)), /* @__PURE__ */ React.createElement("span", null, t("detail.estTotal", { n: fmt3(req.total) })), req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, t("detail.actual", { n: fmt3(req.prompt) })) : null, req.output !== void 0 ? /* @__PURE__ */ React.createElement("span", null, t("detail.output", { n: fmt3(req.output) })) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: partsOf(req), height: 10 }), /* @__PURE__ */ React.createElement("div", { className: "lc-detail-rows" }, CATS.map((c) => {
323
317
  const v = req[c.key] || 0;
324
318
  return /* @__PURE__ */ React.createElement("div", { key: c.key, className: "lc-detail-row" }, /* @__PURE__ */ React.createElement("i", { style: { background: c.color } }), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-label" }, catLabel(c.key)), /* @__PURE__ */ React.createElement("span", { className: "lc-bar-track" }, /* @__PURE__ */ React.createElement("span", { className: "lc-bar-fill", style: { width: (req.total > 0 ? v / req.total * 100 : 0) + "%", background: c.color } })), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-num" }, "\u2248" + fmt3(v)), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-pct" }, req.total > 0 ? Math.round(v / req.total * 100) + "%" : ""));
325
319
  })));
@@ -328,7 +322,7 @@ function makeRequestDetail(kit, StackedBar) {
328
322
 
329
323
  // src/client/components/stackedBar.tsx
330
324
  function makeStackedBar(kit) {
331
- const { t, tr, fmt: fmt3, catLabel } = kit;
325
+ const { t, fmt: fmt3, catLabel } = kit;
332
326
  return function StackedBar(props) {
333
327
  let total = 0;
334
328
  for (const p of props.parts) total += p.value;
@@ -353,7 +347,7 @@ function makeStackedBar(kit) {
353
347
  tip = {
354
348
  // "(pct%)" is a share of the OCCUPIED total — the dashed box
355
349
  // that appears on hover frames exactly this reference region.
356
- text: catLabel(p.key) + " \u2248" + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%) " + tr("overview.ofUsed"),
350
+ text: catLabel(p.key) + " \u2248" + fmt3(p.value) + " (" + Math.round(p.value / total * 100) + "%) " + t("overview.ofUsed"),
357
351
  leftPct: Math.max(12, Math.min(acc + pct / 2, 88))
358
352
  };
359
353
  break;
@@ -397,12 +391,25 @@ function makeStackedBar(kit) {
397
391
  }
398
392
  }
399
393
  ) : null,
400
- showBox ? /* @__PURE__ */ React.createElement("div", { className: "lc-occupied-box", style: { width: usedPct + "%" } }) : null
401
- ), tip ? /* @__PURE__ */ React.createElement("div", { className: "lc-bar-tip", style: { left: tip.leftPct + "%" } }, tip.text) : null);
394
+ /* @__PURE__ */ React.createElement(
395
+ "div",
396
+ {
397
+ className: "lc-occupied-box" + (showBox ? " lc-occupied-box-on" : ""),
398
+ style: { width: usedPct + "%" }
399
+ }
400
+ )
401
+ ), props.tip !== false ? /* @__PURE__ */ React.createElement(
402
+ "div",
403
+ {
404
+ className: "lc-bar-tip" + (tip ? " lc-bar-tip-on" : ""),
405
+ style: { left: tip ? tip.leftPct + "%" : "50%" }
406
+ },
407
+ tip ? tip.text : ""
408
+ ) : null);
402
409
  };
403
410
  }
404
411
  function makeLegend(kit) {
405
- const { tr, fmt: fmt3, catLabel } = kit;
412
+ const { t, fmt: fmt3, catLabel } = kit;
406
413
  return function Legend(props) {
407
414
  let total = 0;
408
415
  for (const p of props.parts) total += p.value;
@@ -413,7 +420,7 @@ function makeLegend(kit) {
413
420
  {
414
421
  key: p.key,
415
422
  className: "lc-chip" + (on ? " lc-chip-on" : ""),
416
- title: tr("overview.ofUsed"),
423
+ title: t("overview.ofUsed"),
417
424
  onMouseEnter: () => {
418
425
  if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
419
426
  },
@@ -459,7 +466,7 @@ function attachMarkers(requests, events) {
459
466
  return markers;
460
467
  }
461
468
  function makeTrendChart(kit) {
462
- const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
469
+ const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel, eventLabel, eventAt } = kit;
463
470
  const CHART_H = 112;
464
471
  const BAR_W = 14;
465
472
  const BAR_GAP = 2;
@@ -513,8 +520,8 @@ function makeTrendChart(kit) {
513
520
  updateEdges(el);
514
521
  });
515
522
  const tipOf = (req) => {
516
- const head = req.stepCount !== void 0 && req.stepCount > 1 ? tr("tip.turn", { t: req.turn ?? 0, n: req.stepCount }) : tr("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 });
517
- return head + " \xB7 " + fmtTime2(req.time) + " \xB7 " + tr("tip.total", { n: fmt3(req.total) }) + (req.prompt !== void 0 ? " \xB7 " + tr("tip.actual", { n: fmt3(req.prompt) }) : "");
523
+ const head = req.stepCount !== void 0 && req.stepCount > 1 ? t("tip.turn", { t: req.turn ?? 0, n: req.stepCount }) : t("tip.step", { t: req.turn ?? 0, s: req.step ?? 0 });
524
+ return head + " \xB7 " + fmtTime2(req.time) + " \xB7 " + t("tip.total", { n: fmt3(req.total) }) + (req.prompt !== void 0 ? " \xB7 " + t("tip.actual", { n: fmt3(req.prompt) }) : "");
518
525
  };
519
526
  const hoveredIdx = props.hoveredSeq !== null ? requests.findIndex((r) => r.seq === props.hoveredSeq) : -1;
520
527
  const hoveredReq = hoveredIdx >= 0 ? requests[hoveredIdx] : null;
@@ -610,7 +617,7 @@ function makeTrendChart(kit) {
610
617
  // src/client/components/contextModal.tsx
611
618
  var TREND_TURNS = 10;
612
619
  function makeContextModal(ctx, kit) {
613
- const { t, tr, fmt: fmt3 } = kit;
620
+ const { t, fmt: fmt3 } = kit;
614
621
  const sessions = ctx.get("sessions");
615
622
  const StackedBar = makeStackedBar(kit);
616
623
  const Legend = makeLegend(kit);
@@ -667,7 +674,7 @@ function makeContextModal(ctx, kit) {
667
674
  const head = data !== null ? headlineOf(data, pressure) : null;
668
675
  return /* @__PURE__ */ React.createElement("div", { className: "lc-modal-backdrop", onClick: close }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-card", onClick: (ev) => {
669
676
  ev.stopPropagation();
670
- } }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-head" }, /* @__PURE__ */ React.createElement("span", { className: "lc-modal-title" }, t("tab")), data !== null && (data.model || data.provider) ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : "")) : null, /* @__PURE__ */ React.createElement("button", { className: "lc-modal-close", "aria-label": t("cmd.close"), onClick: close }, "\xD7")), data === null || head === null ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("loading")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " " + tr("overview.ofWindow", { p: head.pct ?? 0 }) : " " + t("overview.estimate")), !head.estimated ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement("div", { className: "lc-card-title lc-modal-trend" }, t("trend.title")), turns.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("trend.empty")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
677
+ } }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-head" }, /* @__PURE__ */ React.createElement("span", { className: "lc-modal-title" }, t("tab")), data !== null && (data.model || data.provider) ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : "")) : null, /* @__PURE__ */ React.createElement("button", { className: "lc-modal-close", "aria-label": t("cmd.close"), onClick: close }, "\xD7")), data === null || head === null ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("loading")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " tokens" : " " + t("overview.estimate")), head.pct !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-overview-pct" }, /* @__PURE__ */ React.createElement("b", null, head.pct + "%"), t("overview.used")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement("div", { className: "lc-card-title lc-modal-trend" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("trend.title"))), turns.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("trend.empty")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
671
678
  TrendChart,
672
679
  {
673
680
  requests: turns,
@@ -690,6 +697,7 @@ var STYLES = [
690
697
  ".lc-root { padding: 16px 20px 32px; overflow-y: auto; height: 100%; box-sizing: border-box; color: var(--dsw-alias-label-primary); font-size: 13px; }",
691
698
  ".lc-card { background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; padding: 14px 16px; margin-bottom: 14px; }",
692
699
  ".lc-card-title { font-weight: 600; margin-bottom: 10px; display: flex; align-items: baseline; gap: 8px; }",
700
+ ".lc-card-title-text { flex: none; white-space: nowrap; }",
693
701
  ".lc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 8px; }",
694
702
  ".lc-stat { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; min-width: 0; }",
695
703
  ".lc-stat-label { color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
@@ -700,9 +708,13 @@ var STYLES = [
700
708
  ".lc-gran-btn { border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 1; padding: 3px 8px; border-radius: 5px; cursor: pointer; font-family: inherit; }",
701
709
  ".lc-gran-btn:hover { color: var(--dsw-alias-label-primary); }",
702
710
  ".lc-gran-on, .lc-gran-on:hover { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }",
703
- ".lc-overview-num { margin-bottom: 8px; }",
704
- ".lc-overview-num b { font-size: 20px; }",
711
+ ".lc-overview-num { display: flex; align-items: baseline; gap: 6px; margin-bottom: 8px; }",
712
+ ".lc-overview-num > b { font-size: 20px; }",
705
713
  ".lc-overview-num span { color: var(--dsw-alias-label-secondary); }",
714
+ // The used-percentage figure is the emphasis of the line: pushed to the
715
+ // right edge, large and primary-colored, with a small "used" caption.
716
+ ".lc-overview-pct { margin-left: auto; font-size: 11px; }",
717
+ ".lc-overview-pct b { font-size: 20px; color: var(--dsw-alias-label-primary); margin-right: 4px; }",
706
718
  ".lc-stacked-wrap { position: relative; width: 100%; }",
707
719
  ".lc-stacked { display: flex; width: 100%; border-radius: 5px; overflow: hidden; background: rgba(128,128,128,0.18); position: relative; }",
708
720
  // Hover reference frame around the OCCUPIED region of the composition bar:
@@ -711,9 +723,16 @@ var STYLES = [
711
723
  // sits outside it). pointer-events: none keeps hover on the segments/free.
712
724
  // Deliberately high-contrast (label-primary) and thick so the frame reads at
713
725
  // a glance, and the other parts dim underneath it (`.lc-stacked-dim`).
714
- ".lc-occupied-box { position: absolute; top: 0; bottom: 0; left: 0; border: 2px dashed var(--dsw-alias-label-primary); border-radius: 5px; box-sizing: border-box; pointer-events: none; opacity: 1; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-2); }",
715
- ".lc-bar-tip { position: absolute; bottom: calc(100% + 6px); transform: translateX(-50%); z-index: 5; white-space: nowrap; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 3px 8px; font-size: 12px; color: var(--dsw-alias-label-primary); box-shadow: 0 2px 8px rgba(0,0,0,0.18); pointer-events: none; }",
726
+ ".lc-occupied-box { position: absolute; top: 0; bottom: 0; left: 0; border: 2px dashed var(--dsw-alias-label-primary); border-radius: 5px; box-sizing: border-box; pointer-events: none; opacity: 0; box-shadow: 0 0 0 1px var(--dsw-alias-bg-layer-2); transition: opacity 120ms ease; }",
727
+ ".lc-occupied-box-on { opacity: 1; }",
728
+ ".lc-bar-tip { position: absolute; bottom: calc(100% + 6px); transform: translateX(-50%); z-index: 5; white-space: nowrap; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 3px 8px; font-size: 12px; color: var(--dsw-alias-label-primary); box-shadow: 0 2px 8px rgba(0,0,0,0.18); pointer-events: none; opacity: 0; transition: opacity 120ms ease; }",
729
+ ".lc-bar-tip-on { opacity: 1; }",
716
730
  ".lc-stacked > div { height: 100%; }",
731
+ // Hover eases: the shared composition hover (dim + segment brighten, on the
732
+ // overview or the mirrored browser bar) glides in and out — same 120ms
733
+ // rhythm as the rest of the view.
734
+ ".lc-stacked-seg { transition: filter 120ms ease, opacity 120ms ease; }",
735
+ ".lc-stacked-free { transition: box-shadow 120ms ease, opacity 120ms ease; }",
717
736
  ".lc-stacked-seg-on { filter: brightness(1.18); }",
718
737
  ".lc-stacked-free-on { box-shadow: inset 0 0 0 1px var(--dsw-alias-label-secondary); border-radius: 3px; }",
719
738
  // Hover focus: everything except the hovered part (segment, legend chip, or
@@ -724,10 +743,14 @@ var STYLES = [
724
743
  ".lc-stacked-dim .lc-stacked-free { opacity: 0.35; }",
725
744
  ".lc-stacked-dim .lc-stacked-free-on { opacity: 1; }",
726
745
  ".lc-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; }",
727
- ".lc-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--dsw-alias-label-primary); }",
746
+ ".lc-chip { display: inline-flex; align-items: center; gap: 5px; color: var(--dsw-alias-label-primary); padding: 1px 6px; border-radius: 6px; transition: background-color 120ms ease; }",
728
747
  ".lc-chip i, .lc-detail-row i, .lc-node i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; }",
748
+ ".lc-chip i { transition: box-shadow 120ms ease; }",
729
749
  ".lc-chip em { font-style: normal; color: var(--dsw-alias-label-secondary); }",
730
- ".lc-chip-on { font-weight: 600; }",
750
+ // The selected chip glows with the shared hover tint (smoothly, like the
751
+ // browser rows) instead of snapping its weight; the dot ring on top marks
752
+ // the exact segment.
753
+ ".lc-chip-on { font-weight: 600; background: var(--dsw-alias-interactive-bg-hover); }",
731
754
  ".lc-chip-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }",
732
755
  ".lc-tools { margin-top: 10px; color: var(--dsw-alias-label-secondary); display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }",
733
756
  ".lc-tool-chip { background: var(--dsw-alias-bg-layer-2); border-radius: 4px; padding: 1px 7px; font-size: 12px; color: var(--dsw-alias-label-primary); }",
@@ -745,7 +768,7 @@ var STYLES = [
745
768
  ".lc-grid { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--dsw-alias-border-l1); pointer-events: none; }",
746
769
  ".lc-grid-top { top: 18px; }",
747
770
  ".lc-grid-mid { top: 74px; }",
748
- ".lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; transition: opacity 120ms ease; }",
771
+ ".lc-bar { position: relative; width: 14px; flex: none; height: 100%; display: flex; align-items: flex-end; cursor: pointer; border-radius: 2px; transition: opacity 120ms ease, background-color 120ms ease; }",
749
772
  // Turn-aware dimming: while a turn is focused, bars OUTSIDE the active
750
773
  // turn fade to 35% and the whole current turn stays fully opaque.
751
774
  ".lc-chart-dim .lc-bar { opacity: 0.35; }",
@@ -856,9 +879,14 @@ var STYLES = [
856
879
  ".lc-br-cats { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; }",
857
880
  ".lc-br-cat { border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }",
858
881
  ".lc-br-cat-empty { opacity: 0.55; }",
859
- ".lc-br-cat-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; padding: 7px 10px; cursor: pointer; text-align: left; }",
860
- ".lc-br-cat-row:hover { background: var(--dsw-alias-bg-layer-1); }",
861
- ".lc-br-cat-row i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; flex: none; }",
882
+ ".lc-br-cat-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; padding: 7px 10px; cursor: pointer; text-align: left; transition: background-color 120ms ease; }",
883
+ ".lc-br-cat-row:hover { background: var(--dsw-alias-interactive-bg-hover); }",
884
+ // Current-composition hover echo: the category row lit by the shared hover
885
+ // link (hovering the Current Composition card's bar/legend) looks hovered
886
+ // too — same tint as the physical hover, plus a ring on its color dot.
887
+ ".lc-br-cat-on { background: var(--dsw-alias-interactive-bg-hover); }",
888
+ ".lc-br-cat-on i { box-shadow: 0 0 0 1px var(--dsw-alias-brand-primary); }",
889
+ ".lc-br-cat-row i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; flex: none; transition: box-shadow 120ms ease; }",
862
890
  ".lc-br-cat-label { font-weight: 600; }",
863
891
  ".lc-br-cat-count { color: var(--dsw-alias-label-secondary); font-size: 12px; flex: 1; }",
864
892
  ".lc-br-chev { flex: none; width: 12px; color: var(--dsw-alias-label-secondary); transition: transform 120ms ease; }",
@@ -867,9 +895,9 @@ var STYLES = [
867
895
  ".lc-br-pct { flex: none; width: 36px; text-align: right; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
868
896
  ".lc-br-body { border-top: 1px solid var(--dsw-alias-border-l1); padding: 4px 6px; display: flex; flex-direction: column; gap: 2px; }",
869
897
  ".lc-br-elem { border-radius: 6px; }",
870
- ".lc-br-elem-on { background: var(--dsw-alias-bg-layer-1); }",
871
- ".lc-br-elem-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 5px 6px; cursor: pointer; text-align: left; border-radius: 6px; }",
872
- ".lc-br-elem-row:hover { background: var(--dsw-alias-bg-layer-1); }",
898
+ ".lc-br-elem-on { background: var(--dsw-alias-interactive-bg-active); }",
899
+ ".lc-br-elem-row { display: flex; align-items: center; gap: 8px; width: 100%; border: 0; background: transparent; color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 5px 6px; cursor: pointer; text-align: left; border-radius: 6px; transition: background-color 120ms ease; }",
900
+ ".lc-br-elem-row:hover { background: var(--dsw-alias-interactive-bg-hover); }",
873
901
  ".lc-br-tag { flex: none; font-size: 11px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l1); border-radius: 4px; padding: 0 6px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
874
902
  ".lc-br-preview { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
875
903
  ".lc-br-time { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; }",
@@ -930,7 +958,7 @@ function makeNodeText(kit) {
930
958
  };
931
959
  }
932
960
  function makeNodeList(kit) {
933
- const { t, tr, fmt: fmt3, fmtTime: fmtTime2 } = kit;
961
+ const { t, fmt: fmt3, fmtTime: fmtTime2 } = kit;
934
962
  const nodeText = makeNodeText(kit);
935
963
  return function NodeList(props) {
936
964
  if (props.nodes.length === 0) {
@@ -939,7 +967,7 @@ function makeNodeList(kit) {
939
967
  const catColor = {};
940
968
  for (const c of CATS) catColor[c.key] = c.color;
941
969
  const rows = props.nodes.slice().reverse();
942
- return /* @__PURE__ */ React.createElement("div", { className: "lc-nodes" }, props.dropped > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-nodes-more" }, tr("nodes.more", { n: props.dropped })) : null, rows.map((n) => {
970
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-nodes" }, props.dropped > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-nodes-more" }, t("nodes.more", { n: props.dropped })) : null, rows.map((n) => {
943
971
  const text = nodeText(n);
944
972
  return /* @__PURE__ */ React.createElement("div", { key: n.seq, className: "lc-node" }, /* @__PURE__ */ React.createElement("i", { style: { background: catColor[n.cat] || "#999" } }), /* @__PURE__ */ React.createElement("span", { className: "lc-node-preview", title: text }, text), typeof n.time === "number" ? /* @__PURE__ */ React.createElement("span", { className: "lc-node-time" }, fmtTime2(n.time)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-node-tokens" }, fmt3(n.tokens)));
945
973
  }));
@@ -991,7 +1019,7 @@ function NodeContent(props) {
991
1019
  return /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, props.hint));
992
1020
  }
993
1021
  function makeContextBrowser(kit, StackedBar) {
994
- const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
1022
+ const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
995
1023
  const nodeText = makeNodeText(kit);
996
1024
  const catColor = {};
997
1025
  for (const c of CATS) catColor[c.key] = c.color;
@@ -1032,11 +1060,19 @@ function makeContextBrowser(kit, StackedBar) {
1032
1060
  React.useEffect(() => {
1033
1061
  if (!hasMore && missingSeq !== null && !exhausted) setExhausted(true);
1034
1062
  }, [hasMore, missingSeq, exhausted]);
1063
+ const pinSeq = props.pinSeq;
1064
+ React.useEffect(() => {
1065
+ setSel(pinSeq === null || pinSeq === void 0 ? "live" : pinSeq);
1066
+ setOpenCat(null);
1067
+ setOpenElem(null);
1068
+ }, [pinSeq]);
1035
1069
  const awaiting = missingSeq !== null && !exhausted && loadOlderHistory !== void 0 && hasMore;
1036
1070
  const requests = data.requests || [];
1037
1071
  const hoverReq = props.previewSeq !== null && props.previewSeq !== void 0 ? requests.find((r) => r.seq === props.previewSeq) ?? null : null;
1038
1072
  const req = hoverReq ?? (sel === "live" ? null : requests.find((r) => r.seq === sel) ?? null);
1039
1073
  const seq = req !== null ? req.seq : null;
1074
+ const linked = req === null && props.onHoverKey !== void 0;
1075
+ const linkKey = linked && props.hoverKey !== null && props.hoverKey !== "free" ? props.hoverKey : null;
1040
1076
  const view = assemble(data, headers, seq);
1041
1077
  const breakdown = req !== null ? req : data.current;
1042
1078
  const parts = partsOf(breakdown);
@@ -1124,7 +1160,7 @@ function makeContextBrowser(kit, StackedBar) {
1124
1160
  );
1125
1161
  });
1126
1162
  };
1127
- return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("browser.title"), /* @__PURE__ */ React.createElement(
1163
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("browser.title")), /* @__PURE__ */ React.createElement(
1128
1164
  "select",
1129
1165
  {
1130
1166
  className: "lc-br-pick",
@@ -1134,15 +1170,43 @@ function makeContextBrowser(kit, StackedBar) {
1134
1170
  }
1135
1171
  },
1136
1172
  /* @__PURE__ */ React.createElement("option", { value: "live" }, t("browser.live")),
1137
- requests.slice().reverse().map((r) => /* @__PURE__ */ React.createElement("option", { key: r.seq, value: String(r.seq) }, tr("detail.step", { t: r.turn ?? 0, s: r.step ?? 0 }) + " \xB7 " + fmtTime2(r.time)))
1138
- )), /* @__PURE__ */ React.createElement("div", { className: "lc-br-meta" }, /* @__PURE__ */ React.createElement("b", null, req !== null ? tr("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 }) : t("browser.liveNow")), req !== null ? /* @__PURE__ */ React.createElement("span", null, fmtTime2(req.time)) : null, hoverReq !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("browser.preview")) : null, /* @__PURE__ */ React.createElement("span", null, tr("detail.estTotal", { n: fmt3(total) })), req !== null && req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, tr("detail.actual", { n: fmt3(req.prompt) })) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts, height: 10 }), view.missingLive > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, tr("browser.missingLive", { n: view.missingLive })) : null, view.approximate ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.approx")) : null, /* @__PURE__ */ React.createElement("div", { className: "lc-br-cats" }, CATS.map((c) => {
1173
+ requests.slice().reverse().map((r) => /* @__PURE__ */ React.createElement("option", { key: r.seq, value: String(r.seq) }, t("detail.step", { t: r.turn ?? 0, s: r.step ?? 0 }) + " \xB7 " + fmtTime2(r.time)))
1174
+ )), /* @__PURE__ */ React.createElement("div", { className: "lc-br-meta" }, /* @__PURE__ */ React.createElement("b", null, req !== null ? t("detail.step", { t: req.turn ?? 0, s: req.step ?? 0 }) : t("browser.liveNow")), req !== null ? /* @__PURE__ */ React.createElement("span", null, fmtTime2(req.time)) : null, hoverReq !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("browser.preview")) : null, /* @__PURE__ */ React.createElement("span", null, t("detail.estTotal", { n: fmt3(total) })), req !== null && req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-actual" }, t("detail.actual", { n: fmt3(req.prompt) })) : null), /* @__PURE__ */ React.createElement("div", { className: "lc-br-bar" }, /* @__PURE__ */ React.createElement(
1175
+ StackedBar,
1176
+ {
1177
+ parts,
1178
+ height: 10,
1179
+ hoverKey: linked ? linkKey : void 0,
1180
+ onHoverKey: linked ? props.onHoverKey : void 0,
1181
+ tip: false
1182
+ }
1183
+ )), view.missingLive > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.missingLive", { n: view.missingLive })) : null, view.approximate ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.approx")) : null, /* @__PURE__ */ React.createElement("div", { className: "lc-br-cats" }, CATS.map((c) => {
1139
1184
  const count = toolCount(c.key);
1140
1185
  const v = breakdown[c.key] || 0;
1141
1186
  const openable = count > 0 || (c.key === "system" || c.key === "tools") && view.header === null;
1142
1187
  const open = openCat === c.key && openable;
1143
- return /* @__PURE__ */ React.createElement("div", { key: c.key, className: "lc-br-cat" + (openable ? "" : " lc-br-cat-empty") }, /* @__PURE__ */ React.createElement("button", { type: "button", className: "lc-br-cat-row", onClick: () => {
1144
- toggleCat(c.key);
1145
- } }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "\u25B8"), /* @__PURE__ */ React.createElement("i", { style: { background: c.color } }), /* @__PURE__ */ React.createElement("span", { className: "lc-br-cat-label" }, catLabel(c.key)), /* @__PURE__ */ React.createElement("span", { className: "lc-br-cat-count" }, tr("browser.items", { n: count })), /* @__PURE__ */ React.createElement("span", { className: "lc-br-tokens" }, "\u2248" + fmt3(v)), /* @__PURE__ */ React.createElement("span", { className: "lc-br-pct" }, total > 0 ? Math.round(v / total * 100) + "%" : "")), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-body" }, catBody(c.key)) : null);
1188
+ return /* @__PURE__ */ React.createElement("div", { key: c.key, className: "lc-br-cat" + (openable ? "" : " lc-br-cat-empty") }, /* @__PURE__ */ React.createElement(
1189
+ "button",
1190
+ {
1191
+ type: "button",
1192
+ className: "lc-br-cat-row" + (linked && props.hoverKey === c.key ? " lc-br-cat-on" : ""),
1193
+ onMouseEnter: linked ? () => {
1194
+ if (props.onHoverKey !== void 0) props.onHoverKey(c.key);
1195
+ } : void 0,
1196
+ onMouseLeave: linked ? () => {
1197
+ if (props.onHoverKey !== void 0) props.onHoverKey(null);
1198
+ } : void 0,
1199
+ onClick: () => {
1200
+ toggleCat(c.key);
1201
+ }
1202
+ },
1203
+ /* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "\u25B8"),
1204
+ /* @__PURE__ */ React.createElement("i", { style: { background: c.color } }),
1205
+ /* @__PURE__ */ React.createElement("span", { className: "lc-br-cat-label" }, catLabel(c.key)),
1206
+ /* @__PURE__ */ React.createElement("span", { className: "lc-br-cat-count" }, t("browser.items", { n: count })),
1207
+ /* @__PURE__ */ React.createElement("span", { className: "lc-br-tokens" }, "\u2248" + fmt3(v)),
1208
+ /* @__PURE__ */ React.createElement("span", { className: "lc-br-pct" }, total > 0 ? Math.round(v / total * 100) + "%" : "")
1209
+ ), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-body" }, catBody(c.key)) : null);
1146
1210
  })));
1147
1211
  };
1148
1212
  }
@@ -1158,22 +1222,18 @@ function fmt(n) {
1158
1222
  return String(Math.round(n));
1159
1223
  }
1160
1224
  function fmtTime(t) {
1161
- const d = new Date(t);
1162
- function p(x) {
1163
- return (x < 10 ? "0" : "") + x;
1164
- }
1165
- return p(d.getHours()) + ":" + p(d.getMinutes()) + ":" + p(d.getSeconds());
1225
+ return new Date(t).toLocaleTimeString("en-GB", { hour12: false });
1166
1226
  }
1167
1227
 
1168
1228
  // src/client/components/events.tsx
1169
1229
  var EVENT_ICONS = { compaction: "\u2702", prune: "\u2702", inject: "\uFF0B", model: "\u21C4" };
1170
- function makeEventText(t, tr) {
1230
+ function makeEventText(t) {
1171
1231
  function eventLabel(ev) {
1172
- if (ev.kind === "compaction") return tr("ev.compaction", { n: ev.count || 0 });
1232
+ if (ev.kind === "compaction") return t("ev.compaction", { n: ev.count || 0 });
1173
1233
  if (ev.kind === "prune") return t("ev.prune");
1174
- if (ev.kind === "model") return tr("ev.model", { a: ev.from || "?", b: ev.to || "?" });
1234
+ if (ev.kind === "model") return t("ev.model", { a: ev.from || "?", b: ev.to || "?" });
1175
1235
  if (ev.kind === "inject") {
1176
- if (ev.sub === "skill") return tr("ev.skill", { name: ev.name || "?" });
1236
+ if (ev.sub === "skill") return t("ev.skill", { name: ev.name || "?" });
1177
1237
  const base = t("form." + (ev.form || "context"));
1178
1238
  return ev.name ? base + " \xB7 " + ev.name : base;
1179
1239
  }
@@ -1228,7 +1288,7 @@ function makeEventList(kit) {
1228
1288
 
1229
1289
  // src/client/meta.ts
1230
1290
  var PLUGIN_NAME = "dsh-context";
1231
- var PLUGIN_VERSION = true ? "0.13.0" : "0.0.0-dev";
1291
+ var PLUGIN_VERSION = true ? "0.15.0" : "0.0.0-dev";
1232
1292
  var PLUGIN_REPO = true ? "https://github.com/bowenliang123/dsh-context" : "https://github.com/bowenliang123/dsh-context";
1233
1293
  var PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
1234
1294
 
@@ -1276,7 +1336,7 @@ function makePluginInfo(kit) {
1276
1336
  const update = latest !== null && isNewerVersion(latest, PLUGIN_VERSION) ? latest : null;
1277
1337
  const nameValue = [PLUGIN_NAME + " (v" + PLUGIN_VERSION + ")"];
1278
1338
  if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", { key: "update", className: "lc-pi-update" }, "\u2191 v" + update));
1279
- return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("plugin.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("plugin.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-pi-grid" }, row(t("plugin.name"), nameValue, PLUGIN_REPO + "/releases"), row(t("plugin.github"), PLUGIN_REPO_SHORT, PLUGIN_REPO)));
1339
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("plugin.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("plugin.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-pi-grid" }, row(t("plugin.name"), nameValue, PLUGIN_REPO + "/releases"), row(t("plugin.github"), PLUGIN_REPO_SHORT, PLUGIN_REPO)));
1280
1340
  };
1281
1341
  }
1282
1342
 
@@ -1296,7 +1356,7 @@ function makeStatsBoard(kit) {
1296
1356
  else if (ev.kind === "inject") injects++;
1297
1357
  }
1298
1358
  const cell = (label, value) => /* @__PURE__ */ React.createElement("div", { className: "lc-stat" }, /* @__PURE__ */ React.createElement("span", { className: "lc-stat-label" }, label), /* @__PURE__ */ React.createElement("b", { className: "lc-stat-value" }, value));
1299
- return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col lc-col-stats" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("stats.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("stats.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("stats.turns"), fmt3(turns.size)), cell(t("stats.steps"), fmt3(steps)), cell(t("stats.injects"), fmt3(injects)), cell(t("stats.compactions"), fmt3(compactions)), cell(t("stats.prunes"), fmt3(prunes))));
1359
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col lc-col-stats" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("stats.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("stats.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("stats.turns"), fmt3(turns.size)), cell(t("stats.steps"), fmt3(steps)), cell(t("stats.injects"), fmt3(injects)), cell(t("stats.compactions"), fmt3(compactions)), cell(t("stats.prunes"), fmt3(prunes))));
1300
1360
  };
1301
1361
  }
1302
1362
 
@@ -1304,7 +1364,7 @@ function makeStatsBoard(kit) {
1304
1364
  var viewScroll = /* @__PURE__ */ new Map();
1305
1365
  var EVENT_KINDS = ["inject", "compaction", "prune", "model"];
1306
1366
  function makeContextView(ctx, kit) {
1307
- const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
1367
+ const { t, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
1308
1368
  const StackedBar = makeStackedBar(kit);
1309
1369
  const Legend = makeLegend(kit);
1310
1370
  const TrendChart = makeTrendChart(kit);
@@ -1388,9 +1448,9 @@ function makeContextView(ctx, kit) {
1388
1448
  }
1389
1449
  }
1390
1450
  const head = headlineOf(data, pressure);
1391
- return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement("div", { className: "lc-cols lc-head" }, /* @__PURE__ */ React.createElement(StatsBoard, { requests, events }), /* @__PURE__ */ React.createElement(PluginInfo, null)), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("overview.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : ""))), /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " " + tr("overview.ofWindow", { p: head.pct ?? 0 }) : " " + t("overview.estimate")), !head.estimated ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), data.toolList && data.toolList.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-tools" }, t("tools.top"), data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
1451
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement("div", { className: "lc-cols lc-head" }, /* @__PURE__ */ React.createElement(StatsBoard, { requests, events }), /* @__PURE__ */ React.createElement(PluginInfo, null)), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("overview.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : ""))), /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " tokens" : " " + t("overview.estimate")), head.pct !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-overview-pct" }, /* @__PURE__ */ React.createElement("b", null, head.pct + "%"), t("overview.used")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), data.toolList && data.toolList.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-tools" }, t("tools.top"), data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
1392
1452
  return /* @__PURE__ */ React.createElement("span", { key: tool.name, className: "lc-tool-chip" }, tool.name + " " + fmt3(tool.tokens));
1393
- }), data.toolList.length > 5 ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, " " + tr("tools.more", { n: data.toolList.length })) : null) : null), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("trend.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("trend.hint")), /* @__PURE__ */ React.createElement("div", { className: "lc-gran" }, /* @__PURE__ */ React.createElement(
1453
+ }), data.toolList.length > 5 ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, " " + t("tools.more", { n: data.toolList.length })) : null) : null), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("trend.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("trend.hint")), /* @__PURE__ */ React.createElement("div", { className: "lc-gran" }, /* @__PURE__ */ React.createElement(
1394
1454
  "button",
1395
1455
  {
1396
1456
  className: "lc-gran-btn" + (granularity === "step" ? " lc-gran-on" : ""),
@@ -1429,9 +1489,12 @@ function makeContextView(ctx, kit) {
1429
1489
  headers,
1430
1490
  useSession: props.useSession,
1431
1491
  loadOlderHistory: props.loadOlderHistory,
1432
- previewSeq: hoveredSeq
1492
+ previewSeq: hoveredSeq,
1493
+ pinSeq: pinnedReq !== null ? pinnedReq.seq : null,
1494
+ hoverKey: hoverCat,
1495
+ onHoverKey: setHoverCat
1433
1496
  }
1434
- ))), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("events.title"), /* @__PURE__ */ React.createElement("div", { className: "lc-kinds" }, EVENT_KINDS.map((k) => /* @__PURE__ */ React.createElement(
1497
+ ))), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("events.title")), /* @__PURE__ */ React.createElement("div", { className: "lc-kinds" }, EVENT_KINDS.map((k) => /* @__PURE__ */ React.createElement(
1435
1498
  "button",
1436
1499
  {
1437
1500
  key: k,
@@ -1441,16 +1504,15 @@ function makeContextView(ctx, kit) {
1441
1504
  }
1442
1505
  },
1443
1506
  t("kind." + k)
1444
- )))), /* @__PURE__ */ React.createElement(EventList, { events: shownEvents })), /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("nodes.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("nodes.hint"))), /* @__PURE__ */ React.createElement(NodeList, { nodes, dropped: data.droppedNodes || 0 }))), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer")));
1507
+ )))), /* @__PURE__ */ React.createElement(EventList, { events: shownEvents })), /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("nodes.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("nodes.hint"))), /* @__PURE__ */ React.createElement(NodeList, { nodes, dropped: data.droppedNodes || 0 }))), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer")));
1445
1508
  };
1446
1509
  }
1447
1510
 
1448
1511
  // src/client/viewkit.ts
1449
1512
  function makeViewKit(t) {
1450
- const { eventLabel, eventAt } = makeEventText(t, t);
1513
+ const { eventLabel, eventAt } = makeEventText(t);
1451
1514
  return {
1452
1515
  t,
1453
- tr: t,
1454
1516
  fmt,
1455
1517
  fmtTime,
1456
1518
  catLabel: (key) => t("cat." + key),
package/lib/index.js CHANGED
@@ -18,14 +18,7 @@ var Config = z.preprocess(
18
18
  }).strict()
19
19
  );
20
20
  function resolveBounds(config) {
21
- const parsed = Config.parse(config ?? {});
22
- return {
23
- maxRequestSteps: parsed.maxRequestSteps,
24
- maxKeptTurns: parsed.maxKeptTurns,
25
- maxEvents: parsed.maxEvents,
26
- maxNodes: parsed.maxNodes,
27
- maxArchiveNodes: parsed.maxArchiveNodes
28
- };
21
+ return Config.parse(config ?? {});
29
22
  }
30
23
 
31
24
  // src/host/headers.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
5
5
  "author": "bowenliang123",
6
6
  "repository": {
@@ -31,7 +31,8 @@
31
31
  "test": "npm run typecheck && node tests/host.test.mjs && node tests/client.test.mjs",
32
32
  "release": "bash scripts/publish.sh",
33
33
  "hooks": "bash scripts/install-hooks.sh",
34
- "repro": "node tests/repro-real-react.mjs"
34
+ "repro": "node tests/repro-real-react.mjs",
35
+ "social-preview": "node docs/social-preview/generate.mjs"
35
36
  },
36
37
  "dsh": {
37
38
  "bundle": {
@@ -58,7 +59,6 @@
58
59
  ],
59
60
  "license": "Apache-2.0",
60
61
  "dependencies": {
61
- "playwright-core": "^1.62.1",
62
62
  "zod": "^4.4.3"
63
63
  },
64
64
  "devDependencies": {
@@ -70,6 +70,7 @@
70
70
  "@types/react": "^18.3.31",
71
71
  "esbuild": "^0.28.2",
72
72
  "jsdom": "^30.0.1",
73
+ "playwright-core": "^1.62.1",
73
74
  "react": "^18.3.1",
74
75
  "react-dom": "^18.3.1",
75
76
  "typescript": "^7.0.2"