shadok-ai 0.6.3 → 0.6.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test test/*.ts .claude/skills/shadok-ai-agents/test/*.test.mjs context/*/test/*.test.mjs",
package/public/index.html CHANGED
@@ -1902,7 +1902,7 @@
1902
1902
  request (src/csp.ts). A block without it does not run — which is what makes
1903
1903
  HTML an agent might write into the transcript harmless. -->
1904
1904
  <script type="module" nonce="__CSP_NONCE__">
1905
- import { extractLiveText } from "/live-text.js";
1905
+ import { extractLiveText, livePreviewDecision } from "/live-text.js";
1906
1906
  import {
1907
1907
  profileBlurb,
1908
1908
  profileBadges,
@@ -1918,6 +1918,7 @@
1918
1918
  window.unionRect = unionRect;
1919
1919
  window.visibleSteps = visibleSteps;
1920
1920
  window.extractLiveText = extractLiveText;
1921
+ window.livePreviewDecision = livePreviewDecision;
1921
1922
  window.profileBlurb = profileBlurb;
1922
1923
  window.profileBadges = profileBadges;
1923
1924
  window.defaultAgentName = defaultAgentName;
@@ -3482,11 +3483,20 @@
3482
3483
  // for a stream-text that never comes.
3483
3484
  if (/^[*_`\s]*nothing to show[*_`\s.!]*$/i.test(txt)) { clearLivePreview(tab); return; }
3484
3485
  const nT = normPreview(txt);
3485
- // Still showing the previous turn's answer (captured at turn start) — this
3486
- // turn hasn't produced its own text yet. Suppress until it does.
3487
- if (tab.livePreviewBaseline && nT === tab.livePreviewBaseline) { clearLivePreview(tab); return; }
3488
- if (nT && isFinalizedBlock(tab, nT)) { clearLivePreview(tab); return; }
3489
- tab.livePreviewBaseline = ""; // real new text arrived baseline no longer applies
3486
+ // Decide what to do with the block (pure, tested see livePreviewDecision).
3487
+ // The baseline (previous turn's answer) is kept for the WHOLE turn: once new
3488
+ // text has shown, a redraw frame that briefly makes the OLD block the last
3489
+ // one again is HELD, not flipped back that flip is what made the preview
3490
+ // oscillate new↔old while the model writes.
3491
+ const decision = window.livePreviewDecision
3492
+ ? window.livePreviewDecision({
3493
+ isBaseline: !!tab.livePreviewBaseline && nT === tab.livePreviewBaseline,
3494
+ isFinalized: !!nT && isFinalizedBlock(tab, nT),
3495
+ showing: !!tab.livePreviewBubble,
3496
+ })
3497
+ : "show";
3498
+ if (decision === "keep") return;
3499
+ if (decision === "clear") { clearLivePreview(tab); return; }
3490
3500
  if (!tab.livePreviewEl) {
3491
3501
  const turn = document.createElement("div");
3492
3502
  turn.className = "turn claude live-preview";
@@ -65,3 +65,25 @@ export function extractLiveText(screen) {
65
65
  }
66
66
  return parts.join(" ");
67
67
  }
68
+
69
+ /**
70
+ * What to do with the grey live-preview bubble for a freshly extracted block —
71
+ * pure so the anti-flicker rule is testable; the DOM effect stays in the caller.
72
+ *
73
+ * `isBaseline` = the extracted block equals the PREVIOUS turn's answer, captured
74
+ * at turn start and still (or transiently again) on the screen. `isFinalized` =
75
+ * it is a block already streamed as authoritative this turn. `showing` = a
76
+ * preview bubble is currently up.
77
+ *
78
+ * The flicker this fixes: while the model writes, a redraw frame can briefly make
79
+ * the OLD block the last one on screen. Once new text has shown, that must be
80
+ * HELD (keep), not flipped back to the old text — the old "clear on baseline /
81
+ * drop the baseline once new text appears" oscillated new↔old every few frames.
82
+ *
83
+ * @returns {"show"|"keep"|"clear"}
84
+ */
85
+ export function livePreviewDecision({ isBaseline, isFinalized, showing }) {
86
+ if (isBaseline) return showing ? "keep" : "clear";
87
+ if (isFinalized) return "clear";
88
+ return "show";
89
+ }