@linxin666/dsh-remote-web-ui 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/lib/client.js CHANGED
@@ -4269,7 +4269,7 @@ window.__ModuleLoader__.load({
4269
4269
  /** The building package's version, when the bundle carries it. */
4270
4270
  function bakedVersion() {
4271
4271
  try {
4272
- return "0.3.5";
4272
+ return "0.3.6";
4273
4273
  } catch {
4274
4274
  return;
4275
4275
  }
package/lib/mobile.js CHANGED
@@ -13475,11 +13475,19 @@ function foldEvents(events, existing) {
13475
13475
  * Incremental folder for one message stream. Live chat folds one event at a
13476
13476
  * time; rebuilding the five index maps by scanning every message per event
13477
13477
  * made that path O(n) per event (O(n * events) per turn). A folder keeps the
13478
- * indexes alive across folds, applies each event in O(1) map operations, and
13479
- * returns the previous snapshot identity unchanged when nothing applied, so
13480
- * React skips the re-render entirely. Replayed events are no-ops: the maxSeq
13481
- * watermark advanced by the first application skips them, which also makes a
13482
- * double-invoked React state updater harmless.
13478
+ * indexes alive across folds, applies each event in (near-)O(1) map
13479
+ * operations, and returns the previous snapshot identity unchanged when
13480
+ * nothing applied, so React skips the re-render entirely. Replayed events
13481
+ * are no-ops: the maxSeq watermark advanced by the first application skips
13482
+ * them, which also makes a double-invoked React state updater harmless.
13483
+ *
13484
+ * Cost contract: every fold that applies at least one event rebuilds the
13485
+ * snapshot as one fresh O(messages) array copy (React needs the new
13486
+ * identity), and replacement paths locate rows by an O(messages) identity
13487
+ * scan. Both are amortized by the consumer folding bursts as ONE batch on a
13488
+ * fixed cadence (see FOLD_FLUSH_MS in ChatView) instead of per event. An
13489
+ * id-to-index map was measured and deliberately rejected: at realistic
13490
+ * message counts the Map maintenance cost exceeds the identity-scan cost.
13483
13491
  */
13484
13492
  var EventFolder = class {
13485
13493
  state;
@@ -14023,6 +14031,14 @@ function ChatView({ session, mux, onBack }) {
14023
14031
  const liveBufferRef = (0, import_react.useRef)([]);
14024
14032
  /** Incremental folder for this session's stream (indexes stay hot across events). */
14025
14033
  const folderRef = (0, import_react.useRef)(void 0);
14034
+ /**
14035
+ * Live events buffered between fold flushes. Folding every single event
14036
+ * costs one O(messages) snapshot copy per event; flushing at a fixed
14037
+ * cadence folds each burst once while keeping streaming text fluid.
14038
+ */
14039
+ const foldBufferRef = (0, import_react.useRef)([]);
14040
+ /** Timer id of the scheduled fold flush, if any. */
14041
+ const foldFlushTimerRef = (0, import_react.useRef)(void 0);
14026
14042
  /** True once the live buffer hit its cap (oldest events were dropped). */
14027
14043
  const liveBufferOverflowRef = (0, import_react.useRef)(false);
14028
14044
  /** The session's permission select (absent = capability not composed). */
@@ -14115,7 +14131,21 @@ function ChatView({ session, mux, onBack }) {
14115
14131
  }, [session.sessionId]);
14116
14132
  (0, import_react.useEffect)(() => {
14117
14133
  if (mux === void 0) return;
14118
- return mux.onFrame((frame) => {
14134
+ /** Fold every buffered live event in one batch and publish the snapshot. */
14135
+ const flushFoldBuffer = () => {
14136
+ if (foldFlushTimerRef.current !== void 0) {
14137
+ clearTimeout(foldFlushTimerRef.current);
14138
+ foldFlushTimerRef.current = void 0;
14139
+ }
14140
+ const events = foldBufferRef.current;
14141
+ if (events.length === 0) return;
14142
+ foldBufferRef.current = [];
14143
+ setMessages((previous) => {
14144
+ const folder = folderRef.current;
14145
+ return folder === void 0 ? foldEvents(events, previous) : folder.fold(events);
14146
+ });
14147
+ };
14148
+ const unsubscribe = mux.onFrame((frame) => {
14119
14149
  if (frame.type === "session/event") {
14120
14150
  if (frame.sessionId !== session.sessionId) return;
14121
14151
  const event = frame.event;
@@ -14134,10 +14164,9 @@ function ChatView({ session, mux, onBack }) {
14134
14164
  liveBufferRef.current.push(event);
14135
14165
  return;
14136
14166
  }
14137
- setMessages((previous) => {
14138
- const folder = folderRef.current;
14139
- return folder === void 0 ? foldEvents([event], previous) : folder.fold([event]);
14140
- });
14167
+ foldBufferRef.current.push(event);
14168
+ if (!(event.type === "assistant/chunk" || event.type === "message/chunk")) flushFoldBuffer();
14169
+ else if (foldFlushTimerRef.current === void 0) foldFlushTimerRef.current = setTimeout(flushFoldBuffer, 50);
14141
14170
  return;
14142
14171
  }
14143
14172
  if (frame.type === "session/projection" && frame.sessionId === session.sessionId && frame.key === "permissions") {
@@ -14171,6 +14200,14 @@ function ChatView({ session, mux, onBack }) {
14171
14200
  return;
14172
14201
  }
14173
14202
  });
14203
+ return () => {
14204
+ unsubscribe();
14205
+ if (foldFlushTimerRef.current !== void 0) {
14206
+ clearTimeout(foldFlushTimerRef.current);
14207
+ foldFlushTimerRef.current = void 0;
14208
+ }
14209
+ foldBufferRef.current = [];
14210
+ };
14174
14211
  }, [mux, session.sessionId]);
14175
14212
  (0, import_react.useEffect)(() => {
14176
14213
  if (!running) return;