dsh-better-display 0.3.5 → 0.3.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.6 — 2026-10-05
4
+
5
+ - Keep a running tool's elapsed clock counting from the call's stamped start instead of the moment the row mounted. Switching sessions and back — or even reloading the page mid-run — now continues the count rather than restarting it at zero; draft-only calls remember their first-seen time per call id until they settle.
6
+
3
7
  ## 0.3.5 — 2026-10-04
4
8
 
5
9
  - Release finished expand animations so process rows return to their natural height. Content changes after expanding no longer leave blank space or clip steps until a page refresh.
package/README.en.md CHANGED
@@ -9,7 +9,7 @@
9
9
  Open **Settings → Plugins → Add plugin** and enter this in “Package name or address”:
10
10
 
11
11
  ```text
12
- github:aa2246740/dsh-better-display#v0.3.5
12
+ github:aa2246740/dsh-better-display#v0.3.6
13
13
  ```
14
14
 
15
15
  The desktop plugin manager owns the Desktop profile and its bundled package manager. This release includes built `lib/`; normal use needs no clone, build, or DSHX installation. Follow the app if it asks you to reload or reopen after installation.
@@ -17,7 +17,7 @@ The desktop plugin manager owns the Desktop profile and its bundled package mana
17
17
  ### Web CLI
18
18
 
19
19
  ```sh
20
- dsh plugin --profile web add github:aa2246740/dsh-better-display#v0.3.5
20
+ dsh plugin --profile web add github:aa2246740/dsh-better-display#v0.3.6
21
21
  ```
22
22
 
23
23
  This official CLI command writes only the `web` profile; it cannot modify the Desktop App profile. **pnpm** must be on PATH. For an already-running Web Host, reopen that Host once and reload the page because bundles are read at boot.
@@ -34,7 +34,7 @@ From a local checkout or tarball (development/local testing):
34
34
 
35
35
  ```sh
36
36
  dsh plugin --profile web add ./dsh-better-display
37
- dsh plugin --profile web add ./dsh-better-display-0.3.5.tgz
37
+ dsh plugin --profile web add ./dsh-better-display-0.3.6.tgz
38
38
  ```
39
39
 
40
40
  `dsh.bundle` is captured at Host boot. Do not also insert the same row by hand in the profile `cordis.patch.yml`, or it will mount twice.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  打开 **设置 → 插件 → 添加插件**,在“包名或地址”中输入:
10
10
 
11
11
  ```text
12
- github:aa2246740/dsh-better-display#v0.3.5
12
+ github:aa2246740/dsh-better-display#v0.3.6
13
13
  ```
14
14
 
15
15
  桌面端的插件管理器负责 Desktop profile 和其内置包管理器。本发布已包含编译好的 `lib/`;普通使用不需要 clone、构建或安装 DSHX。若应用在安装完成后提示刷新或重新打开,请按提示完成。
@@ -17,7 +17,7 @@ github:aa2246740/dsh-better-display#v0.3.5
17
17
  ### Web CLI
18
18
 
19
19
  ```sh
20
- dsh plugin --profile web add github:aa2246740/dsh-better-display#v0.3.5
20
+ dsh plugin --profile web add github:aa2246740/dsh-better-display#v0.3.6
21
21
  ```
22
22
 
23
23
  这条官方 CLI 命令只写入 `web` profile,不能修改 Desktop App 的 profile。本机 PATH 上需要有 **pnpm**。对于已经运行的 Web Host,请重新打开该 Host 一次,再刷新网页;插件 bundle 会在启动时读取。
@@ -34,7 +34,7 @@ dsh plugin --profile web add github:aa2246740/dsh-better-display#v0.3.5
34
34
 
35
35
  ```sh
36
36
  dsh plugin --profile web add ./dsh-better-display
37
- dsh plugin --profile web add ./dsh-better-display-0.3.5.tgz
37
+ dsh plugin --profile web add ./dsh-better-display-0.3.6.tgz
38
38
  ```
39
39
 
40
40
  `dsh.bundle` 是开机捕获的。不要再往 profile 的 `cordis.patch.yml` 手写同一条 insert,会重复挂载。
package/lib/client.js CHANGED
@@ -371,6 +371,44 @@ window.__ModuleLoader__.load({
371
371
  if (facts.exitCode === 0) return "succeeded";
372
372
  return "returned";
373
373
  }
374
+ /**
375
+ * First-seen time for calls the host has not stamped yet. A draft that
376
+ * precedes a landed call carries no timestamp, so without this memory the
377
+ * running clock would restart at zero on every remount — e.g. switching to
378
+ * another conversation and back while the call is still preparing. Keyed by
379
+ * call id, dropped when the call leaves the running phases.
380
+ */
381
+ const clockFirstSeen = /* @__PURE__ */ new Map();
382
+ const CLOCK_FIRST_SEEN_LIMIT = 512;
383
+ /**
384
+ * Session-stamped start of a call, or null while only a draft exists.
385
+ * A settled result carries `callTime`, the paired tool/call event's time; a
386
+ * still-running call head stamps the same instant in its own `time` field.
387
+ */
388
+ function callStartTime(entry) {
389
+ const block = entry.block;
390
+ if (!block) return null;
391
+ return "kind" in block ? block.callTime : block.time;
392
+ }
393
+ /**
394
+ * The instant a still-running call's elapsed clock counts from. The stamped
395
+ * time wins so a remounted view keeps counting from the real start; an
396
+ * un-stamped draft falls back to when this view first saw that call id, which
397
+ * survives remounts for the same reason.
398
+ */
399
+ function runningClockBase(callId, block, now = Date.now()) {
400
+ const stamped = callStartTime({ block });
401
+ if (stamped != null) return stamped;
402
+ const seen = clockFirstSeen.get(callId);
403
+ if (seen !== void 0) return seen;
404
+ if (clockFirstSeen.size >= CLOCK_FIRST_SEEN_LIMIT) clockFirstSeen.delete(clockFirstSeen.keys().next().value);
405
+ clockFirstSeen.set(callId, now);
406
+ return now;
407
+ }
408
+ /** Forget a call's first-seen time once its clock is no longer live. */
409
+ function forgetCallClock(callId) {
410
+ clockFirstSeen.delete(callId);
411
+ }
374
412
  function activitySummary(entry) {
375
413
  const { name, raw } = toolIdentity(entry);
376
414
  const args = inputFields(raw);
@@ -10872,7 +10910,11 @@ ${trimmed}
10872
10910
  const pairs = toPairs(list);
10873
10911
  let pairIndex = -1;
10874
10912
  while (++pairIndex < pairs.length) visitParents(tree, "text", visitor);
10875
- /** @type {BuildVisitor<Root, 'text'>} */
10913
+ /**
10914
+ * Visit a text node, and handle it if it is not in an ignored parent.
10915
+ *
10916
+ * @type {BuildVisitor<Root, 'text'>}
10917
+ */
10876
10918
  function visitor(node, parents) {
10877
10919
  let index = -1;
10878
10920
  /** @type {Parents | undefined} */
@@ -10890,11 +10932,11 @@ ${trimmed}
10890
10932
  * Handle a text node which is not in an ignored parent.
10891
10933
  *
10892
10934
  * @param {Text} node
10893
- * Text node.
10935
+ * Text node to search in.
10894
10936
  * @param {Array<Parents>} parents
10895
- * Parents.
10937
+ * Ancestors of `node`.
10896
10938
  * @returns {VisitorResult}
10897
- * Result.
10939
+ * Where to continue visiting.
10898
10940
  */
10899
10941
  function handler(node, parents) {
10900
10942
  const parent = parents[parents.length - 1];
@@ -10922,12 +10964,12 @@ ${trimmed}
10922
10964
  } : void 0;
10923
10965
  if (value === false) find.lastIndex = position + 1;
10924
10966
  else {
10925
- if (start !== position) nodes.push({
10967
+ if (start !== position) add({
10926
10968
  type: "text",
10927
10969
  value: node.value.slice(start, position)
10928
10970
  });
10929
- if (Array.isArray(value)) nodes.push(...value);
10930
- else if (value) nodes.push(value);
10971
+ if (Array.isArray(value)) for (const child of value) add(child);
10972
+ else if (value) add(value);
10931
10973
  start = position + match[0].length;
10932
10974
  change = true;
10933
10975
  }
@@ -10935,13 +10977,26 @@ ${trimmed}
10935
10977
  match = find.exec(node.value);
10936
10978
  }
10937
10979
  if (change) {
10938
- if (start < node.value.length) nodes.push({
10980
+ if (start < node.value.length) add({
10939
10981
  type: "text",
10940
10982
  value: node.value.slice(start)
10941
10983
  });
10942
10984
  parent.children.splice(index, 1, ...nodes);
10943
10985
  } else nodes = [node];
10944
10986
  return index + nodes.length;
10987
+ /**
10988
+ * Add a node.
10989
+ *
10990
+ * @param {PhrasingContent} child
10991
+ * Node to add.
10992
+ * @returns {undefined}
10993
+ * Nothing.
10994
+ */
10995
+ function add(child) {
10996
+ const previous = nodes[nodes.length - 1];
10997
+ if (previous && previous.type === "text" && child.type === "text") previous.value += child.value;
10998
+ else nodes.push(child);
10999
+ }
10945
11000
  }
10946
11001
  }
10947
11002
  /**
@@ -10953,9 +11008,9 @@ ${trimmed}
10953
11008
  * Clean pairs.
10954
11009
  */
10955
11010
  function toPairs(tupleOrList) {
11011
+ if (!Array.isArray(tupleOrList)) throw new TypeError("Expected find and replace tuple or list of tuples");
10956
11012
  /** @type {Pairs} */
10957
11013
  const result = [];
10958
- if (!Array.isArray(tupleOrList)) throw new TypeError("Expected find and replace tuple or list of tuples");
10959
11014
  /** @type {FindAndReplaceList} */
10960
11015
  const list = !tupleOrList[0] || Array.isArray(tupleOrList[0]) ? tupleOrList : [tupleOrList];
10961
11016
  let index = -1;
@@ -10969,9 +11024,9 @@ ${trimmed}
10969
11024
  * Turn a find into an expression.
10970
11025
  *
10971
11026
  * @param {Find} find
10972
- * Find.
11027
+ * Pattern to find.
10973
11028
  * @returns {RegExp}
10974
- * Expression.
11029
+ * Global expression.
10975
11030
  */
10976
11031
  function toExpression(find) {
10977
11032
  return typeof find === "string" ? new RegExp(escapeStringRegexp(find), "g") : find;
@@ -10980,9 +11035,9 @@ ${trimmed}
10980
11035
  * Turn a replace into a function.
10981
11036
  *
10982
11037
  * @param {Replace} replace
10983
- * Replace.
11038
+ * Thing to replace with.
10984
11039
  * @returns {ReplaceFunction}
10985
- * Function.
11040
+ * Function that returns that thing.
10986
11041
  */
10987
11042
  function toFunction(replace) {
10988
11043
  return typeof replace === "function" ? replace : function() {
@@ -45062,19 +45117,22 @@ ${trimmed}
45062
45117
  const showState = phase === "preparing" || phase === "running" || phase === "failed" || phase === "interrupted";
45063
45118
  const running = phase === "preparing" || phase === "running";
45064
45119
  const [liveMs, setLiveMs] = (0, react.useState)(null);
45065
- const openedAt = (0, react.useRef)(null);
45066
45120
  (0, react.useEffect)(() => {
45067
45121
  if (!running) {
45068
- openedAt.current = null;
45122
+ forgetCallClock(entry.callId);
45069
45123
  setLiveMs(null);
45070
45124
  return;
45071
45125
  }
45072
- const base = (block && "kind" in block && block.callTime != null ? block.callTime : null) ?? (openedAt.current ??= Date.now());
45126
+ const base = runningClockBase(entry.callId, block);
45073
45127
  const tick = () => setLiveMs(Math.max(0, Date.now() - base));
45074
45128
  tick();
45075
45129
  const timer = setInterval(tick, 200);
45076
45130
  return () => clearInterval(timer);
45077
- }, [running, block]);
45131
+ }, [
45132
+ running,
45133
+ block,
45134
+ entry.callId
45135
+ ]);
45078
45136
  const elapsed = block && "kind" in block && block.callTime != null ? Math.max(0, block.time - block.callTime) : null;
45079
45137
  const shownMs = liveMs ?? (elapsed !== null && elapsed >= SLOW_TOOL_MS ? elapsed : null);
45080
45138
  const rawResult = (0, react.useMemo)(() => {