shadok-ai 0.16.11 → 0.16.13

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/index.html +38 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.16.11",
3
+ "version": "0.16.13",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test --test-timeout=120000 test/*.ts context/*/test/*.test.mjs",
package/public/index.html CHANGED
@@ -5273,6 +5273,20 @@
5273
5273
  // source of truth. A new session (e.g. a Telegram /spawn) shows up as a tab
5274
5274
  // live; a renamed channel renames its tab; a channel that left the registry
5275
5275
  // (ended elsewhere) closes its tab.
5276
+ /** Is a group name being TYPED right now, anywhere? The server→client name
5277
+ * sync must not overwrite the field under the cursor, and there are three
5278
+ * places to rename a group: its head in the agents column, its row in the
5279
+ * rail, and — since the column title became editable — that title. Keying
5280
+ * the guard on the group HEAD alone missed the last two: `inlineRename`
5281
+ * swaps the span it was given for the input, so the input is wherever the
5282
+ * gesture started, not inside `headEl`. One query over all three, and the
5283
+ * name is simply left alone for one poll (4 s), which costs nothing. */
5284
+ function renamingAGroup() {
5285
+ return !!document.querySelector(
5286
+ "#groups input.rename, #railList input.rename, #tabbar .side-label input.rename",
5287
+ );
5288
+ }
5289
+
5276
5290
  let lastChannelIds = new Set();
5277
5291
  async function syncChannels() {
5278
5292
  if (restoring) return;
@@ -5292,7 +5306,7 @@
5292
5306
  if (sg == null || sg.id == null) continue;
5293
5307
  const g = groups.find((x) => x.id === sg.id);
5294
5308
  if (!g) { createGroup(sg); continue; }
5295
- if (sg.name && sg.name !== g.nameEl.textContent && !g.headEl.querySelector("input"))
5309
+ if (sg.name && sg.name !== g.nameEl.textContent && !renamingAGroup())
5296
5310
  g.nameEl.textContent = sg.name;
5297
5311
  if (sg.icon && sg.icon !== g.icon) g.icon = sg.icon;
5298
5312
  }
@@ -7873,11 +7887,25 @@
7873
7887
  // reloads onto the new build quickly instead of waiting out a backoff.
7874
7888
  reloadExpectedUntil = Date.now() + 90_000;
7875
7889
  break;
7876
- case "history":
7890
+ case "history": {
7877
7891
  // Full replay from the transcript (source of truth). Clear first so a
7878
7892
  // reconnect (laptop wake / dropped link) rebuilds the transcript instead
7879
7893
  // of appending a second copy of the whole history.
7880
- t.transcriptEl.innerHTML = "";
7894
+ //
7895
+ // Hold the reader's place across the wipe+rebuild. On a RECONNECT the
7896
+ // transcript already carries the conversation, and tearing it down to
7897
+ // rebuild it snaps the view to the top UNLESS the browser's scroll
7898
+ // anchoring happens to save us — which it does not when the tab was in
7899
+ // the background while the replay ran (anchoring needs a live layout),
7900
+ // and does not in Safari. That is the reported "the chat jumps back up
7901
+ // when I come back". The replay is the same messages in the same order,
7902
+ // appended bottom-first, so we restore deterministically: pinned to the
7903
+ // bottom if they were following live, otherwise holding the same
7904
+ // distance from the bottom so what they were reading stays in view.
7905
+ const el = t.transcriptEl;
7906
+ const wasStick = el._stickBottom !== false;
7907
+ const fromBottom = Math.max(0, el.scrollHeight - el.scrollTop - el.clientHeight);
7908
+ el.innerHTML = "";
7881
7909
  t.unreadSep = null; // the separator element went with the wipe
7882
7910
  addTurn(t, "system", "", "— resumed session history —");
7883
7911
  for (const h of msg.turns) {
@@ -7909,7 +7937,14 @@
7909
7937
  if (h.afterInternal) b.parentElement.classList.add("after-gap");
7910
7938
  }
7911
7939
  }
7940
+ // Restore the reader's place deterministically — never trust the
7941
+ // browser's implicit scroll anchoring for a full teardown.
7942
+ el._stickBottom = wasStick;
7943
+ el.scrollTop = wasStick
7944
+ ? el.scrollHeight
7945
+ : Math.max(0, el.scrollHeight - el.clientHeight - fromBottom);
7912
7946
  break;
7947
+ }
7913
7948
  case "ready":
7914
7949
  // The server just named the instance it is. If it's not the one this
7915
7950
  // page was loaded from, the port was taken over — stop writing, prompt a