dsh-turn-navigator 0.4.0 → 0.4.1

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.1] - 2026-09-03
4
+
5
+ ### Fixed
6
+
7
+ - **Rail tooltip/aria now show the TRUE turn number** — the "第 N 轮" label previously used `entry.index` (the position within each source's own subset: every history page starts at 1, and window-only extras start at 1 too), so a merged long session could read "第 38 轮" followed by "第 2 轮". The label now comes from `entry.turn` (absolute, consistent with the "正在定位第 N 轮" bubble), and the merged list re-derives `index` purely as its sorted list position — `src/client/TurnNavRail.tsx`, `src/client/turns.ts`.
8
+ - **Rail viewport follows the active turn** — the rail is capped at 30vh with an internal scrollbar, so in a long session the current turn's capsule sat outside the visible band on open/scroll. A new effect scrolls the active capsule into the rail's viewport (centered) whenever it leaves it, with a pointer-over guard so a user browsing the rail is never yanked — `src/client/TurnNavRail.tsx`.
9
+ - **Compile/runtime hardening on dsh 0.1.2+** — the newer `ClientContext` type surface dropped `connection.api` (the 0.1.2+ journal channel replaced the RPC); the browser side now reads it structurally (`(connection as { api?: HistoryApi })?.api`) so both older and newer hosts compile and run — `src/client/index.ts`.
10
+
3
11
  ## [0.4.0] - 2026-08-29
4
12
 
5
13
  ### Added
package/lib/client.js CHANGED
@@ -442,10 +442,10 @@ window.__ModuleLoader__.load({
442
442
  function clampFeedbackY(y) {
443
443
  return Math.max(24, Math.min(y, window.innerHeight - 24));
444
444
  }
445
- /** Tooltip body for one turn: index, time, full summary. */
445
+ /** Tooltip body for one turn: turn number, time, full summary. */
446
446
  function tooltipText(entry, t) {
447
447
  const time = formatTime(entry.startTime);
448
- const label = t("turnLabel", { n: String(entry.index) });
448
+ const label = t("turnLabel", { n: String(entry.turn) });
449
449
  const body = entry.fullText || entry.summary || t("noSummary");
450
450
  const lines = [label];
451
451
  if (time !== "") lines.push(time);
@@ -480,6 +480,7 @@ window.__ModuleLoader__.load({
480
480
  const railRef = (0, react.useRef)(null);
481
481
  const tipRef = (0, react.useRef)(null);
482
482
  const hoverScrollRef = (0, react.useRef)(null);
483
+ const pointerOverRailRef = (0, react.useRef)(false);
483
484
  (0, react.useEffect)(() => {
484
485
  let timer = null;
485
486
  const check = () => {
@@ -545,7 +546,10 @@ window.__ModuleLoader__.load({
545
546
  if (historyTurns.length === 0) return windowTurns;
546
547
  const historySet = new Set(historyTurns.map((entry) => entry.turn));
547
548
  const extras = windowTurns.filter((entry) => !historySet.has(entry.turn));
548
- return [...historyTurns, ...extras].sort((a, b) => a.turn - b.turn);
549
+ return [...historyTurns, ...extras].sort((a, b) => a.turn - b.turn).map((entry, i) => ({
550
+ ...entry,
551
+ index: i + 1
552
+ }));
549
553
  }, [historyTurns, windowTurns]);
550
554
  const [activeTurn, setActiveTurn] = (0, react.useState)(null);
551
555
  (0, react.useEffect)(() => {
@@ -582,6 +586,19 @@ window.__ModuleLoader__.load({
582
586
  if (frame !== null) cancelAnimationFrame(frame);
583
587
  };
584
588
  }, [turns.length]);
589
+ (0, react.useEffect)(() => {
590
+ if (activeTurn === null || pointerOverRailRef.current) return;
591
+ const index = turns.findIndex((entry) => entry.turn === activeTurn);
592
+ if (index < 0) return;
593
+ const rail = railRef.current;
594
+ if (rail === null) return;
595
+ const btn = rail.querySelectorAll(".tn-cap-btn")[index];
596
+ if (btn === void 0) return;
597
+ const railRect = rail.getBoundingClientRect();
598
+ const btnRect = btn.getBoundingClientRect();
599
+ if (btnRect.top >= railRect.top && btnRect.bottom <= railRect.bottom) return;
600
+ centerCapsule(index);
601
+ }, [activeTurn, turns]);
585
602
  (0, react.useEffect)(() => {
586
603
  const rail = railRef.current;
587
604
  if (rail === null) return;
@@ -741,7 +758,11 @@ window.__ModuleLoader__.load({
741
758
  className: `tn-wrap${officialRail ? " tn-nudge" : ""}`,
742
759
  role: "navigation",
743
760
  "aria-label": t("rail"),
761
+ onMouseEnter: () => {
762
+ pointerOverRailRef.current = true;
763
+ },
744
764
  onMouseLeave: () => {
765
+ pointerOverRailRef.current = false;
745
766
  setHoverIndex(-1);
746
767
  stopHoverScroll();
747
768
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-turn-navigator",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "DSH Smoothly Turn Nav (DSH STN): full-history piano-key turn rail for DeepSeek Harness (dsh) web conversations — see every turn at a glance, hover to preview, click to jump anywhere, and replace the official (non-disableable) turn rail.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",