@prettier-ai/dsh-client-ui-chat 0.1.2-alpha.2 → 0.1.2-alpha.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/lib/client.js CHANGED
@@ -32,11 +32,25 @@ window.__ModuleLoader__.load({
32
32
  //#endregion
33
33
  //#region lib/types/client/contract/snapshot.js
34
34
  const EMPTY_LIST$1 = [];
35
+ const EMPTY_TIMELINE = {
36
+ turnOrder: EMPTY_LIST$1,
37
+ turns: /* @__PURE__ */ new Map()
38
+ };
39
+ const EMPTY_NODE_SOURCE = {
40
+ getSnapshot: () => void 0,
41
+ subscribe: () => () => {}
42
+ };
43
+ const EMPTY_NODE_PROCESS_SOURCE = {
44
+ getSnapshot: () => void 0,
45
+ subscribe: () => () => {}
46
+ };
35
47
  /** Empty Chat target used before a view builder is registered. */
36
48
  const EMPTY_CHAT_SNAPSHOT = {
37
49
  order: EMPTY_LIST$1,
38
50
  nodes: {
39
51
  get: () => void 0,
52
+ source: () => EMPTY_NODE_SOURCE,
53
+ processSource: () => EMPTY_NODE_PROCESS_SOURCE,
40
54
  values: () => EMPTY_LIST$1
41
55
  },
42
56
  locations: {
@@ -44,10 +58,7 @@ window.__ModuleLoader__.load({
44
58
  getStep: () => EMPTY_LIST$1
45
59
  },
46
60
  navigation: { items: () => EMPTY_LIST$1 },
47
- timeline: {
48
- turnOrder: EMPTY_LIST$1,
49
- turns: /* @__PURE__ */ new Map()
50
- },
61
+ timeline: EMPTY_TIMELINE,
51
62
  legacy: {
52
63
  nodes: EMPTY_LIST$1,
53
64
  turnTimings: /* @__PURE__ */ new Map(),
@@ -206,7 +217,7 @@ window.__ModuleLoader__.load({
206
217
  });
207
218
  //#endregion
208
219
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/ContextBody.module.css.mjs
209
- const css$14 = ".abgUBa_text{color:var(--dsw-alias-label-secondary);font:inherit;white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.abgUBa_fields{border-top:1px solid var(--dsw-alias-line-secondary);flex-direction:column;gap:2px;margin:8px 0 0;padding-top:8px;display:flex}.abgUBa_field{gap:8px;min-width:0;display:flex}.abgUBa_fieldKey{min-width:96px;color:var(--dsw-alias-label-caption);flex:none}.abgUBa_fieldValue{min-width:0;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;flex:auto;margin:0}.abgUBa_files{flex-wrap:wrap;gap:4px 12px;margin:0 0 8px;padding:0;list-style:none;display:flex}.abgUBa_file{align-items:baseline;gap:6px;min-width:0;display:flex}.abgUBa_filePath{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}.abgUBa_fileAction{color:var(--dsw-alias-label-caption)}.abgUBa_catalogNotice{color:var(--dsw-alias-label-caption);margin:0 0 6px}.abgUBa_entries{flex-direction:column;gap:4px;margin:0;padding:0;list-style:none;display:flex}.abgUBa_entry{gap:8px;min-width:0;display:flex}.abgUBa_entryName{color:var(--dsw-alias-label-secondary);flex:none}.abgUBa_entryDescription{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.abgUBa_sections{flex-direction:column;gap:8px;margin:0;display:flex}.abgUBa_section{flex-direction:column;gap:2px;min-width:0;display:flex}.abgUBa_sectionName{color:var(--dsw-alias-label-caption)}.abgUBa_sectionText{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.abgUBa_relaySender{color:var(--dsw-alias-label-caption);overflow-wrap:anywhere;margin:0 0 6px}.abgUBa_recalls{flex-direction:column;gap:2px;margin:0 0 8px;padding:0;list-style:none;display:flex}.abgUBa_recall{gap:8px;min-width:0;display:flex}.abgUBa_recallLabel{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}.abgUBa_recallCounts{color:var(--dsw-alias-label-caption);flex:none}";
220
+ const css$14 = ".abgUBa_text{color:var(--dsw-alias-label-secondary);font:inherit;white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.abgUBa_fields{border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:2px;margin:8px 0 0;padding-top:8px;display:flex}.abgUBa_field{gap:8px;min-width:0;display:flex}.abgUBa_fieldKey{min-width:96px;color:var(--dsw-alias-label-caption);flex:none}.abgUBa_fieldValue{min-width:0;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;flex:auto;margin:0}.abgUBa_files{flex-wrap:wrap;gap:4px 12px;margin:0 0 8px;padding:0;list-style:none;display:flex}.abgUBa_file{align-items:baseline;gap:6px;min-width:0;display:flex}.abgUBa_filePath{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}.abgUBa_fileAction{color:var(--dsw-alias-label-caption)}.abgUBa_catalogNotice{color:var(--dsw-alias-label-caption);margin:0 0 6px}.abgUBa_entries{flex-direction:column;gap:4px;margin:0;padding:0;list-style:none;display:flex}.abgUBa_entry{gap:8px;min-width:0;display:flex}.abgUBa_entryName{color:var(--dsw-alias-label-secondary);flex:none}.abgUBa_entryDescription{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.abgUBa_sections{flex-direction:column;gap:8px;margin:0;display:flex}.abgUBa_section{flex-direction:column;gap:2px;min-width:0;display:flex}.abgUBa_sectionName{color:var(--dsw-alias-label-caption)}.abgUBa_sectionText{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.abgUBa_relaySender{color:var(--dsw-alias-label-caption);overflow-wrap:anywhere;margin:0 0 6px}.abgUBa_recalls{flex-direction:column;gap:2px;margin:0 0 8px;padding:0;list-style:none;display:flex}.abgUBa_recall{gap:8px;min-width:0;display:flex}.abgUBa_recallLabel{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}.abgUBa_recallCounts{color:var(--dsw-alias-label-caption);flex:none}";
210
221
  const tagId$14 = "@prettier-ai/dsh-client-ui-chat/ContextBody.module.css";
211
222
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$14) + "]") === null) {
212
223
  const tag = document.createElement("style");
@@ -947,7 +958,7 @@ window.__ModuleLoader__.load({
947
958
  }
948
959
  //#endregion
949
960
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/MessageIconActions.module.css.mjs
950
- const css$12 = ".BmHDMW_actions{height:calc(28px + var(--dsh-content-font-delta,0px));align-items:center;gap:8px;display:flex}.BmHDMW_timeStart{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px}.BmHDMW_timeEnd{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);white-space:nowrap}@media (hover:hover){[data-actions-reveal=hover] .BmHDMW_actions{opacity:0;transition:opacity 80ms}[data-actions-reveal=hover]:hover .BmHDMW_actions,[data-actions-reveal=hover]:focus-within .BmHDMW_actions{opacity:1}}.BmHDMW_action{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.BmHDMW_action svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}.BmHDMW_action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.BmHDMW_action[data-unavailable]{cursor:default;opacity:.4}.BmHDMW_action[data-unavailable]:hover{color:var(--dsw-alias-label-tertiary);background:0 0}.BmHDMW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
961
+ const css$12 = ".BmHDMW_actions{height:calc(28px + var(--dsh-content-font-delta,0px));align-items:center;gap:8px;display:flex}.BmHDMW_timeStart{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px}.BmHDMW_timeEnd{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);white-space:nowrap}@media (hover:hover){[data-actions-reveal=hover] .BmHDMW_actions,:is([data-chat-flow-kind=user],[data-chat-flow-kind=steering]):has(~:is([data-chat-flow-kind=user],[data-chat-flow-kind=steering])) .BmHDMW_actions{opacity:0;transition:opacity 80ms}[data-actions-reveal=hover]:hover .BmHDMW_actions,[data-actions-reveal=hover]:focus-within .BmHDMW_actions,:is([data-chat-flow-kind=user],[data-chat-flow-kind=steering]):has(~:is([data-chat-flow-kind=user],[data-chat-flow-kind=steering])):hover .BmHDMW_actions,:is([data-chat-flow-kind=user],[data-chat-flow-kind=steering]):has(~:is([data-chat-flow-kind=user],[data-chat-flow-kind=steering])):focus-within .BmHDMW_actions{opacity:1}}.BmHDMW_action{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.BmHDMW_action svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}.BmHDMW_action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.BmHDMW_action[data-unavailable]{cursor:default;opacity:.4}.BmHDMW_action[data-unavailable]:hover{color:var(--dsw-alias-label-tertiary);background:0 0}.BmHDMW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
951
962
  const tagId$12 = "@prettier-ai/dsh-client-ui-chat/MessageIconActions.module.css";
952
963
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$12) + "]") === null) {
953
964
  const tag = document.createElement("style");
@@ -1169,7 +1180,7 @@ window.__ModuleLoader__.load({
1169
1180
  });
1170
1181
  }
1171
1182
  /** Right-aligned bubble shared by user and steering rows. */
1172
- function UserStyleBubble({ content, renderMessageImages, actions, pending = false, echo = false, referenceLabels = [], previewImages, reveal = "always", t }) {
1183
+ function UserStyleBubble({ content, renderMessageImages, actions, pending = false, echo = false, referenceLabels = [], previewImages, t }) {
1173
1184
  const { text, images: contentImages, rest } = contentParts(content);
1174
1185
  const images = previewImages ?? contentImages;
1175
1186
  const truncated = (total) => t("json.truncated", { total });
@@ -1178,7 +1189,6 @@ window.__ModuleLoader__.load({
1178
1189
  className: MessageItem_module_css_default.userRow,
1179
1190
  "data-pending-steering": pending || void 0,
1180
1191
  "data-submission-echo": echo || void 0,
1181
- "data-actions-reveal": reveal,
1182
1192
  children: [(0, react_jsx_runtime.jsxs)("div", {
1183
1193
  className: MessageItem_module_css_default.userStack,
1184
1194
  children: [
@@ -1223,10 +1233,10 @@ window.__ModuleLoader__.load({
1223
1233
  });
1224
1234
  }
1225
1235
  /**
1226
- * Render one local submission echo with the exact visual language of the
1227
- * durable user node that replaces it: draft text plus object-URL previews,
1228
- * visible from the submit click until the durable `user/message` (or its
1229
- * queue occurrence) renders.
1236
+ * Render one local transcript or steering submission echo with the same
1237
+ * visual language and surface marker as the Host occurrence that replaces
1238
+ * it: draft text plus object-URL previews, visible from the submit click
1239
+ * until the durable `user/message` or steering occurrence renders.
1230
1240
  * @param props - the session snapshot's pending submission and render seats.
1231
1241
  * @returns the echoed user bubble.
1232
1242
  */
@@ -1243,6 +1253,7 @@ window.__ModuleLoader__.load({
1243
1253
  ...image.height === void 0 ? {} : { height: image.height }
1244
1254
  } })), [submission.images]),
1245
1255
  renderMessageImages,
1256
+ pending: submission.placement === "steering",
1246
1257
  echo: true,
1247
1258
  t,
1248
1259
  actions: (text) => (0, react_jsx_runtime.jsx)(MessageIconActions, {
@@ -1255,20 +1266,12 @@ window.__ModuleLoader__.load({
1255
1266
  });
1256
1267
  }
1257
1268
  /** User and admitted-steering keyed Chat renderer. */
1258
- const UserMessageNodeView = (0, react.memo)(function UserMessageNodeView({ node, renderMessageImages, useChat, t }) {
1269
+ const UserMessageNodeView = (0, react.memo)(function UserMessageNodeView({ node, renderMessageImages, t }) {
1259
1270
  const data = node.data;
1260
- const isLatestUserRow = useChat((snapshot) => {
1261
- for (let index = snapshot.order.length - 1; index >= 0; index -= 1) {
1262
- const candidate = snapshot.nodes.get(snapshot.order[index] ?? "");
1263
- if (candidate?.kind === "user" || candidate?.kind === "steering") return candidate.key === node.key;
1264
- }
1265
- return true;
1266
- });
1267
1271
  return (0, react_jsx_runtime.jsx)(UserStyleBubble, {
1268
1272
  content: data.content,
1269
1273
  renderMessageImages,
1270
1274
  ...data.referenceLabels === void 0 ? {} : { referenceLabels: data.referenceLabels },
1271
- reveal: isLatestUserRow ? "always" : "hover",
1272
1275
  t,
1273
1276
  actions: (text) => (0, react_jsx_runtime.jsx)(MessageIconActions, {
1274
1277
  text,
@@ -1340,49 +1343,13 @@ window.__ModuleLoader__.load({
1340
1343
  "turn-tail"
1341
1344
  ]);
1342
1345
  /**
1343
- * Identify one finalized answer generation without using its ordering anchor.
1344
- * @param spec - current Turn process specification.
1345
- * @returns stable identity until the finalized answer Step is withdrawn or replaced.
1346
- */
1347
- function turnProcessGeneration(spec) {
1348
- return `${String(spec.turn)}|${spec.answerStep === null ? "" : String(spec.answerStep)}`;
1349
- }
1350
- /**
1351
- * Encode one process specification as a primitive Location-data value.
1352
- * @param spec - current Turn process specification.
1353
- * @returns reference-stable scalar for equal specifications.
1346
+ * Compare immutable Turn-process specifications by their published fields.
1347
+ * @param left - previous specification.
1348
+ * @param right - next specification.
1349
+ * @returns whether both values describe the same process presentation.
1354
1350
  */
1355
- function encodeTurnProcess(spec) {
1356
- return [
1357
- spec.turn,
1358
- spec.controlAnchorSeq,
1359
- spec.processStartSeq,
1360
- spec.answerAnchorSeq ?? "",
1361
- spec.answerStep ?? "",
1362
- spec.inlineReasoning ? 1 : 0,
1363
- spec.messageCount,
1364
- spec.toolCallCount,
1365
- spec.subagentCount
1366
- ].join("|");
1367
- }
1368
- /**
1369
- * Decode a same-process signature produced by {@link encodeTurnProcess}.
1370
- * @param signature - encoded Turn process value.
1371
- * @returns decoded process specification.
1372
- */
1373
- function decodeTurnProcess(signature) {
1374
- const [turn, controlAnchorSeq, processStartSeq, answerAnchorSeq, answerStep, inlineReasoning, messageCount, toolCallCount, subagentCount] = signature.split("|");
1375
- return {
1376
- turn: Number(turn),
1377
- controlAnchorSeq: Number(controlAnchorSeq),
1378
- processStartSeq: Number(processStartSeq),
1379
- answerAnchorSeq: answerAnchorSeq === "" ? null : Number(answerAnchorSeq),
1380
- answerStep: answerStep === "" ? null : Number(answerStep),
1381
- inlineReasoning: inlineReasoning === "1",
1382
- messageCount: Number(messageCount),
1383
- toolCallCount: Number(toolCallCount),
1384
- subagentCount: Number(subagentCount)
1385
- };
1351
+ function sameTurnProcessSpec(left, right) {
1352
+ return left.turn === right.turn && left.controlAnchorSeq === right.controlAnchorSeq && left.processStartSeq === right.processStartSeq && left.answerAnchorSeq === right.answerAnchorSeq && left.answerStep === right.answerStep && left.inlineReasoning === right.inlineReasoning && left.messageCount === right.messageCount && left.toolCallCount === right.toolCallCount && left.subagentCount === right.subagentCount;
1386
1353
  }
1387
1354
  /**
1388
1355
  * Recognize the shipped subagent delegation name and its configured variants.
@@ -1397,7 +1364,7 @@ window.__ModuleLoader__.load({
1397
1364
  //#region lib/types/client/stores.js
1398
1365
  /** Per-Session Chat selection store shared by the transcript and details panel. */
1399
1366
  /**
1400
- * Resolve any stored generation for one Turn.
1367
+ * Resolve the manually expanded answer for one Turn.
1401
1368
  * @param state - Chat store snapshot.
1402
1369
  * @param turn - owning Turn.
1403
1370
  * @returns the Turn's stored entry, when present.
@@ -1419,7 +1386,7 @@ window.__ModuleLoader__.load({
1419
1386
  select: (draft, target) => {
1420
1387
  draft.selection = target;
1421
1388
  },
1422
- setTurnProcessOpen: (draft, turn, generation, open) => {
1389
+ setTurnProcessOpen: (draft, turn, answerStep, open) => {
1423
1390
  const index = draft.turnProcesses.findIndex((entry) => entry.turn === turn);
1424
1391
  if (!open) {
1425
1392
  if (index >= 0) draft.turnProcesses.splice(index, 1);
@@ -1427,7 +1394,7 @@ window.__ModuleLoader__.load({
1427
1394
  }
1428
1395
  const next = {
1429
1396
  turn,
1430
- generation
1397
+ answerStep
1431
1398
  };
1432
1399
  if (index < 0) draft.turnProcesses.push(next);
1433
1400
  else draft.turnProcesses[index] = next;
@@ -1467,7 +1434,7 @@ window.__ModuleLoader__.load({
1467
1434
  }
1468
1435
  //#endregion
1469
1436
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/ChatView.module.css.mjs
1470
- const css$11 = ".fnEpAq_root{flex-direction:column;flex:auto;min-height:0;display:flex;position:relative}.fnEpAq_scroll{min-height:0;padding:16px calc(var(--dsh-composer-side-clearance) + 16px);flex:auto;overflow-y:auto;container-type:inline-size}[data-conversation-scroll] .fnEpAq_root{flex:none;height:auto;min-height:auto}[data-conversation-scroll] .fnEpAq_scroll{flex:none;min-height:auto;overflow:visible}.fnEpAq_column{max-width:var(--dsh-chat-content-width);flex-direction:column;width:100%;margin:0 auto;display:flex}.fnEpAq_column>:not([hidden]):not(.fnEpAq_flowItem:empty)~:not([hidden]):not(.fnEpAq_flowItem:empty){margin-top:var(--dsh-chat-flow-gap,16px)}.fnEpAq_flowItem{min-width:0}.fnEpAq_flowItem[data-turn-process-answer]{--dsh-chat-flow-gap:8px}.fnEpAq_flowItem:empty{display:none}.fnEpAq_callRow{border-radius:6px}.fnEpAq_turnStatus{height:calc(26px + var(--dsh-content-font-delta,0px));font:var(--dsw-font-s-strong-14);font-size:var(--dsh-content-font-size,14px);line-height:calc(22px + var(--dsh-content-font-delta,0px));white-space:nowrap;background:linear-gradient(90deg, var(--dsw-static-deepseek-500) 0%, var(--dsw-static-deepseek-500) 40%, var(--dsw-static-deepseek-200) 50%, var(--dsw-static-deepseek-500) 60%, var(--dsw-static-deepseek-500) 100%);color:#0000;-webkit-text-fill-color:transparent;background-position:100% 0;background-size:250% 100%;-webkit-background-clip:text;background-clip:text;flex:none;align-self:flex-start;align-items:center;animation:1.8s linear infinite fnEpAq_dsh-turn-status-shimmer;display:inline-flex}.fnEpAq_turnStatusClock{font:var(--dsw-font-xs-13);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-caption);-webkit-text-fill-color:var(--dsw-alias-label-caption);margin-left:8px;font-weight:400}@keyframes fnEpAq_dsh-turn-status-shimmer{to{background-position:0 0}}@media (prefers-reduced-motion:reduce){.fnEpAq_turnStatus{background-position:0 0;background-size:100% 100%;animation:none}}.fnEpAq_hint{color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px))}.fnEpAq_openError{color:var(--dsw-alias-state-error-primary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px))}.fnEpAq_older{justify-content:center;display:flex}.fnEpAq_older button{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover-solid);cursor:pointer;border:none;border-radius:14px;padding:4px 12px;font-size:12px}.fnEpAq_older button:disabled{cursor:default;opacity:.6}.fnEpAq_toBottomSlot{z-index:8;height:0;padding-right:max(0px, calc((100% - var(--dsh-chat-content-width)) / 2));pointer-events:none;justify-content:flex-end;display:flex;position:sticky;bottom:16px}[data-conversation-scroll] .fnEpAq_toBottomSlot{bottom:calc(var(--dsh-composer-height,152px) + 16px)}.fnEpAq_toBottom{border:1px solid var(--dsw-alias-border-l2);width:34px;height:34px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-floating-fill);box-shadow:var(--dsw-shadow-lv2);cursor:pointer;pointer-events:auto;border-radius:100px;justify-content:center;align-items:center;margin-top:-34px;padding:0;display:flex}.fnEpAq_toBottom:hover{background:var(--dsw-alias-button-floating-hover)}.fnEpAq_modalAction{min-width:72px}";
1437
+ const css$11 = ".fnEpAq_root{flex-direction:column;flex:auto;min-height:0;display:flex;position:relative}.fnEpAq_scroll{min-height:0;padding:16px calc(var(--dsh-composer-side-clearance) + 16px);flex:auto;overflow-y:auto;container-type:inline-size}[data-conversation-scroll] .fnEpAq_root{flex:none;height:auto;min-height:auto}[data-conversation-scroll] .fnEpAq_scroll{flex:none;min-height:auto;overflow:visible}.fnEpAq_column{max-width:var(--dsh-chat-content-width);flex-direction:column;width:100%;margin:0 auto;display:flex}.fnEpAq_column>:not([hidden]):not(.fnEpAq_flowItem:empty)~:not([hidden]):not(.fnEpAq_flowItem:empty){margin-top:var(--dsh-chat-flow-gap,16px)}.fnEpAq_flowItem{min-width:0}.fnEpAq_flowItem[data-turn-process-answer]{--dsh-chat-flow-gap:8px}.fnEpAq_flowItem:empty{display:none}.fnEpAq_callRow{border-radius:6px}.fnEpAq_turnStatus{height:calc(26px + var(--dsh-content-font-delta,0px));font:var(--dsw-font-s-strong-14);font-size:var(--dsh-content-font-size,14px);line-height:calc(22px + var(--dsh-content-font-delta,0px));white-space:nowrap;background:linear-gradient(90deg, var(--dsw-static-deepseek-500) 0%, var(--dsw-static-deepseek-500) 40%, var(--dsw-static-deepseek-200) 50%, var(--dsw-static-deepseek-500) 60%, var(--dsw-static-deepseek-500) 100%);color:#0000;-webkit-text-fill-color:transparent;background-position:100% 0;background-size:250% 100%;-webkit-background-clip:text;background-clip:text;flex:none;align-self:flex-start;align-items:center;animation:1.8s linear infinite fnEpAq_dsh-turn-status-shimmer;display:inline-flex}.fnEpAq_turnStatusClock{font:var(--dsw-font-xs-13);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-caption);-webkit-text-fill-color:var(--dsw-alias-label-caption);margin-left:8px;font-weight:400}@keyframes fnEpAq_dsh-turn-status-shimmer{to{background-position:0 0}}@media (prefers-reduced-motion:reduce){.fnEpAq_turnStatus{background-position:0 0;background-size:100% 100%;animation:none}}.fnEpAq_hint{color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px))}.fnEpAq_openError{color:var(--dsw-alias-state-error-primary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(18px + var(--dsh-content-font-delta-secondary,0px))}.fnEpAq_older{justify-content:center;display:flex}.fnEpAq_older button{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover-solid);cursor:pointer;border:none;border-radius:14px;padding:4px 12px;font-size:12px}.fnEpAq_older button:disabled{cursor:default;opacity:.6}.fnEpAq_toBottomSlot{z-index:8;height:0;padding-right:max(0px, calc((100% - var(--dsh-chat-content-width)) / 2));pointer-events:none;justify-content:flex-end;display:flex;position:sticky;bottom:16px}[data-conversation-scroll] .fnEpAq_toBottomSlot{bottom:calc(var(--dsh-composer-height,152px) + 16px)}.fnEpAq_toBottom{--dsw-elevation-stroke-color:var(--dsw-alias-border-l3);corner-shape:round;width:34px;height:34px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-floating-fill);box-shadow:var(--dsw-elevation-panel);cursor:pointer;pointer-events:auto;border:0;border-radius:100px;justify-content:center;align-items:center;margin-top:-34px;padding:0;display:flex}.fnEpAq_toBottom:hover{background:var(--dsw-alias-button-floating-hover)}.fnEpAq_modalAction{min-width:72px}";
1471
1438
  const tagId$11 = "@prettier-ai/dsh-client-ui-chat/ChatView.module.css";
1472
1439
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$11) + "]") === null) {
1473
1440
  const tag = document.createElement("style");
@@ -1494,85 +1461,44 @@ window.__ModuleLoader__.load({
1494
1461
  };
1495
1462
  //#endregion
1496
1463
  //#region lib/types/client/chat/ChatNodeSeat.js
1497
- const EMPTY_PROCESS_KEYS = [];
1498
- function turnProcessOpeningHumanAnchor(keys, nodes, spec) {
1499
- let anchor;
1500
- for (const key of keys) {
1501
- const node = nodes.get(key);
1502
- if ((node?.kind === "user" || node?.kind === "steering") && node.anchorSeq < spec.controlAnchorSeq) anchor = Math.min(anchor ?? node.anchorSeq, node.anchorSeq);
1503
- }
1504
- return anchor;
1464
+ function turnDataOf(node) {
1465
+ const location = node?.location;
1466
+ return location?.kind === "turn" || location?.kind === "step" ? location.turn.data : void 0;
1505
1467
  }
1506
- /** Derive disclosure facts from one content-revisioned Turn index. */
1507
- function turnProcessLayout(keys, nodes, spec) {
1508
- let hasExternalProcess = false;
1509
- let compactAnswer = true;
1510
- const openingHumanAnchor = turnProcessOpeningHumanAnchor(keys, nodes, spec);
1511
- for (const key of keys) {
1512
- const node = nodes.get(key);
1513
- if (node === void 0 || node.kind === "turn-process") continue;
1514
- if ((node.kind === "user" || node.kind === "steering") && (openingHumanAnchor === void 0 || node.anchorSeq > openingHumanAnchor) && (spec.answerAnchorSeq === null || node.anchorSeq < spec.answerAnchorSeq)) compactAnswer = false;
1515
- if (TURN_PROCESS_INDEPENDENT_KINDS.has(node.kind) || node.anchorSeq < spec.processStartSeq || spec.answerAnchorSeq !== null && node.anchorSeq >= spec.answerAnchorSeq) continue;
1516
- if (node.kind !== "assistant-step" || spec.answerStep === null || node.data.step !== spec.answerStep) hasExternalProcess = true;
1517
- }
1518
- return {
1519
- hasExternalProcess,
1520
- compactAnswer
1521
- };
1468
+ function turnOf(node) {
1469
+ const location = node?.location;
1470
+ return location?.kind === "turn" || location?.kind === "step" ? location.turn.turn : void 0;
1522
1471
  }
1523
1472
  /** Subscribe, apply Turn-process visibility, and dispatch one stable Context key. */
1524
- const ChatNodeSeat = (0, react.memo)(function ChatNodeSeat({ nodeKey, historyIncomplete, compactTranscript, selectedCallId, cwd, openFile, inspectCall, forkAt, renderMessageImages, fileMentions, useChat, useStore, actions, renderSlot, t }) {
1525
- const node = useChat((snapshot) => snapshot.nodes.get(nodeKey));
1526
- const processSignature = useChat((snapshot) => {
1527
- const location = snapshot.nodes.get(nodeKey)?.location;
1528
- return location?.kind === "turn" || location?.kind === "step" ? location.turn.data.get("turn-process") : void 0;
1529
- });
1530
- const processSpec = (0, react.useMemo)(() => processSignature === void 0 ? void 0 : decodeTurnProcess(processSignature), [processSignature]);
1531
- const nodeStore = useChat((snapshot) => snapshot.nodes);
1532
- const processLayoutKeys = useChat((snapshot) => {
1533
- if (!compactTranscript || historyIncomplete || processSpec === void 0) return EMPTY_PROCESS_KEYS;
1534
- const current = snapshot.nodes.get(nodeKey);
1535
- const location = current?.location;
1536
- if (current === void 0 || location?.kind !== "turn" && location?.kind !== "step" || location.turn.status !== "closed" || location.turn.turn !== processSpec.turn) return EMPTY_PROCESS_KEYS;
1537
- return current.kind === "turn-process" || current.kind === "assistant-step" && current.data.step === processSpec.answerStep ? snapshot.locations.getTurn(processSpec.turn) : EMPTY_PROCESS_KEYS;
1538
- });
1539
- const processLayout = (0, react.useMemo)(() => processSpec === void 0 || processLayoutKeys.length === 0 ? void 0 : turnProcessLayout(processLayoutKeys, nodeStore, processSpec), [
1540
- nodeStore,
1541
- processLayoutKeys,
1542
- processSpec
1543
- ]);
1544
- const processGeneration = (0, react.useMemo)(() => processSpec === void 0 ? void 0 : turnProcessGeneration(processSpec), [processSpec]);
1473
+ const ChatNodeSeat = (0, react.memo)(function ChatNodeSeat({ nodeKey, useChatNode, useChatNodeProcess, historyIncomplete, compactTranscript, selectedCallId, cwd, openFile, inspectCall, forkAt, renderMessageImages, fileMentions, useStore, actions, renderSlot, t }) {
1474
+ const node = useChatNode(nodeKey);
1475
+ const routedNode = node;
1476
+ const turn = turnOf(routedNode);
1477
+ const processPresentation = useChatNodeProcess(nodeKey);
1478
+ const processSpec = processPresentation?.spec;
1545
1479
  const storedEntry = useStore((state) => processSpec === void 0 ? void 0 : storedTurnProcessEntry(state, processSpec.turn));
1546
- const processOpen = (storedEntry?.generation === processGeneration ? storedEntry : void 0) !== void 0;
1480
+ const processOpen = (processSpec !== void 0 && processSpec.answerStep !== null && storedEntry?.answerStep === processSpec.answerStep ? storedEntry : void 0) !== void 0;
1547
1481
  const setOpen = (0, react.useCallback)((open) => {
1548
- if (processGeneration !== void 0 && processSpec !== void 0) actions.setTurnProcessOpen(processSpec.turn, processGeneration, open);
1549
- }, [
1550
- actions,
1551
- processGeneration,
1552
- processSpec
1553
- ]);
1554
- const routedNode = node;
1555
- const sameTurn = routedNode !== void 0 && processSpec !== void 0 && (routedNode.location.kind === "turn" || routedNode.location.kind === "step") && routedNode.location.turn.turn === processSpec.turn;
1556
- const turnClosed = sameTurn && routedNode.location.turn.status === "closed";
1557
- const processWindowReady = processSpec !== void 0 && compactTranscript && processSpec.answerAnchorSeq !== null && turnClosed && !historyIncomplete;
1558
- const processMember = sameTurn && processWindowReady && !TURN_PROCESS_INDEPENDENT_KINDS.has(routedNode.kind) && routedNode.anchorSeq >= processSpec.processStartSeq && routedNode.anchorSeq < processSpec.answerAnchorSeq;
1559
- const processAnswer = sameTurn && processWindowReady && routedNode.kind === "assistant-step" && routedNode.data.step === processSpec.answerStep;
1482
+ if (processSpec !== void 0 && processSpec.answerStep !== null) actions.setTurnProcessOpen(processSpec.turn, processSpec.answerStep, open);
1483
+ }, [actions, processSpec]);
1484
+ const processWindowReady = processSpec !== void 0 && processPresentation !== void 0 && compactTranscript && processSpec.answerAnchorSeq !== null && processPresentation.turn === processSpec.turn && processPresentation.turnClosed && !historyIncomplete;
1485
+ const processMember = routedNode !== void 0 && processWindowReady && !TURN_PROCESS_INDEPENDENT_KINDS.has(routedNode.kind) && routedNode.anchorSeq >= processSpec.processStartSeq && routedNode.anchorSeq < processSpec.answerAnchorSeq;
1486
+ const processAnswer = routedNode !== void 0 && processWindowReady && routedNode.kind === "assistant-step" && routedNode.data.step === processSpec.answerStep;
1560
1487
  const ownsDisclosure = routedNode?.kind === "turn-process" || processAnswer;
1561
- const foldable = processWindowReady && (processMember || ownsDisclosure && ((processLayout?.hasExternalProcess ?? false) || processSpec.inlineReasoning));
1562
- const turnProcess = (0, react.useMemo)(() => processGeneration === void 0 || processSpec === void 0 ? void 0 : {
1488
+ const foldable = processWindowReady && (processMember || ownsDisclosure && (processPresentation.hasExternalProcess || processSpec.inlineReasoning));
1489
+ const turnProcess = (0, react.useMemo)(() => processSpec === void 0 ? void 0 : {
1563
1490
  spec: processSpec,
1564
1491
  foldable,
1565
1492
  open: processOpen,
1566
1493
  setOpen
1567
1494
  }, [
1568
1495
  foldable,
1569
- processGeneration,
1570
1496
  processOpen,
1571
1497
  processSpec,
1572
1498
  setOpen
1573
1499
  ]);
1574
1500
  const controllerInactive = routedNode?.kind === "turn-process" && !foldable;
1575
- const compactAnswer = processAnswer && foldable && processLayout?.compactAnswer === true && !processOpen;
1501
+ const compactAnswer = processAnswer && foldable && processPresentation.compactAnswer && !processOpen;
1576
1502
  const processHidden = controllerInactive || foldable && processMember && !processOpen;
1577
1503
  const wrapperRef = useSearchableHidden(processHidden, (0, react.useCallback)(() => {
1578
1504
  if (processMember) setOpen(true);
@@ -1598,8 +1524,7 @@ window.__ModuleLoader__.load({
1598
1524
  turnProcess
1599
1525
  ]);
1600
1526
  if (routedNode === void 0 || owner === null) return null;
1601
- const location = routedNode.location;
1602
- const turn = location.kind === "turn" || location.kind === "step" ? location.turn.turn : void 0;
1527
+ const turnData = turnDataOf(routedNode);
1603
1528
  const routedOwner = {
1604
1529
  ...owner,
1605
1530
  node: routedNode
@@ -1616,7 +1541,7 @@ window.__ModuleLoader__.load({
1616
1541
  "data-turn-process-answer": compactAnswer || void 0,
1617
1542
  children: renderSlot("conversation.chat.node", routedOwner, {
1618
1543
  entryKey: routedNode.kind,
1619
- hookContext: nodeKey,
1544
+ hookContext: turnData,
1620
1545
  fallback: (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.JsonBlock, {
1621
1546
  label: t("message.unknownSurface", { type: routedNode.kind }),
1622
1547
  payload: routedNode.data,
@@ -1627,7 +1552,7 @@ window.__ModuleLoader__.load({
1627
1552
  });
1628
1553
  //#endregion
1629
1554
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/TurnNavigator.module.css.mjs
1630
- const css$10 = ".alqd3G_slot{z-index:6;pointer-events:none;height:0;position:sticky;top:0}.alqd3G_rail{--turn-rail-band:calc(var(--dsh-conversation-viewport-height,100dvh) - var(--dsh-composer-height,152px));--turn-preview-height:100px;top:calc(var(--turn-rail-band) / 2);right:calc(12px - (var(--dsh-composer-side-clearance) + 16px));width:28px;height:min(var(--turn-natural-height), max(0px, calc(var(--turn-rail-band) - 64px)), 420px);cursor:pointer;pointer-events:auto;transition:height .22s cubic-bezier(.2,.8,.2,1);position:absolute;transform:translateY(-50%)}.alqd3G_marks{inset:var(--turn-rail-inset) 0;position:absolute}.alqd3G_markPosition{top:min(var(--turn-natural-position), var(--turn-position));height:10px;transition:top .22s cubic-bezier(.2,.8,.2,1);animation:.15s ease-out alqd3G_dsh-turn-mark-enter;position:absolute;left:0;right:0;transform:translateY(-50%)}.alqd3G_mark{cursor:pointer;pointer-events:none;background:0 0;border:0;border-radius:8px;width:20px;padding:0;position:absolute;inset:0 0 0 auto}.alqd3G_mark:before{background:var(--dsw-alias-border-l4);content:\"\";border-radius:2px;width:12px;height:2px;transition:width .14s,background-color .14s;position:absolute;top:50%;right:0;transform:translateY(-50%)}.alqd3G_markPreview:before{background:var(--dsw-alias-label-tertiary);width:18px}.alqd3G_markActive:before{background:var(--dsw-alias-label-primary);width:20px}.alqd3G_mark:focus-visible:before{background:var(--dsw-alias-state-business-primary);width:20px}.alqd3G_mark:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.alqd3G_preview{top:clamp(0px, calc(min(var(--turn-natural-position), var(--turn-position)) + var(--turn-rail-inset) - var(--turn-preview-height) / 2), calc(100% - var(--turn-preview-height)));box-sizing:border-box;width:min(300px,100cqw - 120px);max-height:var(--turn-preview-height);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-shadow-lv2);pointer-events:none;border-radius:10px;padding:10px 12px;transition:top .14s cubic-bezier(.2,.8,.2,1);animation:.12s ease-out alqd3G_dsh-turn-preview-enter;position:absolute;right:calc(100% + 10px);overflow:hidden}.alqd3G_previewPrompt,.alqd3G_previewResponse{-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.alqd3G_previewPrompt{font:var(--dsw-font-xs-strong-13);-webkit-line-clamp:2}.alqd3G_previewResponse{color:var(--dsw-alias-label-caption);font:var(--dsw-font-xxs-12);-webkit-line-clamp:2;margin-top:4px}@keyframes alqd3G_dsh-turn-mark-enter{0%{opacity:0}to{opacity:1}}@keyframes alqd3G_dsh-turn-preview-enter{0%{opacity:0;transform:translate(4px)}to{opacity:1;transform:translate(0)}}@container (width<=900px){.alqd3G_slot{display:none}}@media (prefers-reduced-motion:reduce){.alqd3G_rail,.alqd3G_markPosition,.alqd3G_mark:before,.alqd3G_preview{transition:none;animation:none}}";
1555
+ const css$10 = ".alqd3G_slot{z-index:7;pointer-events:none;height:0;position:sticky;top:0}.alqd3G_frame{--turn-rail-band:calc(var(--dsh-conversation-viewport-height,100dvh) - var(--dsh-composer-height,152px));--turn-preview-height:100px;top:calc(var(--turn-rail-band) / 2);right:calc(12px - (var(--dsh-composer-side-clearance) + 16px));width:28px;height:min(var(--turn-natural-height), max(0px, calc(var(--turn-rail-band) - 64px)), 420px);cursor:pointer;pointer-events:auto;transition:height .22s cubic-bezier(.2,.8,.2,1);position:absolute;transform:translateY(-50%)}.alqd3G_scroller{overscroll-behavior:contain;scrollbar-width:none;position:absolute;inset:0;overflow-y:auto}.alqd3G_scroller::-webkit-scrollbar{display:none}.alqd3G_fadeTop{mask-image:linear-gradient(#0000 0,#000 24px 100%)}.alqd3G_fadeBottom{mask-image:linear-gradient(#000 0 calc(100% - 24px),#0000 100%)}.alqd3G_fadeTop.alqd3G_fadeBottom{mask-image:linear-gradient(#0000 0,#000 24px calc(100% - 24px),#0000 100%)}.alqd3G_marks{height:var(--turn-natural-height);position:relative}.alqd3G_markPosition{top:calc(var(--turn-natural-position) + var(--turn-rail-inset));height:10px;transition:top .22s cubic-bezier(.2,.8,.2,1);animation:.15s ease-out alqd3G_dsh-turn-mark-enter;position:absolute;left:0;right:0;transform:translateY(-50%)}.alqd3G_mark{cursor:pointer;pointer-events:none;background:0 0;border:0;border-radius:8px;width:20px;padding:0;position:absolute;inset:0 0 0 auto}.alqd3G_mark:before{background:var(--dsw-alias-border-l4);content:\"\";border-radius:2px;width:12px;height:2px;transition:width .14s,background-color .14s;position:absolute;top:50%;right:0;transform:translateY(-50%)}.alqd3G_markUnloaded:before{opacity:.6;width:8px}.alqd3G_markPreview:before{background:var(--dsw-alias-label-tertiary);width:18px}.alqd3G_markBusy:before{animation:1s ease-in-out infinite alqd3G_dsh-turn-mark-busy}.alqd3G_markActive:before{background:var(--dsw-alias-label-primary);width:20px}.alqd3G_mark:focus-visible:before{background:var(--dsw-alias-state-business-primary);width:20px}.alqd3G_mark:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.alqd3G_preview{top:clamp(0px, calc(var(--turn-natural-position) + var(--turn-rail-inset) - var(--turn-scroll-top,0px) - var(--turn-preview-height) / 2), calc(100% - var(--turn-preview-height)));box-sizing:border-box;width:min(300px,100cqw - 120px);max-height:var(--turn-preview-height);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-panel);pointer-events:none;border:0;border-radius:10px;padding:10px 12px;transition:top .14s cubic-bezier(.2,.8,.2,1);animation:.12s ease-out alqd3G_dsh-turn-preview-enter;position:absolute;right:calc(100% + 10px);overflow:hidden}.alqd3G_previewPrompt,.alqd3G_previewResponse{-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.alqd3G_previewPrompt{font:var(--dsw-font-xs-strong-13);-webkit-line-clamp:1}.alqd3G_previewResponse{color:var(--dsw-alias-label-caption);font:var(--dsw-font-xxs-12);-webkit-line-clamp:3;margin-top:4px}@keyframes alqd3G_dsh-turn-mark-enter{0%{opacity:0}to{opacity:1}}@keyframes alqd3G_dsh-turn-preview-enter{0%{opacity:0;transform:translate(4px)}to{opacity:1;transform:translate(0)}}@keyframes alqd3G_dsh-turn-mark-busy{0%,to{opacity:1}50%{opacity:.35}}@container (width<=900px){.alqd3G_slot{display:none}}@media (prefers-reduced-motion:reduce){.alqd3G_frame,.alqd3G_scroller,.alqd3G_markPosition,.alqd3G_mark:before,.alqd3G_markBusy:before,.alqd3G_preview{scroll-behavior:auto;transition:none;animation:none}}";
1631
1556
  const tagId$10 = "@prettier-ai/dsh-client-ui-chat/TurnNavigator.module.css";
1632
1557
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$10) + "]") === null) {
1633
1558
  const tag = document.createElement("style");
@@ -1637,96 +1562,173 @@ window.__ModuleLoader__.load({
1637
1562
  document.head.appendChild(tag);
1638
1563
  }
1639
1564
  var TurnNavigator_module_css_default = {
1565
+ "dsh-turn-mark-busy": "alqd3G_dsh-turn-mark-busy",
1640
1566
  "dsh-turn-mark-enter": "alqd3G_dsh-turn-mark-enter",
1641
1567
  "dsh-turn-preview-enter": "alqd3G_dsh-turn-preview-enter",
1568
+ "fadeBottom": "alqd3G_fadeBottom",
1569
+ "fadeTop": "alqd3G_fadeTop",
1570
+ "frame": "alqd3G_frame",
1642
1571
  "mark": "alqd3G_mark",
1643
1572
  "markActive": "alqd3G_markActive",
1573
+ "markBusy": "alqd3G_markBusy",
1644
1574
  "markPosition": "alqd3G_markPosition",
1645
1575
  "markPreview": "alqd3G_markPreview",
1576
+ "markUnloaded": "alqd3G_markUnloaded",
1646
1577
  "marks": "alqd3G_marks",
1647
1578
  "preview": "alqd3G_preview",
1648
1579
  "previewPrompt": "alqd3G_previewPrompt",
1649
1580
  "previewResponse": "alqd3G_previewResponse",
1650
- "rail": "alqd3G_rail",
1581
+ "scroller": "alqd3G_scroller",
1651
1582
  "slot": "alqd3G_slot"
1652
1583
  };
1653
1584
  //#endregion
1654
1585
  //#region lib/types/client/chat/TurnNavigator.js
1655
- /** Resting gap between neighbouring marks before the rail compresses to fit. */
1586
+ /** Fixed pitch between neighbouring marks; overflow scrolls inside the frame. */
1656
1587
  const TURN_SPACING_PX = 10;
1657
1588
  /** Rail padding above the first mark and below the last one, per end. */
1658
1589
  const RAIL_INSET_PX = 6;
1659
- function itemPosition(index, count) {
1660
- const ratio = count <= 1 ? 0 : index / (count - 1);
1590
+ /** Fade band the mask reserves at a scrollable end. */
1591
+ const FADE_PX = 24;
1592
+ function itemPosition(index) {
1593
+ return { "--turn-natural-position": `${String(index * TURN_SPACING_PX)}px` };
1594
+ }
1595
+ function frameStyle(count, scrollTop) {
1661
1596
  return {
1662
- "--turn-natural-position": `${String(index * TURN_SPACING_PX)}px`,
1663
- "--turn-position": `${String(ratio * 100)}%`
1597
+ "--turn-natural-height": `${String((count - 1) * TURN_SPACING_PX + 2 * RAIL_INSET_PX)}px`,
1598
+ "--turn-rail-inset": `${String(RAIL_INSET_PX)}px`,
1599
+ "--turn-scroll-top": `${String(scrollTop)}px`
1664
1600
  };
1665
1601
  }
1666
- function railSize(count) {
1602
+ function itemAtPointer(items, frame, scrollTop, clientY) {
1603
+ const offset = clientY - frame.getBoundingClientRect().top + scrollTop - RAIL_INSET_PX;
1604
+ return items[Math.max(0, Math.min(items.length - 1, Math.round(offset / TURN_SPACING_PX)))];
1605
+ }
1606
+ const RAIL_AT_REST = {
1607
+ top: 0,
1608
+ canScrollUp: false,
1609
+ canScrollDown: false
1610
+ };
1611
+ function railScrollState(scroller) {
1612
+ const top = scroller.scrollTop;
1667
1613
  return {
1668
- "--turn-natural-height": `${String((count - 1) * TURN_SPACING_PX + 2 * RAIL_INSET_PX)}px`,
1669
- "--turn-rail-inset": `${String(RAIL_INSET_PX)}px`
1614
+ top,
1615
+ canScrollUp: top > 1,
1616
+ canScrollDown: top < scroller.scrollHeight - scroller.clientHeight - 1
1670
1617
  };
1671
1618
  }
1672
- function itemAtPointer(items, rail, clientY) {
1673
- const rect = rail.getBoundingClientRect();
1674
- const usableHeight = Math.max(1, rect.height - 2 * RAIL_INSET_PX);
1675
- const ratio = Math.max(0, Math.min(1, (clientY - rect.top - RAIL_INSET_PX) / usableHeight));
1676
- return items[Math.round(ratio * (items.length - 1))];
1619
+ function sameRailScrollState(left, right) {
1620
+ return left.top === right.top && left.canScrollUp === right.canScrollUp && left.canScrollDown === right.canScrollDown;
1677
1621
  }
1678
- function TurnNavigatorRail({ items, activeTurn, onNavigate, t }) {
1622
+ function TurnNavigatorRail({ items, activeTurn, busyTurn, onNavigate, t }) {
1679
1623
  const [previewTurn, setPreviewTurn] = (0, react.useState)(null);
1624
+ const [scrollState, setScrollState] = (0, react.useState)(RAIL_AT_REST);
1625
+ const scrollerRef = (0, react.useRef)(null);
1626
+ /** While the pointer works the rail, follow must not move it under the hand. */
1627
+ const pointerInsideRef = (0, react.useRef)(false);
1680
1628
  const previewId = (0, react.useId)();
1629
+ const syncScrollState = () => {
1630
+ const scroller = scrollerRef.current;
1631
+ if (scroller === null) return;
1632
+ const next = railScrollState(scroller);
1633
+ setScrollState((current) => sameRailScrollState(current, next) ? current : next);
1634
+ };
1635
+ (0, react.useEffect)(() => {
1636
+ const scroller = scrollerRef.current;
1637
+ if (scroller === null || typeof ResizeObserver === "undefined") return;
1638
+ const observer = new ResizeObserver(syncScrollState);
1639
+ observer.observe(scroller);
1640
+ return () => {
1641
+ observer.disconnect();
1642
+ };
1643
+ }, []);
1644
+ (0, react.useEffect)(syncScrollState, [items.length]);
1645
+ (0, react.useEffect)(() => {
1646
+ const scroller = scrollerRef.current;
1647
+ const index = items.findIndex((item) => item.turn === activeTurn);
1648
+ if (scroller === null || index < 0 || pointerInsideRef.current) return;
1649
+ const markTop = index * TURN_SPACING_PX + RAIL_INSET_PX;
1650
+ const viewTop = scroller.scrollTop;
1651
+ const viewHeight = scroller.clientHeight;
1652
+ if (viewHeight <= 0 || markTop >= viewTop + FADE_PX && markTop <= viewTop + viewHeight - FADE_PX) return;
1653
+ const target = Math.max(0, markTop - viewHeight / 2);
1654
+ const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
1655
+ if (typeof scroller.scrollTo === "function") scroller.scrollTo({
1656
+ top: target,
1657
+ behavior: reduced ? "auto" : "smooth"
1658
+ });
1659
+ else scroller.scrollTop = target;
1660
+ syncScrollState();
1661
+ }, [activeTurn, items]);
1681
1662
  if (items.length < 2) return null;
1682
1663
  const previewIndex = items.findIndex((item) => item.turn === previewTurn);
1683
1664
  const preview = previewIndex < 0 ? void 0 : items[previewIndex];
1684
- const previewPosition = previewIndex < 0 ? void 0 : itemPosition(previewIndex, items.length);
1665
+ const previewPosition = previewIndex < 0 ? void 0 : itemPosition(previewIndex);
1685
1666
  const previewAtPointer = (event) => {
1686
- setPreviewTurn(itemAtPointer(items, event.currentTarget, event.clientY)?.turn ?? null);
1667
+ const scrollTop = scrollerRef.current?.scrollTop ?? 0;
1668
+ setPreviewTurn(itemAtPointer(items, event.currentTarget, scrollTop, event.clientY)?.turn ?? null);
1687
1669
  };
1688
1670
  const navigateAtPointer = (event) => {
1689
- const item = itemAtPointer(items, event.currentTarget, event.clientY);
1671
+ const scrollTop = scrollerRef.current?.scrollTop ?? 0;
1672
+ const item = itemAtPointer(items, event.currentTarget, scrollTop, event.clientY);
1690
1673
  if (item !== void 0) onNavigate(item);
1691
1674
  };
1675
+ const fadeClasses = [TurnNavigator_module_css_default.scroller];
1676
+ if (scrollState.canScrollUp) fadeClasses.push(TurnNavigator_module_css_default.fadeTop);
1677
+ if (scrollState.canScrollDown) fadeClasses.push(TurnNavigator_module_css_default.fadeBottom);
1692
1678
  return (0, react_jsx_runtime.jsx)("div", {
1693
1679
  className: TurnNavigator_module_css_default.slot,
1694
1680
  children: (0, react_jsx_runtime.jsxs)("nav", {
1695
- className: TurnNavigator_module_css_default.rail,
1696
- style: railSize(items.length),
1681
+ className: TurnNavigator_module_css_default.frame,
1682
+ style: frameStyle(items.length, scrollState.top),
1697
1683
  "aria-label": t("chat.turnNavigation.label"),
1698
1684
  onClick: navigateAtPointer,
1699
1685
  onPointerMove: previewAtPointer,
1686
+ onPointerEnter: () => {
1687
+ pointerInsideRef.current = true;
1688
+ },
1700
1689
  onPointerLeave: () => {
1690
+ pointerInsideRef.current = false;
1701
1691
  setPreviewTurn(null);
1702
1692
  },
1703
1693
  children: [(0, react_jsx_runtime.jsx)("div", {
1704
- className: TurnNavigator_module_css_default.marks,
1705
- children: items.map((item, index) => {
1706
- const active = item.turn === activeTurn;
1707
- const showingPreview = item.turn === previewTurn;
1708
- const markClass = active ? `${TurnNavigator_module_css_default.mark} ${TurnNavigator_module_css_default.markActive}` : showingPreview ? `${TurnNavigator_module_css_default.mark} ${TurnNavigator_module_css_default.markPreview}` : TurnNavigator_module_css_default.mark;
1709
- return (0, react_jsx_runtime.jsx)("div", {
1710
- className: TurnNavigator_module_css_default.markPosition,
1711
- style: itemPosition(index, items.length),
1712
- children: (0, react_jsx_runtime.jsx)("button", {
1713
- type: "button",
1714
- className: markClass,
1715
- "aria-label": t("chat.turnNavigation.jump", { turn: item.turn }),
1716
- "aria-current": active ? "true" : void 0,
1717
- "aria-describedby": showingPreview ? previewId : void 0,
1718
- onClick: (event) => {
1719
- event.stopPropagation();
1720
- onNavigate(item);
1721
- },
1722
- onFocus: () => {
1723
- setPreviewTurn(item.turn);
1724
- },
1725
- onBlur: () => {
1726
- setPreviewTurn(null);
1727
- }
1728
- })
1729
- }, item.turn);
1694
+ ref: scrollerRef,
1695
+ className: fadeClasses.join(" "),
1696
+ onScroll: () => {
1697
+ syncScrollState();
1698
+ },
1699
+ children: (0, react_jsx_runtime.jsx)("div", {
1700
+ className: TurnNavigator_module_css_default.marks,
1701
+ children: items.map((item, index) => {
1702
+ const active = item.turn === activeTurn;
1703
+ const showingPreview = item.turn === previewTurn;
1704
+ const classes = [TurnNavigator_module_css_default.mark];
1705
+ if (item.anchor.kind === "unloaded") classes.push(TurnNavigator_module_css_default.markUnloaded);
1706
+ if (active) classes.push(TurnNavigator_module_css_default.markActive);
1707
+ else if (showingPreview) classes.push(TurnNavigator_module_css_default.markPreview);
1708
+ if (item.turn === busyTurn) classes.push(TurnNavigator_module_css_default.markBusy);
1709
+ return (0, react_jsx_runtime.jsx)("div", {
1710
+ className: TurnNavigator_module_css_default.markPosition,
1711
+ style: itemPosition(index),
1712
+ children: (0, react_jsx_runtime.jsx)("button", {
1713
+ type: "button",
1714
+ className: classes.join(" "),
1715
+ "aria-label": t(item.anchor.kind === "loaded" ? "chat.turnNavigation.jump" : "chat.turnNavigation.jumpLoad", { turn: item.turn }),
1716
+ "aria-current": active ? "true" : void 0,
1717
+ "aria-busy": item.turn === busyTurn ? "true" : void 0,
1718
+ "aria-describedby": showingPreview ? previewId : void 0,
1719
+ onClick: (event) => {
1720
+ event.stopPropagation();
1721
+ onNavigate(item);
1722
+ },
1723
+ onFocus: () => {
1724
+ setPreviewTurn(item.turn);
1725
+ },
1726
+ onBlur: () => {
1727
+ setPreviewTurn(null);
1728
+ }
1729
+ })
1730
+ }, item.turn);
1731
+ })
1730
1732
  })
1731
1733
  }), preview !== void 0 && previewPosition !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
1732
1734
  id: previewId,
@@ -1745,9 +1747,12 @@ window.__ModuleLoader__.load({
1745
1747
  });
1746
1748
  }
1747
1749
  /**
1748
- * Compact rail of the currently loaded Turns with hover and focus previews.
1750
+ * Fixed-pitch rail of every known Turn loaded marks scroll, unloaded marks
1751
+ * page history in first — with hover and focus previews. Overflow scrolls
1752
+ * inside the frame, gradient fades marking each scrollable end, and the
1753
+ * active mark keeps itself in view while the pointer is elsewhere.
1749
1754
  *
1750
- * Memoized because it renders two host elements per loaded Turn while the
1755
+ * Memoized because it renders two host elements per Turn while the
1751
1756
  * enclosing view re-renders on every streaming delta: without the guard a long
1752
1757
  * session rebuilds hundreds of marks per commit for a rail that only changes
1753
1758
  * when a Turn is added, removed, or becomes active. Its props must therefore
@@ -1755,7 +1760,102 @@ window.__ModuleLoader__.load({
1755
1760
  */
1756
1761
  const TurnNavigator = (0, react.memo)(TurnNavigatorRail);
1757
1762
  //#endregion
1763
+ //#region ../../util/brand/src/index.ts
1764
+ /**
1765
+ * Apply a compile-time number brand without changing the value.
1766
+ * @param value - number admitted by the domain that owns the target brand.
1767
+ * @returns the same number with the requested compile-time brand.
1768
+ */
1769
+ function brandNumber(value) {
1770
+ return value;
1771
+ }
1772
+ //#endregion
1773
+ //#region ../../core/session/src/types.ts
1774
+ /**
1775
+ * Admit a numeric value as an existing Session event position.
1776
+ * @param value - non-negative safe integer admitted by the owning log operation.
1777
+ * @returns the same number with the Session-sequence brand.
1778
+ */
1779
+ function SessionSeq(value) {
1780
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionSeq must be a non-negative safe integer, got ${String(value)}`);
1781
+ return brandNumber(value);
1782
+ }
1783
+ //#endregion
1784
+ //#region lib/types/client/chat/turn-rail-items.js
1785
+ /**
1786
+ * View-layer union of the host turn outline and the loaded rail items. The
1787
+ * conversation snapshot never carries projection values, so this merge is the
1788
+ * one place the rail's two sources meet: the `turnOutline` projection names
1789
+ * every turn of the session, and the loaded window supplies anchors and
1790
+ * richer previews for the turns it holds.
1791
+ */
1792
+ const EMPTY_ITEMS$1 = [];
1793
+ /**
1794
+ * Structurally narrow one wire outline entry (projection values cross the
1795
+ * wire). `turn` and `seq` are the load-bearing fields — a mark cannot exist
1796
+ * or jump without them — so their damage drops the entry; the previews are
1797
+ * decorative, so a malformed one degrades to `''` and the turn stays
1798
+ * navigable by number.
1799
+ */
1800
+ function outlineEntry(value) {
1801
+ if (typeof value !== "object" || value === null) return void 0;
1802
+ const entry = value;
1803
+ if (typeof entry.turn !== "number" || !Number.isSafeInteger(entry.turn) || entry.turn < 0) return void 0;
1804
+ if (typeof entry.seq !== "number" || !Number.isSafeInteger(entry.seq) || entry.seq < 0 || Object.is(entry.seq, -0)) return void 0;
1805
+ return {
1806
+ turn: entry.turn,
1807
+ seq: SessionSeq(entry.seq),
1808
+ prompt: typeof entry.prompt === "string" ? entry.prompt : "",
1809
+ response: typeof entry.response === "string" ? entry.response : ""
1810
+ };
1811
+ }
1812
+ /** Wire outline entries, or none when the projection is absent or malformed. */
1813
+ function outlineEntries(outline) {
1814
+ return Array.isArray(outline) ? outline : EMPTY_ITEMS$1;
1815
+ }
1816
+ /**
1817
+ * Merge the host outline with the loaded rail items into the full ladder.
1818
+ * A turn present in both sides keeps the loaded anchor, taking an outline
1819
+ * preview only where the window's own is empty (a mid-Turn window head, or a
1820
+ * turn whose loaded nodes carry no text); turns on one side only pass
1821
+ * through. Result ascends by turn.
1822
+ * @param loaded - loaded-window rail items (timeline order).
1823
+ * @param outline - `turnOutline` projection value, treated as wire data.
1824
+ * @returns every known turn, ascending; a stable empty array when none.
1825
+ */
1826
+ function mergeTurnRailItems(loaded, outline) {
1827
+ const byTurn = /* @__PURE__ */ new Map();
1828
+ for (const raw of outlineEntries(outline)) {
1829
+ const entry = outlineEntry(raw);
1830
+ if (entry === void 0) continue;
1831
+ byTurn.set(entry.turn, {
1832
+ turn: entry.turn,
1833
+ prompt: entry.prompt,
1834
+ response: entry.response,
1835
+ anchor: {
1836
+ kind: "unloaded",
1837
+ seq: entry.seq
1838
+ }
1839
+ });
1840
+ }
1841
+ for (const item of loaded) {
1842
+ const preview = byTurn.get(item.turn);
1843
+ byTurn.set(item.turn, {
1844
+ turn: item.turn,
1845
+ prompt: item.prompt !== "" ? item.prompt : preview?.prompt ?? "",
1846
+ response: item.response !== "" ? item.response : preview?.response ?? "",
1847
+ anchor: {
1848
+ kind: "loaded",
1849
+ key: item.anchorKey
1850
+ }
1851
+ });
1852
+ }
1853
+ if (byTurn.size === 0) return EMPTY_ITEMS$1;
1854
+ return [...byTurn.values()].sort((left, right) => left.turn - right.turn);
1855
+ }
1856
+ //#endregion
1758
1857
  //#region lib/types/client/chat/ChatView.js
1858
+ const SCROLL_SAMPLE_INTERVAL_MS = 500;
1759
1859
  /** Active column host when present; otherwise the view-local scroller. */
1760
1860
  function scrollerOf(from) {
1761
1861
  return from.closest("[data-conversation-scroll]") ?? from;
@@ -1888,14 +1988,22 @@ window.__ModuleLoader__.load({
1888
1988
  })]
1889
1989
  });
1890
1990
  }
1991
+ const ChatNodeList = (0, react.memo)(function ChatNodeList({ order, ...seatProps }) {
1992
+ return order.map((nodeKey) => (0, react_jsx_runtime.jsx)(ChatNodeSeat, {
1993
+ nodeKey,
1994
+ ...seatProps
1995
+ }, nodeKey));
1996
+ });
1891
1997
  /**
1892
1998
  * The chat view slot entry: pure component over the composed props; each
1893
1999
  * ordered business Node crosses the keyed renderer seat.
1894
2000
  */
1895
- function ChatView({ useSession, useChat, useSessions, useStore, actions, renderSlot, sessionId, openFile, loadOlder, loadImage, openView, chatScroll, forkAt, fileMentions, useTranscriptView, t }) {
2001
+ function ChatView({ useSession, useChat, useChatNode, useChatNodeProcess, useSessions, useStore, actions, renderSlot, sessionId, openFile, loadOlder, loadThrough, loadImage, openView, chatScroll, forkAt, fileMentions, useTranscriptView, useProjection, t }) {
1896
2002
  const order = useChat((s) => s.order);
1897
2003
  const nodeStore = useChat((s) => s.nodes);
1898
2004
  const turnNavigationItems = useChat((s) => s.navigation.items());
2005
+ const turnOutline = useProjection("turnOutline");
2006
+ const railItems = (0, react.useMemo)(() => mergeTurnRailItems(turnNavigationItems, turnOutline), [turnNavigationItems, turnOutline]);
1899
2007
  const timeline = useChat((s) => s.timeline);
1900
2008
  const inbox = useSession((s) => s.queue);
1901
2009
  const cwd = useSessions((s) => s.byId[sessionId]?.cwd);
@@ -1938,7 +2046,7 @@ window.__ModuleLoader__.load({
1938
2046
  const visibleSubmissions = (0, react.useMemo)(() => {
1939
2047
  if (pendingSubmissions.length === 0) return pendingSubmissions;
1940
2048
  const observed = observedRpcIds(order, nodeStore, inbox);
1941
- return pendingSubmissions.filter((submission) => !observed.has(submission.requestId));
2049
+ return pendingSubmissions.filter((submission) => submission.placement !== "queued" && !observed.has(submission.requestId));
1942
2050
  }, [
1943
2051
  pendingSubmissions,
1944
2052
  order,
@@ -1954,12 +2062,23 @@ window.__ModuleLoader__.load({
1954
2062
  const columnRef = (0, react.useRef)(null);
1955
2063
  const [atBottom, setAtBottom] = (0, react.useState)(() => chatScroll.read() === null);
1956
2064
  const atBottomRef = (0, react.useRef)(atBottom);
2065
+ const scrollSamplePendingRef = (0, react.useRef)(false);
2066
+ const [, setScrollSampleTick] = (0, react.useState)(0);
1957
2067
  const [activeTurn, setActiveTurn] = (0, react.useState)(() => turnNavigationItems.at(-1)?.turn ?? null);
1958
2068
  /** Last position delivered or written on the main thread. */
1959
2069
  const observedTopRef = (0, react.useRef)(0);
1960
2070
  /** Paging anchor: semantic row/position at click, updated by reader scrolls
1961
2071
  * while the request is pending and restored after the prepend lands. */
1962
2072
  const anchorRef = (0, react.useRef)(null);
2073
+ /** Unloaded-turn jump in flight: target turn plus its load-through seq. */
2074
+ const pendingJumpRef = (0, react.useRef)(null);
2075
+ /** Whether the in-flight jump already landed mid-paging (settle then only corrects an untouched landing). */
2076
+ const jumpLandedRef = (0, react.useRef)(false);
2077
+ const [busyJumpTurn, setBusyJumpTurn] = (0, react.useState)(null);
2078
+ /** Bumped when a loadThrough completion settles, after its last page's commit. */
2079
+ const [jumpSettleTick, setJumpSettleTick] = (0, react.useState)(0);
2080
+ /** Window head at the last settle-time repage; an unmoved head falls back instead of repaging forever. */
2081
+ const jumpRepageHeadRef = (0, react.useRef)(null);
1963
2082
  const firstSeqRef = (0, react.useRef)(null);
1964
2083
  const openedRef = (0, react.useRef)(false);
1965
2084
  const lastKeyRef = (0, react.useRef)(null);
@@ -1977,6 +2096,7 @@ window.__ModuleLoader__.load({
1977
2096
  const lastSubmissionId = visibleSubmissions[visibleSubmissions.length - 1]?.requestId ?? null;
1978
2097
  const followSig = `${openState}:${firstSeq}:${lastKey}:${order.length}:${running ? 1 : 0}:${lastSteeringId ?? ""}:${lastSubmissionId ?? ""}`;
1979
2098
  const syncActiveTurn = (0, react.useCallback)(() => {
2099
+ if (scrollSamplePendingRef.current) return;
1980
2100
  const local = listRef.current;
1981
2101
  const first = turnNavigationItems[0];
1982
2102
  if (local === null || first === void 0) {
@@ -1984,13 +2104,17 @@ window.__ModuleLoader__.load({
1984
2104
  return;
1985
2105
  }
1986
2106
  const el = scrollerOf(local);
2107
+ if (el.scrollHeight - el.scrollTop - el.clientHeight <= 25) {
2108
+ const latest = turnNavigationItems.at(-1)?.turn ?? first.turn;
2109
+ setActiveTurn((current) => current === latest ? current : latest);
2110
+ return;
2111
+ }
1987
2112
  const reading = turnAtLine(local, el.getBoundingClientRect().top + Math.min(96, el.clientHeight * .2));
1988
2113
  let next = first.turn;
1989
2114
  if (reading !== null) for (const item of turnNavigationItems) {
1990
2115
  if (item.turn > reading) break;
1991
2116
  next = item.turn;
1992
2117
  }
1993
- if (el.scrollHeight - el.scrollTop - el.clientHeight <= 25) next = turnNavigationItems.at(-1)?.turn ?? next;
1994
2118
  setActiveTurn((current) => current === next ? current : next);
1995
2119
  }, [turnNavigationItems]);
1996
2120
  const activeTurnRef = (0, react.useRef)(null);
@@ -2015,6 +2139,8 @@ window.__ModuleLoader__.load({
2015
2139
  }, [scheduleActiveTurn]);
2016
2140
  const toBottom = (el) => {
2017
2141
  anchorRef.current = null;
2142
+ pendingJumpRef.current = null;
2143
+ setBusyJumpTurn((current) => current === null ? current : null);
2018
2144
  el.scrollTop = el.scrollHeight;
2019
2145
  observedTopRef.current = el.scrollTop;
2020
2146
  atBottomRef.current = true;
@@ -2022,7 +2148,52 @@ window.__ModuleLoader__.load({
2022
2148
  chatScroll.save(null);
2023
2149
  setActiveTurn(turnNavigationItems.at(-1)?.turn ?? null);
2024
2150
  };
2151
+ const landOnRowRef = (0, react.useRef)(() => {});
2152
+ landOnRowRef.current = (local, el, row, turn) => {
2153
+ el.scrollTop += flowTop(row, el) - 24;
2154
+ observedTopRef.current = el.scrollTop;
2155
+ const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 25;
2156
+ atBottomRef.current = isAtBottom;
2157
+ setAtBottom(isAtBottom);
2158
+ setActiveTurn(turn);
2159
+ const position = isAtBottom ? null : scrollPosition(local, el);
2160
+ if (isAtBottom) chatScroll.save(null);
2161
+ else if (position !== null) chatScroll.save(position);
2162
+ };
2163
+ /**
2164
+ * Land the pending jump once its Turn has a rendered anchor row; false
2165
+ * while it must keep waiting. Mid-jump landings (`settle` false) keep the
2166
+ * jump armed with the target row as the paging anchor, so later chunks and
2167
+ * the load-earlier button's unmount re-land on the same row; the settling
2168
+ * call clears the jump.
2169
+ */
2170
+ const realizePendingJump = (local, el, settle) => {
2171
+ const pending = pendingJumpRef.current;
2172
+ if (pending === null) return true;
2173
+ const item = railItems.find((candidate) => candidate.turn === pending.turn);
2174
+ if (item === void 0 || item.anchor.kind !== "loaded") return false;
2175
+ const row = anchorElement(local, item.anchor.key);
2176
+ if (row === null) return false;
2177
+ if (settle) {
2178
+ pendingJumpRef.current = null;
2179
+ setBusyJumpTurn(null);
2180
+ const held = anchorRef.current;
2181
+ const landedEarlier = jumpLandedRef.current;
2182
+ jumpLandedRef.current = false;
2183
+ anchorRef.current = null;
2184
+ if (!landedEarlier || held?.key === item.anchor.key) landOnRowRef.current(local, el, row, pending.turn);
2185
+ return true;
2186
+ }
2187
+ landOnRowRef.current(local, el, row, pending.turn);
2188
+ jumpLandedRef.current = true;
2189
+ anchorRef.current = {
2190
+ key: item.anchor.key,
2191
+ top: flowTop(row, el)
2192
+ };
2193
+ return true;
2194
+ };
2025
2195
  (0, react.useLayoutEffect)(() => {
2196
+ if (scrollSamplePendingRef.current) return;
2026
2197
  const local = listRef.current;
2027
2198
  /* v8 ignore next -- ref-null guard: React attaches the ref before layout effects run. */
2028
2199
  if (local === null) return;
@@ -2056,6 +2227,10 @@ window.__ModuleLoader__.load({
2056
2227
  const row = anchorElement(local, anchor.key);
2057
2228
  if (row !== null) el.scrollTop += flowTop(row, el) - anchor.top;
2058
2229
  observedTopRef.current = el.scrollTop;
2230
+ if (!realizePendingJump(local, el, false) && row !== null) anchorRef.current = {
2231
+ key: anchor.key,
2232
+ top: flowTop(row, el)
2233
+ };
2059
2234
  firstSeqRef.current = firstSeq;
2060
2235
  /* v8 ignore next -- ?? arm: a prepend adds nodes, so the flow list here is never empty. */
2061
2236
  lastKeyRef.current = lastKey;
@@ -2073,7 +2248,11 @@ window.__ModuleLoader__.load({
2073
2248
  lastSteeringIdRef.current = lastSteeringId;
2074
2249
  lastSubmissionIdRef.current = lastSubmissionId;
2075
2250
  followSigRef.current = followSig;
2076
- if (appendedUser || appendedSteering || appendedSubmission || tipMoved && atBottomRef.current) toBottom(el);
2251
+ if (appendedUser || appendedSteering || appendedSubmission || tipMoved && atBottomRef.current) {
2252
+ toBottom(el);
2253
+ return;
2254
+ }
2255
+ if (pendingJumpRef.current !== null) realizePendingJump(local, el, false);
2077
2256
  });
2078
2257
  const onScrollRef = (0, react.useRef)(() => {});
2079
2258
  onScrollRef.current = () => {
@@ -2106,16 +2285,31 @@ window.__ModuleLoader__.load({
2106
2285
  /* v8 ignore next -- ref-null guard: effect runs after the list node commits. */
2107
2286
  if (local === null) return;
2108
2287
  const el = scrollerOf(local);
2109
- const onScroll = () => {
2288
+ let sampleTimer;
2289
+ const sample = () => {
2290
+ if (!scrollSamplePendingRef.current) return;
2291
+ scrollSamplePendingRef.current = false;
2292
+ if (sampleTimer !== void 0) window.clearTimeout(sampleTimer);
2293
+ sampleTimer = void 0;
2110
2294
  onScrollRef.current();
2295
+ setScrollSampleTick((tick) => tick + 1);
2296
+ };
2297
+ const onScroll = () => {
2298
+ scrollSamplePendingRef.current = true;
2299
+ sampleTimer ??= window.setTimeout(sample, SCROLL_SAMPLE_INTERVAL_MS);
2111
2300
  };
2112
2301
  el.addEventListener("scroll", onScroll, { passive: true });
2302
+ el.addEventListener("scrollend", sample, { passive: true });
2113
2303
  return () => {
2114
2304
  el.removeEventListener("scroll", onScroll);
2305
+ el.removeEventListener("scrollend", sample);
2306
+ if (sampleTimer !== void 0) window.clearTimeout(sampleTimer);
2307
+ scrollSamplePendingRef.current = false;
2115
2308
  };
2116
2309
  }, []);
2117
2310
  const followRef = (0, react.useRef)(null);
2118
2311
  followRef.current = () => {
2312
+ if (scrollSamplePendingRef.current) return;
2119
2313
  const local = listRef.current;
2120
2314
  if (local !== null && atBottomRef.current) {
2121
2315
  const el = scrollerOf(local);
@@ -2142,6 +2336,39 @@ window.__ModuleLoader__.load({
2142
2336
  (0, react.useEffect)(() => {
2143
2337
  if (!loadingOlder) anchorRef.current = null;
2144
2338
  }, [loadingOlder]);
2339
+ (0, react.useEffect)(() => {
2340
+ const pending = pendingJumpRef.current;
2341
+ const local = listRef.current;
2342
+ if (pending === null || local === null) return;
2343
+ const el = scrollerOf(local);
2344
+ if (realizePendingJump(local, el, true)) return;
2345
+ if ((firstSeq === null || firstSeq > pending.seq) && hasMore) {
2346
+ if (loadingOlder) return;
2347
+ if (jumpRepageHeadRef.current !== firstSeq) {
2348
+ jumpRepageHeadRef.current = firstSeq;
2349
+ const held = pagingAnchor(local, el);
2350
+ if (held !== null && held.dataset.chatAnchorKey !== void 0) anchorRef.current = {
2351
+ key: held.dataset.chatAnchorKey,
2352
+ top: flowTop(held, el)
2353
+ };
2354
+ loadThrough(pending.seq).finally(() => {
2355
+ setJumpSettleTick((tick) => tick + 1);
2356
+ });
2357
+ return;
2358
+ }
2359
+ }
2360
+ for (const row of local.querySelectorAll("[data-chat-turn]:not([hidden])")) {
2361
+ const turn = Number(row.dataset.chatTurn);
2362
+ if (!Number.isSafeInteger(turn) || turn < pending.turn) continue;
2363
+ landOnRowRef.current(local, el, row, turn);
2364
+ break;
2365
+ }
2366
+ pendingJumpRef.current = null;
2367
+ setBusyJumpTurn(null);
2368
+ }, [jumpSettleTick]);
2369
+ (0, react.useEffect)(() => {
2370
+ if (!loadingOlder && pendingJumpRef.current !== null) setJumpSettleTick((tick) => tick + 1);
2371
+ }, [loadingOlder]);
2145
2372
  const loadOlderAnchored = () => {
2146
2373
  const local = listRef.current;
2147
2374
  /* v8 ignore next -- ref-null guard: the paging button renders inside the list tree. */
@@ -2158,24 +2385,38 @@ window.__ModuleLoader__.load({
2158
2385
  const navigateToTurn = (0, react.useCallback)((item) => {
2159
2386
  const local = listRef.current;
2160
2387
  if (local === null) return;
2161
- const row = anchorElement(local, item.anchorKey);
2162
- if (row === null) return;
2163
2388
  const el = scrollerOf(local);
2164
- el.scrollTop += flowTop(row, el) - 24;
2165
- observedTopRef.current = el.scrollTop;
2389
+ if (item.anchor.kind === "unloaded") {
2390
+ atBottomRef.current = false;
2391
+ setAtBottom(false);
2392
+ const held = pagingAnchor(local, el);
2393
+ if (held !== null && held.dataset.chatAnchorKey !== void 0) anchorRef.current = {
2394
+ key: held.dataset.chatAnchorKey,
2395
+ top: flowTop(held, el)
2396
+ };
2397
+ pendingJumpRef.current = {
2398
+ turn: item.turn,
2399
+ seq: item.anchor.seq
2400
+ };
2401
+ jumpRepageHeadRef.current = null;
2402
+ jumpLandedRef.current = false;
2403
+ setBusyJumpTurn(item.turn);
2404
+ loadThrough(item.anchor.seq).finally(() => {
2405
+ setJumpSettleTick((tick) => tick + 1);
2406
+ });
2407
+ return;
2408
+ }
2409
+ const row = anchorElement(local, item.anchor.key);
2410
+ if (row === null) return;
2411
+ pendingJumpRef.current = null;
2412
+ setBusyJumpTurn((current) => current === null ? current : null);
2413
+ landOnRowRef.current(local, el, row, item.turn);
2166
2414
  const landed = loadingOlder ? pagingAnchor(local, el) : null;
2167
2415
  anchorRef.current = landed === null || landed.dataset.chatAnchorKey === void 0 ? null : {
2168
2416
  key: landed.dataset.chatAnchorKey,
2169
2417
  top: flowTop(landed, el)
2170
2418
  };
2171
- const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 25;
2172
- atBottomRef.current = isAtBottom;
2173
- setAtBottom(isAtBottom);
2174
- setActiveTurn(item.turn);
2175
- const position = isAtBottom ? null : scrollPosition(local, el);
2176
- if (isAtBottom) chatScroll.save(null);
2177
- else if (position !== null) chatScroll.save(position);
2178
- }, [loadingOlder, chatScroll]);
2419
+ }, [loadingOlder, loadThrough]);
2179
2420
  return (0, react_jsx_runtime.jsxs)("div", {
2180
2421
  className: ChatView_module_css_default.root,
2181
2422
  children: [(0, react_jsx_runtime.jsxs)("div", {
@@ -2183,8 +2424,9 @@ window.__ModuleLoader__.load({
2183
2424
  className: ChatView_module_css_default.scroll,
2184
2425
  children: [
2185
2426
  (0, react_jsx_runtime.jsx)(TurnNavigator, {
2186
- items: turnNavigationItems,
2427
+ items: railItems,
2187
2428
  activeTurn,
2429
+ busyTurn: busyJumpTurn,
2188
2430
  onNavigate: navigateToTurn,
2189
2431
  t
2190
2432
  }),
@@ -2213,11 +2455,12 @@ window.__ModuleLoader__.load({
2213
2455
  children: loadingOlder ? t("loading") : t("chat.loadOlder")
2214
2456
  })
2215
2457
  }),
2216
- order.map((nodeKey) => (0, react_jsx_runtime.jsx)(ChatNodeSeat, {
2217
- nodeKey,
2458
+ (0, react_jsx_runtime.jsx)(ChatNodeList, {
2459
+ order,
2460
+ useChatNode,
2461
+ useChatNodeProcess,
2218
2462
  historyIncomplete: hasMore,
2219
2463
  compactTranscript,
2220
- useChat,
2221
2464
  useStore,
2222
2465
  actions,
2223
2466
  selectedCallId,
@@ -2229,7 +2472,7 @@ window.__ModuleLoader__.load({
2229
2472
  fileMentions,
2230
2473
  renderSlot,
2231
2474
  t
2232
- }, nodeKey)),
2475
+ }),
2233
2476
  running && (0, react_jsx_runtime.jsx)(TurnStatus, {
2234
2477
  startTime: runningTurnStart,
2235
2478
  t
@@ -2328,6 +2571,7 @@ window.__ModuleLoader__.load({
2328
2571
  "chat.deepDiving": "深度求索中...",
2329
2572
  "chat.turnNavigation.label": "轮次导航",
2330
2573
  "chat.turnNavigation.jump": "跳转到第 {turn} 轮",
2574
+ "chat.turnNavigation.jumpLoad": "加载并跳转到第 {turn} 轮",
2331
2575
  "chat.turnNavigation.turn": "第 {turn} 轮",
2332
2576
  "settings.transcript.title": "对话显示",
2333
2577
  "settings.transcript.description": "控制已完成轮次的过程内容",
@@ -2440,6 +2684,7 @@ window.__ModuleLoader__.load({
2440
2684
  "chat.deepDiving": "Deep diving...",
2441
2685
  "chat.turnNavigation.label": "Turn navigation",
2442
2686
  "chat.turnNavigation.jump": "Jump to turn {turn}",
2687
+ "chat.turnNavigation.jumpLoad": "Load and jump to turn {turn}",
2443
2688
  "chat.turnNavigation.turn": "Turn {turn}",
2444
2689
  "settings.transcript.title": "Conversation display",
2445
2690
  "settings.transcript.description": "Controls process content in completed turns",
@@ -2525,40 +2770,6 @@ window.__ModuleLoader__.load({
2525
2770
  "clock.ymd": "{y}-{m}-{d}"
2526
2771
  };
2527
2772
  //#endregion
2528
- //#region lib/types/client/chat/use-throttled-visual-update.js
2529
- /** Frame-throttled scheduling for non-essential visual alignment. */
2530
- const DEFAULT_INTERVAL_FRAMES = 3;
2531
- /**
2532
- * Return a stable scheduler that coalesces visual updates over a frame interval.
2533
- * @param update - DOM alignment to run after the throttle interval.
2534
- * @param intervalFrames - frames to wait before applying the latest alignment.
2535
- * @returns a stable function that schedules the latest update.
2536
- */
2537
- function useThrottledVisualUpdate(update, intervalFrames = DEFAULT_INTERVAL_FRAMES) {
2538
- const updateRef = (0, react.useRef)(update);
2539
- updateRef.current = update;
2540
- const pendingFrameRef = (0, react.useRef)(null);
2541
- (0, react.useLayoutEffect)(() => () => {
2542
- if (pendingFrameRef.current === null) return;
2543
- cancelAnimationFrame(pendingFrameRef.current);
2544
- pendingFrameRef.current = null;
2545
- }, []);
2546
- return (0, react.useCallback)(() => {
2547
- if (pendingFrameRef.current !== null) return;
2548
- let remainingFrames = intervalFrames;
2549
- const advance = () => {
2550
- remainingFrames -= 1;
2551
- if (remainingFrames > 0) {
2552
- pendingFrameRef.current = requestAnimationFrame(advance);
2553
- return;
2554
- }
2555
- pendingFrameRef.current = null;
2556
- updateRef.current();
2557
- };
2558
- pendingFrameRef.current = requestAnimationFrame(advance);
2559
- }, [intervalFrames]);
2560
- }
2561
- //#endregion
2562
2773
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/accessibility.module.css.mjs
2563
2774
  const css$9 = ".nrZFeq_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
2564
2775
  const tagId$9 = "@prettier-ai/dsh-client-ui-chat/accessibility.module.css";
@@ -2572,7 +2783,7 @@ window.__ModuleLoader__.load({
2572
2783
  var accessibility_module_css_default = { "visuallyHidden": "nrZFeq_visuallyHidden" };
2573
2784
  //#endregion
2574
2785
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/ReasoningRow.module.css.mjs
2575
- const css$8 = ".xhC5dq_root{flex-direction:column;display:flex}.xhC5dq_row{position:relative;overflow:hidden}.xhC5dq_root[data-state=running] .xhC5dq_row:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite xhC5dq_dsh-reasoning-row-sweep;position:absolute;left:0}@keyframes xhC5dq_dsh-reasoning-row-sweep{0%{left:-300px}90%,to{left:100%}}.xhC5dq_leading{flex-shrink:0}.xhC5dq_chevron{color:var(--dsw-alias-label-secondary)}.xhC5dq_title{font-weight:400}.xhC5dq_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.xhC5dq_summary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.xhC5dq_summary[data-follow-end]{text-overflow:clip}.xhC5dq_thinkBody{padding:4px 0 4px calc(22px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));white-space:pre-wrap;word-break:break-word}@media (prefers-reduced-motion:reduce){.xhC5dq_root[data-state=running] .xhC5dq_row:after{animation:none}}";
2786
+ const css$8 = ".xhC5dq_root{flex-direction:column;display:flex}.xhC5dq_root:not([data-expanded]){contain:size layout;height:calc(24px + var(--dsh-content-font-delta,0px))}.xhC5dq_row{position:relative;overflow:hidden}.xhC5dq_root[data-state=running] .xhC5dq_row:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite xhC5dq_dsh-reasoning-row-sweep;position:absolute;left:0}@keyframes xhC5dq_dsh-reasoning-row-sweep{0%{left:-300px}90%,to{left:100%}}.xhC5dq_leading{flex-shrink:0}.xhC5dq_chevron{color:var(--dsw-alias-label-secondary)}.xhC5dq_title{font-weight:400}.xhC5dq_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.xhC5dq_summary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));white-space:nowrap;flex:auto;overflow:hidden}.xhC5dq_summaryText{text-overflow:ellipsis;display:block;overflow:hidden}.xhC5dq_summary[data-follow-end]{justify-content:flex-end;display:flex}.xhC5dq_summary[data-follow-end] .xhC5dq_summaryText{text-align:start;text-overflow:clip;flex:none;width:max-content;min-width:100%;overflow:visible}.xhC5dq_thinkBody{padding:4px 0 4px calc(22px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));white-space:pre-wrap;word-break:break-word}@media (prefers-reduced-motion:reduce){.xhC5dq_root[data-state=running] .xhC5dq_row:after{animation:none}}";
2576
2787
  const tagId$8 = "@prettier-ai/dsh-client-ui-chat/ReasoningRow.module.css";
2577
2788
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$8) + "]") === null) {
2578
2789
  const tag = document.createElement("style");
@@ -2589,6 +2800,7 @@ window.__ModuleLoader__.load({
2589
2800
  "row": "xhC5dq_row",
2590
2801
  "separator": "xhC5dq_separator",
2591
2802
  "summary": "xhC5dq_summary",
2803
+ "summaryText": "xhC5dq_summaryText",
2592
2804
  "thinkBody": "xhC5dq_thinkBody",
2593
2805
  "title": "xhC5dq_title"
2594
2806
  };
@@ -2613,24 +2825,12 @@ window.__ModuleLoader__.load({
2613
2825
  */
2614
2826
  function ReasoningRow({ text, running, t }) {
2615
2827
  const [expanded, setExpanded] = (0, react.useState)(false);
2616
- const summaryRef = (0, react.useRef)(null);
2617
2828
  const summary = running ? latestLine(text) : firstLine(text);
2618
- const scheduleSummaryScroll = useThrottledVisualUpdate(() => {
2619
- const element = summaryRef.current;
2620
- if (element === null) return;
2621
- element.scrollLeft = running ? element.scrollWidth - element.clientWidth : 0;
2622
- });
2623
- (0, react.useEffect)(() => {
2624
- scheduleSummaryScroll();
2625
- }, [
2626
- running,
2627
- scheduleSummaryScroll,
2628
- summary
2629
- ]);
2630
2829
  return (0, react_jsx_runtime.jsxs)("div", {
2631
2830
  className: ReasoningRow_module_css_default.root,
2632
2831
  "data-variant": "think",
2633
2832
  "data-state": running ? "running" : "ok",
2833
+ "data-expanded": expanded || void 0,
2634
2834
  children: [running && (0, react_jsx_runtime.jsx)("span", {
2635
2835
  className: accessibility_module_css_default.visuallyHidden,
2636
2836
  children: t("row.running")
@@ -2651,10 +2851,12 @@ window.__ModuleLoader__.load({
2651
2851
  className: ReasoningRow_module_css_default.separator,
2652
2852
  "aria-hidden": true
2653
2853
  }), (0, react_jsx_runtime.jsx)("span", {
2654
- ref: summaryRef,
2655
2854
  className: ReasoningRow_module_css_default.summary,
2656
2855
  "data-follow-end": running || void 0,
2657
- children: summary
2856
+ children: (0, react_jsx_runtime.jsx)("span", {
2857
+ className: ReasoningRow_module_css_default.summaryText,
2858
+ children: summary
2859
+ })
2658
2860
  })] }),
2659
2861
  children: (0, react_jsx_runtime.jsx)("div", {
2660
2862
  className: ReasoningRow_module_css_default.thinkBody,
@@ -2793,7 +2995,7 @@ window.__ModuleLoader__.load({
2793
2995
  });
2794
2996
  //#endregion
2795
2997
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/GenericCommandCard.module.css.mjs
2796
- const css$6 = "._0R17eG_root{flex-direction:column;display:flex}._0R17eG_row{position:relative;overflow:hidden}._0R17eG_root[data-state=running] ._0R17eG_row:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite _0R17eG_dsh-command-row-sweep;position:absolute;left:0}@keyframes _0R17eG_dsh-command-row-sweep{0%{left:-300px}90%,to{left:100%}}._0R17eG_leading{flex-shrink:0}._0R17eG_chevron{color:var(--dsw-alias-label-secondary)}._0R17eG_title{font-weight:400}._0R17eG_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}._0R17eG_summary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}._0R17eG_summary[data-error],._0R17eG_body[data-error]{color:var(--dsw-alias-state-error-primary)}._0R17eG_body{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-markdown-code-block);max-height:260px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-markdown-code-block-small);white-space:pre-wrap;border-radius:12px;margin:4px 0 4px 4px;padding:12px 16px;overflow:auto}@media (prefers-reduced-motion:reduce){._0R17eG_root[data-state=running] ._0R17eG_row:after{animation:none}}";
2998
+ const css$6 = "._0R17eG_root{flex-direction:column;display:flex}._0R17eG_row{position:relative;overflow:hidden}._0R17eG_root[data-state=running] ._0R17eG_row:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite _0R17eG_dsh-command-row-sweep;position:absolute;left:0}@keyframes _0R17eG_dsh-command-row-sweep{0%{left:-300px}90%,to{left:100%}}._0R17eG_leading{flex-shrink:0}._0R17eG_chevron{color:var(--dsw-alias-label-secondary)}._0R17eG_title{font-weight:400}._0R17eG_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}._0R17eG_summary{min-width:0;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}._0R17eG_summary[data-error],._0R17eG_body[data-error]{color:var(--dsw-alias-state-error-primary)}._0R17eG_body{border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-markdown-code-block);max-height:260px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-markdown-code-block-small);white-space:pre-wrap;border-radius:12px;margin:4px 0 4px 4px;padding:12px 16px;overflow:auto}@media (prefers-reduced-motion:reduce){._0R17eG_root[data-state=running] ._0R17eG_row:after{animation:none}}";
2797
2999
  const tagId$6 = "@prettier-ai/dsh-client-ui-chat/GenericCommandCard.module.css";
2798
3000
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$6) + "]") === null) {
2799
3001
  const tag = document.createElement("style");
@@ -2969,7 +3171,7 @@ window.__ModuleLoader__.load({
2969
3171
  });
2970
3172
  //#endregion
2971
3173
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/TurnProcessNodeView.module.css.mjs
2972
- const css$5 = ".SfQmhG_root{box-sizing:border-box;border:none;border-bottom:1px solid var(--dsw-alias-border-l2);width:100%;min-width:0;height:33px;color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:left;background:0 0;align-items:center;padding:0 0 8px;display:flex}.SfQmhG_root:not([data-open]){margin-bottom:8px}.SfQmhG_chevron{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;margin-left:6px;transition:transform .1s;transform:rotate(-90deg)}.SfQmhG_root[data-open] .SfQmhG_chevron{transform:rotate(0)}.SfQmhG_label{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:24px;overflow:hidden}@media (prefers-reduced-motion:reduce){.SfQmhG_chevron{transition:none}}";
3174
+ const css$5 = ".SfQmhG_root{box-sizing:border-box;border:none;border-bottom:.5px solid var(--dsw-alias-border-l2);width:100%;min-width:0;height:33px;color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:left;background:0 0;align-items:center;padding:0 0 8px;display:flex}.SfQmhG_root:not([data-open]){margin-bottom:8px}.SfQmhG_chevron{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;margin-left:6px;transition:transform .1s;transform:rotate(-90deg)}.SfQmhG_root[data-open] .SfQmhG_chevron{transform:rotate(0)}.SfQmhG_label{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:24px;overflow:hidden}@media (prefers-reduced-motion:reduce){.SfQmhG_chevron{transition:none}}";
2973
3175
  const tagId$5 = "@prettier-ai/dsh-client-ui-chat/TurnProcessNodeView.module.css";
2974
3176
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
2975
3177
  const tag = document.createElement("style");
@@ -3097,7 +3299,7 @@ window.__ModuleLoader__.load({
3097
3299
  }
3098
3300
  //#endregion
3099
3301
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/chat/TurnUsagePanel.module.css.mjs
3100
- const css$4 = ".ZVOUHW_root{min-width:0;display:inline-flex}.ZVOUHW_root+.ZVOUHW_root{margin-left:-6px}.ZVOUHW_trigger{min-width:0;height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);font-variant-numeric:tabular-nums;line-height:calc(24px + var(--dsh-content-font-delta,0px));white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:28px;align-items:center;gap:4px;padding:6px 8px;display:inline-flex}.ZVOUHW_label{text-overflow:ellipsis;min-width:0;overflow:hidden}.ZVOUHW_trigger svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px));flex:none}.ZVOUHW_trigger:hover,.ZVOUHW_trigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}@media (width<=480px){.ZVOUHW_trigger{width:calc(28px + var(--dsh-content-font-delta,0px));justify-content:center;padding:6px}.ZVOUHW_trigger .ZVOUHW_label{display:none}.ZVOUHW_root+.ZVOUHW_root{margin-left:0}}.ZVOUHW_panel{z-index:1100;box-sizing:border-box;border:1px solid var(--dsw-alias-border-inverted);background:var(--dsw-specific-menu);width:max-content;min-width:min(300px,100vw - 24px);max-width:min(440px,100vw - 24px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-secondary);cursor:default;border-radius:12px;padding:16px;font-size:12px;line-height:18px;position:fixed}.ZVOUHW_title{color:var(--dsw-alias-label-primary);justify-content:space-between;gap:16px;margin-bottom:8px;font-weight:500;display:flex}.ZVOUHW_titleRule{border-top:1px solid var(--dsw-alias-border-l2);margin-bottom:10px}.ZVOUHW_titleValue{font-variant-numeric:tabular-nums}.ZVOUHW_titleLabel{align-items:center;gap:6px;min-width:0;display:inline-flex}.ZVOUHW_titleLabel svg{flex:none;width:14px;height:14px}.ZVOUHW_details{color:var(--dsw-alias-label-tertiary);grid-template-columns:minmax(76px,auto) minmax(0,1fr);gap:6px 16px;margin:0;display:grid}.ZVOUHW_details dt,.ZVOUHW_details dd{min-width:0;margin:0}.ZVOUHW_details dd{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-align:right}.ZVOUHW_details .ZVOUHW_route{overflow-wrap:anywhere}.ZVOUHW_reasoning{color:var(--dsw-alias-label-tertiary);white-space:nowrap}";
3302
+ const css$4 = ".ZVOUHW_root{min-width:0;display:inline-flex}.ZVOUHW_root+.ZVOUHW_root{margin-left:-6px}.ZVOUHW_trigger{min-width:0;height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);font-variant-numeric:tabular-nums;line-height:calc(24px + var(--dsh-content-font-delta,0px));white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:28px;align-items:center;gap:4px;padding:6px 8px;display:inline-flex}.ZVOUHW_label{text-overflow:ellipsis;min-width:0;overflow:hidden}.ZVOUHW_trigger svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px));flex:none}.ZVOUHW_trigger:hover,.ZVOUHW_trigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}@media (width<=480px){.ZVOUHW_trigger{width:calc(28px + var(--dsh-content-font-delta,0px));justify-content:center;padding:6px}.ZVOUHW_trigger .ZVOUHW_label{display:none}.ZVOUHW_root+.ZVOUHW_root{margin-left:0}}.ZVOUHW_panel{z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);width:max-content;min-width:min(300px,100vw - 24px);max-width:min(440px,100vw - 24px);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-secondary);cursor:default;border:0;border-radius:12px;padding:16px;font-size:12px;line-height:18px;position:fixed}.ZVOUHW_title{color:var(--dsw-alias-label-primary);justify-content:space-between;gap:16px;margin-bottom:8px;font-weight:500;display:flex}.ZVOUHW_titleRule{border-top:.5px solid var(--dsw-alias-border-l2);margin-bottom:10px}.ZVOUHW_titleValue{font-variant-numeric:tabular-nums}.ZVOUHW_titleLabel{align-items:center;gap:6px;min-width:0;display:inline-flex}.ZVOUHW_titleLabel svg{flex:none;width:14px;height:14px}.ZVOUHW_details{color:var(--dsw-alias-label-tertiary);grid-template-columns:minmax(76px,auto) minmax(0,1fr);gap:6px 16px;margin:0;display:grid}.ZVOUHW_details dt,.ZVOUHW_details dd{min-width:0;margin:0}.ZVOUHW_details dd{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-align:right}.ZVOUHW_details .ZVOUHW_route{overflow-wrap:anywhere}.ZVOUHW_reasoning{color:var(--dsw-alias-label-tertiary);white-space:nowrap}";
3101
3303
  const tagId$4 = "@prettier-ai/dsh-client-ui-chat/TurnUsagePanel.module.css";
3102
3304
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
3103
3305
  const tag = document.createElement("style");
@@ -3628,6 +3830,42 @@ window.__ModuleLoader__.load({
3628
3830
  function billedInputTokens(usage) {
3629
3831
  return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
3630
3832
  }
3833
+ /** Render and measure one non-empty statistics line. */
3834
+ const StatsLineContent = (0, react.memo)(function StatsLineContent({ groups, line }) {
3835
+ const rootRef = (0, react.useRef)(null);
3836
+ const [truncated, setTruncated] = (0, react.useState)(false);
3837
+ const measure = (0, react.useCallback)(() => {
3838
+ const el = rootRef.current;
3839
+ if (el === null) return;
3840
+ const next = el.scrollWidth > el.clientWidth;
3841
+ setTruncated((current) => current === next ? current : next);
3842
+ }, []);
3843
+ (0, react.useLayoutEffect)(() => {
3844
+ const el = rootRef.current;
3845
+ if (el === null || typeof ResizeObserver === "undefined") return;
3846
+ const observer = new ResizeObserver(measure);
3847
+ observer.observe(el);
3848
+ return () => {
3849
+ observer.disconnect();
3850
+ };
3851
+ }, [measure]);
3852
+ (0, react.useLayoutEffect)(measure, [line, measure]);
3853
+ return (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.Tooltip, {
3854
+ label: line,
3855
+ side: "top",
3856
+ delayMs: 500,
3857
+ disabled: !truncated,
3858
+ children: (0, react_jsx_runtime.jsx)("div", {
3859
+ ref: rootRef,
3860
+ className: StatsLine_module_css_default.root,
3861
+ children: groups.map((group, i) => (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [i > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
3862
+ className: StatsLine_module_css_default.sep,
3863
+ "aria-hidden": true,
3864
+ children: "|"
3865
+ }), " "] }), (0, react_jsx_runtime.jsx)("span", { children: group })] }, group))
3866
+ })
3867
+ });
3868
+ });
3631
3869
  const StatsLine = (0, react.memo)(function StatsLine({ useChat, useProjection, t }) {
3632
3870
  const settledNodes = useChat((s) => s.legacy.nodes);
3633
3871
  const usage = useProjection("tokenUsage");
@@ -3657,37 +3895,10 @@ window.__ModuleLoader__.load({
3657
3895
  }));
3658
3896
  }
3659
3897
  const line = groups.join(" | ");
3660
- const rootRef = (0, react.useRef)(null);
3661
- const [truncated, setTruncated] = (0, react.useState)(false);
3662
- (0, react.useLayoutEffect)(() => {
3663
- const el = rootRef.current;
3664
- if (el === null) return;
3665
- const measure = () => {
3666
- setTruncated(el.scrollWidth > el.clientWidth);
3667
- };
3668
- measure();
3669
- if (typeof ResizeObserver === "undefined") return;
3670
- const observer = new ResizeObserver(measure);
3671
- observer.observe(el);
3672
- return () => {
3673
- observer.disconnect();
3674
- };
3675
- }, [line]);
3676
3898
  if (groups.length === 0) return null;
3677
- return (0, react_jsx_runtime.jsx)(_prettier_ai_dsh_client_ui_primitives.Tooltip, {
3678
- label: line,
3679
- side: "top",
3680
- delayMs: 500,
3681
- disabled: !truncated,
3682
- children: (0, react_jsx_runtime.jsx)("div", {
3683
- ref: rootRef,
3684
- className: StatsLine_module_css_default.root,
3685
- children: groups.map((group, i) => (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [i > 0 && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
3686
- className: StatsLine_module_css_default.sep,
3687
- "aria-hidden": true,
3688
- children: "|"
3689
- }), " "] }), (0, react_jsx_runtime.jsx)("span", { children: group })] }, group))
3690
- })
3899
+ return (0, react_jsx_runtime.jsx)(StatsLineContent, {
3900
+ groups,
3901
+ line
3691
3902
  });
3692
3903
  });
3693
3904
  //#endregion
@@ -4245,6 +4456,10 @@ window.__ModuleLoader__.load({
4245
4456
  }
4246
4457
  };
4247
4458
  }
4459
+ function publishedAssistantData(context) {
4460
+ const location = context.start?.location ?? context.matches.at(-1)?.location;
4461
+ return location?.kind === "step" ? location.step.data.get("assistant-step") : void 0;
4462
+ }
4248
4463
  /** Per-step Assistant streaming/final/interruption Definition. */
4249
4464
  const assistantDefinition = {
4250
4465
  kind: "assistant-step",
@@ -4309,15 +4524,17 @@ window.__ModuleLoader__.load({
4309
4524
  };
4310
4525
  },
4311
4526
  buildViewNode: (context) => {
4312
- const projected = projectAssistant(context);
4313
- if (projected === void 0) return null;
4314
- if (projected.settled === void 0 && !projected.visible) {
4315
- const state = context.state ?? fallbackState$5(context);
4316
- if (state === void 0) return null;
4527
+ const state = context.state ?? fallbackState$5(context);
4528
+ if (state === void 0) return null;
4529
+ const data = publishedAssistantData(context);
4530
+ if (data === void 0) return null;
4531
+ const settled = data.finalNode;
4532
+ const visible = settled === void 0 ? state.visibleBlocks > 0 : hasVisibleContent(data.blocks);
4533
+ if (settled === void 0 && !visible) {
4317
4534
  const current = context.current.get("chat");
4318
4535
  if (!state.hidden || current === void 0 || current === null) return null;
4319
4536
  }
4320
- return chatNode(context, "assistant-step", projected.anchorSeq, projected.data, { visibility: projected.settled?.interrupted === true || projected.visible ? "visible" : "hidden" });
4537
+ return chatNode(context, "assistant-step", settled?.seq ?? state.firstVisibleSeq ?? context.matches[0]?.event.seq ?? 0, data, { visibility: settled?.interrupted === true || visible ? "visible" : "hidden" });
4321
4538
  }
4322
4539
  };
4323
4540
  /**
@@ -4348,27 +4565,42 @@ window.__ModuleLoader__.load({
4348
4565
  //#endregion
4349
4566
  //#region lib/types/client/conversation-nodes/turn-navigation.js
4350
4567
  /**
4351
- * Preview budget per field. The rail clamps two short lines, so anything past
4352
- * this is invisible; copying whole transcripts into navigation state would
4568
+ * Preview budgets, sized to the rail card's clamps (one prompt line, up to
4569
+ * three response lines) and mirrored by the turnOutline projection so a turn
4570
+ * shows the same words before and after its events load. Anything past a
4571
+ * budget is invisible; copying whole transcripts into navigation state would
4353
4572
  * otherwise grow with the loaded window on every structural update.
4354
4573
  */
4355
- const PREVIEW_LIMIT = 160;
4356
- /** Join rendered text until the preview budget is met, then stop reading. */
4357
- function preview(parts) {
4574
+ const PROMPT_PREVIEW_LIMIT = 50;
4575
+ const RESPONSE_PREVIEW_LIMIT = 120;
4576
+ /** Join rendered text, collapse whitespace, and cap at `limit` with a trailing ellipsis when clipped. */
4577
+ function preview(parts, limit) {
4358
4578
  let text = "";
4579
+ let unread = false;
4359
4580
  for (const part of parts) {
4360
- text += text === "" ? part : ` ${part}`;
4361
- if (text.length >= PREVIEW_LIMIT) break;
4581
+ if (text.length >= limit * 2) {
4582
+ unread = true;
4583
+ break;
4584
+ }
4585
+ const clipped = part.length > limit * 2;
4586
+ const chunk = clipped ? part.slice(0, limit * 2) : part;
4587
+ text += text === "" ? chunk : ` ${chunk}`;
4588
+ if (clipped) {
4589
+ unread = true;
4590
+ break;
4591
+ }
4362
4592
  }
4363
- return text.replace(/\s+/g, " ").trim().slice(0, PREVIEW_LIMIT);
4593
+ const normalized = text.replace(/\s+/g, " ").trim();
4594
+ if (normalized.length > limit - 1) return `${normalized.slice(0, limit - 1).trimEnd()}…`;
4595
+ return unread ? `${normalized}…` : normalized;
4364
4596
  }
4365
4597
  function promptText(node) {
4366
4598
  if (node.kind !== "user") return "";
4367
- return preview(node.data.content.flatMap((block) => block.type === "text" ? [block.text] : []));
4599
+ return preview(node.data.content.flatMap((block) => block.type === "text" ? [block.text] : []), PROMPT_PREVIEW_LIMIT);
4368
4600
  }
4369
4601
  function responseText(node) {
4370
4602
  if (node.kind !== "assistant-step") return "";
4371
- return preview(node.data.blocks.flatMap((block) => block.kind === "text" ? [block.text] : []));
4603
+ return preview(node.data.blocks.flatMap((block) => block.kind === "text" ? [block.text] : []), RESPONSE_PREVIEW_LIMIT);
4372
4604
  }
4373
4605
  /**
4374
4606
  * Whether two items carry the same rail state, so the reader can keep its array.
@@ -4401,6 +4633,92 @@ window.__ModuleLoader__.load({
4401
4633
  };
4402
4634
  }
4403
4635
  //#endregion
4636
+ //#region lib/types/client/conversation-nodes/turn-process-presentation.js
4637
+ function nodeTurn(node) {
4638
+ const location = node?.location;
4639
+ return location?.kind === "turn" || location?.kind === "step" ? location.turn.turn : void 0;
4640
+ }
4641
+ function samePresentation(left, right) {
4642
+ return left === right || left !== void 0 && right !== void 0 && left.spec === right.spec && left.turn === right.turn && left.turnClosed === right.turnClosed && left.hasExternalProcess === right.hasExternalProcess && left.compactAnswer === right.compactAnswer;
4643
+ }
4644
+ function derivePresentation(turn, locations, nodes) {
4645
+ const keys = locations.getTurn(turn);
4646
+ const control = keys.map((key) => nodes.get(key)).find((node) => node?.kind === "turn-process");
4647
+ if (control === void 0) return void 0;
4648
+ const spec = control.data;
4649
+ const location = control.location;
4650
+ if (location.kind !== "turn" && location.kind !== "step") return void 0;
4651
+ let openingHumanAnchor;
4652
+ for (const key of keys) {
4653
+ const node = nodes.get(key);
4654
+ if ((node?.kind === "user" || node?.kind === "steering") && node.anchorSeq < spec.controlAnchorSeq) openingHumanAnchor = Math.min(openingHumanAnchor ?? node.anchorSeq, node.anchorSeq);
4655
+ }
4656
+ let hasExternalProcess = false;
4657
+ let compactAnswer = true;
4658
+ for (const key of keys) {
4659
+ const node = nodes.get(key);
4660
+ if (node === void 0 || node.kind === "turn-process") continue;
4661
+ if ((node.kind === "user" || node.kind === "steering") && (openingHumanAnchor === void 0 || node.anchorSeq > openingHumanAnchor) && (spec.answerAnchorSeq === null || node.anchorSeq < spec.answerAnchorSeq)) compactAnswer = false;
4662
+ if (TURN_PROCESS_INDEPENDENT_KINDS.has(node.kind) || node.anchorSeq < spec.processStartSeq || spec.answerAnchorSeq !== null && node.anchorSeq >= spec.answerAnchorSeq) continue;
4663
+ if (node.kind !== "assistant-step" || spec.answerStep === null || node.data.step !== spec.answerStep) hasExternalProcess = true;
4664
+ }
4665
+ return {
4666
+ turn,
4667
+ spec,
4668
+ turnClosed: location.turn.status === "closed",
4669
+ hasExternalProcess,
4670
+ compactAnswer
4671
+ };
4672
+ }
4673
+ /** Mutable projection of cross-Node process layout facts by Turn. */
4674
+ var ChatTurnProcessProjector = class {
4675
+ presentations = /* @__PURE__ */ new Map();
4676
+ /**
4677
+ * Read the retained process presentation for a Node's Turn.
4678
+ * @param node - Current Chat Node.
4679
+ * @returns The Turn's process presentation, when present.
4680
+ */
4681
+ get(node) {
4682
+ const turn = nodeTurn(node);
4683
+ return turn === void 0 ? void 0 : this.presentations.get(turn);
4684
+ }
4685
+ /**
4686
+ * Replace every projected Turn.
4687
+ * @param order - visible Chat Node order.
4688
+ * @param locations - current Chat Location index.
4689
+ * @param nodes - current Chat Node store.
4690
+ * @returns Turns whose process presentation changed.
4691
+ */
4692
+ replace(order, locations, nodes) {
4693
+ const turns = /* @__PURE__ */ new Set();
4694
+ for (const key of order) {
4695
+ const turn = nodeTurn(nodes.get(key));
4696
+ if (turn !== void 0) turns.add(turn);
4697
+ }
4698
+ const changed = /* @__PURE__ */ new Set();
4699
+ for (const turn of new Set([...this.presentations.keys(), ...turns])) if (this.set(turn, turns.has(turn) ? derivePresentation(turn, locations, nodes) : void 0)) changed.add(turn);
4700
+ return changed;
4701
+ }
4702
+ /**
4703
+ * Recompute selected Turns after incremental Node changes.
4704
+ * @param turns - affected Turn numbers.
4705
+ * @param locations - current Chat Location index.
4706
+ * @param nodes - current Chat Node store.
4707
+ * @returns Turns whose process presentation changed.
4708
+ */
4709
+ update(turns, locations, nodes) {
4710
+ const changed = /* @__PURE__ */ new Set();
4711
+ for (const turn of turns) if (this.set(turn, derivePresentation(turn, locations, nodes))) changed.add(turn);
4712
+ return changed;
4713
+ }
4714
+ set(turn, next) {
4715
+ if (samePresentation(this.presentations.get(turn), next)) return false;
4716
+ if (next === void 0) this.presentations.delete(turn);
4717
+ else this.presentations.set(turn, next);
4718
+ return true;
4719
+ }
4720
+ };
4721
+ //#endregion
4404
4722
  //#region lib/types/client/conversation-nodes/chat-snapshot-builder.js
4405
4723
  const EMPTY_KEYS = [];
4406
4724
  const EMPTY_TURNS = [];
@@ -4409,13 +4727,59 @@ window.__ModuleLoader__.load({
4409
4727
  function sameReferences$1(left, right) {
4410
4728
  return left.length === right.length && left.every((value, index) => value === right[index]);
4411
4729
  }
4730
+ function cachedSource(sources, key, create) {
4731
+ let source = sources.get(key);
4732
+ if (source === void 0) {
4733
+ source = create();
4734
+ sources.set(key, source);
4735
+ }
4736
+ return source;
4737
+ }
4738
+ var MutableChatSource = class {
4739
+ read;
4740
+ label;
4741
+ listeners = /* @__PURE__ */ new Set();
4742
+ published;
4743
+ constructor(read, label) {
4744
+ this.read = read;
4745
+ this.label = label;
4746
+ this.published = read();
4747
+ }
4748
+ getSnapshot = () => this.read();
4749
+ subscribe = (listener) => {
4750
+ this.listeners.add(listener);
4751
+ return () => {
4752
+ this.listeners.delete(listener);
4753
+ };
4754
+ };
4755
+ publish() {
4756
+ const next = this.getSnapshot();
4757
+ if (this.published === next) return;
4758
+ this.published = next;
4759
+ (0, _prettier_ai_dsh_client_store.notifySubscribers)(this.listeners, this.label);
4760
+ }
4761
+ };
4412
4762
  var MutableChatNodeStore = class {
4413
4763
  byKey = /* @__PURE__ */ new Map();
4764
+ turnProcesses = new ChatTurnProcessProjector();
4765
+ sources = /* @__PURE__ */ new Map();
4766
+ processSources = /* @__PURE__ */ new Map();
4767
+ dirtyKeys = /* @__PURE__ */ new Set();
4768
+ dirtyProcessKeys = /* @__PURE__ */ new Set();
4414
4769
  valuesCache = EMPTY_LIST;
4415
4770
  valuesDirty = false;
4416
4771
  get(key) {
4417
4772
  return this.byKey.get(key);
4418
4773
  }
4774
+ source(key) {
4775
+ return cachedSource(this.sources, key, () => new MutableChatSource(() => this.get(key), `[ui-chat] node source ${key}`));
4776
+ }
4777
+ processSource(key) {
4778
+ return cachedSource(this.processSources, key, () => new MutableChatSource(() => this.process(key), `[ui-chat] node process source ${key}`));
4779
+ }
4780
+ process(key) {
4781
+ return this.turnProcesses.get(this.get(key));
4782
+ }
4419
4783
  values() {
4420
4784
  if (this.valuesDirty) {
4421
4785
  this.valuesCache = [...this.byKey.values()];
@@ -4424,8 +4788,20 @@ window.__ModuleLoader__.load({
4424
4788
  return this.valuesCache;
4425
4789
  }
4426
4790
  replace(nodes) {
4791
+ const previous = new Map(this.byKey);
4427
4792
  this.byKey.clear();
4428
- for (const node of nodes) this.byKey.set(node.key, node);
4793
+ for (const node of nodes) {
4794
+ this.byKey.set(node.key, node);
4795
+ if (previous.get(node.key) !== node) {
4796
+ this.dirtyKeys.add(node.key);
4797
+ this.dirtyProcessKeys.add(node.key);
4798
+ }
4799
+ previous.delete(node.key);
4800
+ }
4801
+ for (const key of previous.keys()) {
4802
+ this.dirtyKeys.add(key);
4803
+ this.dirtyProcessKeys.add(key);
4804
+ }
4429
4805
  this.valuesCache = [...this.byKey.values()];
4430
4806
  this.valuesDirty = false;
4431
4807
  }
@@ -4434,10 +4810,29 @@ window.__ModuleLoader__.load({
4434
4810
  for (const node of nodes) {
4435
4811
  if (this.byKey.get(node.key) === node) continue;
4436
4812
  this.byKey.set(node.key, node);
4813
+ this.dirtyKeys.add(node.key);
4814
+ this.dirtyProcessKeys.add(node.key);
4437
4815
  changed = true;
4438
4816
  }
4439
4817
  if (changed) this.valuesDirty = true;
4440
4818
  }
4819
+ touchProcesses(turns, locations) {
4820
+ for (const turn of turns) for (const key of locations.getTurn(turn)) this.dirtyProcessKeys.add(key);
4821
+ }
4822
+ replaceProcesses(order, locations) {
4823
+ this.touchProcesses(this.turnProcesses.replace(order, locations, this), locations);
4824
+ }
4825
+ updateProcesses(turns, locations) {
4826
+ this.touchProcesses(this.turnProcesses.update(turns, locations, this), locations);
4827
+ }
4828
+ publish() {
4829
+ const dirty = [...this.dirtyKeys];
4830
+ const dirtyProcesses = [...this.dirtyProcessKeys];
4831
+ this.dirtyKeys.clear();
4832
+ this.dirtyProcessKeys.clear();
4833
+ for (const key of dirty) this.sources.get(key)?.publish();
4834
+ for (const key of dirtyProcesses) this.processSources.get(key)?.publish();
4835
+ }
4441
4836
  };
4442
4837
  var MutableChatLocationIndex = class {
4443
4838
  turns = /* @__PURE__ */ new Map();
@@ -4553,6 +4948,15 @@ window.__ModuleLoader__.load({
4553
4948
  if (location.kind === "turn") return { turn: location.turn.turn };
4554
4949
  return {};
4555
4950
  }
4951
+ function locationTurnStatus(location) {
4952
+ return location.kind === "turn" || location.kind === "step" ? location.turn.status : void 0;
4953
+ }
4954
+ function processPresentationInputChanged(previous, next, structural) {
4955
+ if (structural || previous === void 0) return true;
4956
+ if (locationTurnStatus(previous.location) !== locationTurnStatus(next.location)) return true;
4957
+ if (previous.kind === "turn-process" && next.kind === "turn-process") return previous.data !== next.data;
4958
+ return previous.kind === "assistant-step" && next.kind === "assistant-step" && previous.data.step !== next.data.step;
4959
+ }
4556
4960
  function turnProcessPresentations(nodes) {
4557
4961
  const presentations = /* @__PURE__ */ new Map();
4558
4962
  for (const raw of nodes) {
@@ -4911,12 +5315,16 @@ window.__ModuleLoader__.load({
4911
5315
  this.store.replace(nodes);
4912
5316
  this.order = orderedVisibleChatNodes(nodes).map((node) => node.key);
4913
5317
  this.locations.rebuild(this.order, this.store);
5318
+ this.store.replaceProcesses(this.order, this.locations);
4914
5319
  this.navigation.rebuild(input.timeline, this.locations, this.store);
4915
5320
  this.timeline = input.timeline;
4916
- return this.snapshot(input.timeline, this.legacy.replace(nodes, input.timeline));
5321
+ const snapshot = this.snapshot(input.timeline, this.legacy.replace(nodes, input.timeline));
5322
+ this.store.publish();
5323
+ return snapshot;
4917
5324
  }
4918
5325
  apply(input) {
4919
5326
  const upserts = this.referenceLabels.apply(input.upserts, this.store);
5327
+ const processTurns = /* @__PURE__ */ new Set();
4920
5328
  let structural = false;
4921
5329
  const contentOnly = [];
4922
5330
  for (const node of upserts) {
@@ -4924,6 +5332,12 @@ window.__ModuleLoader__.load({
4924
5332
  const nodeStructural = previous === void 0 || previous.kind !== node.kind || previous.anchorSeq !== node.anchorSeq || previous.visibility !== node.visibility || locationIdentity(previous.location) !== locationIdentity(node.location);
4925
5333
  structural ||= nodeStructural;
4926
5334
  if (!nodeStructural) contentOnly.push(node);
5335
+ if (processPresentationInputChanged(previous, node, nodeStructural)) {
5336
+ const previousTurn = previous === void 0 ? void 0 : locationCoordinates(previous.location).turn;
5337
+ const nextTurn = locationCoordinates(node.location).turn;
5338
+ if (previousTurn !== void 0) processTurns.add(previousTurn);
5339
+ if (nextTurn !== void 0) processTurns.add(nextTurn);
5340
+ }
4927
5341
  }
4928
5342
  this.store.upsert(upserts);
4929
5343
  if (structural) {
@@ -4932,10 +5346,13 @@ window.__ModuleLoader__.load({
4932
5346
  this.locations.rebuild(this.order, this.store);
4933
5347
  }
4934
5348
  this.locations.touch(contentOnly);
5349
+ this.store.updateProcesses(processTurns, this.locations);
4935
5350
  if (structural || input.timeline !== this.timeline) this.navigation.rebuild(input.timeline, this.locations, this.store);
4936
5351
  else this.navigation.touch(turnsOf(contentOnly), this.locations, this.store);
4937
5352
  this.timeline = input.timeline;
4938
- return this.snapshot(input.timeline, this.legacy.apply(upserts, input.timeline));
5353
+ const snapshot = this.snapshot(input.timeline, this.legacy.apply(upserts, input.timeline));
5354
+ this.store.publish();
5355
+ return snapshot;
4939
5356
  }
4940
5357
  snapshot(timeline, legacy = this.legacy.replace(EMPTY_LIST, timeline)) {
4941
5358
  return {
@@ -5224,43 +5641,87 @@ window.__ModuleLoader__.load({
5224
5641
  }
5225
5642
  //#endregion
5226
5643
  //#region lib/types/client/conversation-nodes/inbox.js
5644
+ const EMPTY_PENDING = {
5645
+ kind: "snapshot",
5646
+ ids: []
5647
+ };
5648
+ const EMPTY_CURRENT_CLAIMED = /* @__PURE__ */ new Set();
5649
+ function materializePending(state) {
5650
+ const splices = [];
5651
+ let current = state;
5652
+ while (current.kind === "splice") {
5653
+ splices.push(current);
5654
+ current = current.previous;
5655
+ }
5656
+ const pending = [...current.ids];
5657
+ for (const splice of splices.reverse()) pending.splice(splice.start, splice.removedCount, ...splice.inserted);
5658
+ return pending;
5659
+ }
5660
+ function withoutInserted(claimed, inserted) {
5661
+ let next;
5662
+ for (const id of inserted) {
5663
+ if (!claimed.has(id)) continue;
5664
+ next ??= new Set(claimed);
5665
+ next.delete(id);
5666
+ }
5667
+ return next ?? claimed;
5668
+ }
5669
+ /**
5670
+ * Apply one next-step splice under the AgentLoop's durable event ordering.
5671
+ * An entered claim logs its complete message batch before another claim; a
5672
+ * rejected claim logs no messages, so only the current claim can classify a
5673
+ * later `user/message`.
5674
+ */
5227
5675
  function applySplice(previous, splice) {
5228
- const pending = [...previous?.state.pending ?? []];
5229
- const claimed = new Set(previous?.state.claimed ?? []);
5230
- const removed = pending.splice(splice.start, splice.removedCount ?? 0, ...splice.inserted);
5231
- for (const identity of splice.inserted) claimed.delete(identity.id);
5232
- if (splice.target === "next-step" && splice.outcome !== "canceled") for (const identity of removed) claimed.add(identity.id);
5676
+ const priorPending = previous?.state.pending ?? EMPTY_PENDING;
5677
+ const inserted = splice.inserted.map((identity) => identity.id);
5678
+ const removedCount = splice.removedCount ?? 0;
5679
+ if (removedCount > 0 && splice.outcome !== "canceled") {
5680
+ const pending = materializePending(priorPending);
5681
+ const removed = pending.splice(splice.start, removedCount, ...inserted);
5682
+ return {
5683
+ pending: {
5684
+ kind: "snapshot",
5685
+ ids: pending
5686
+ },
5687
+ currentClaimed: new Set(removed)
5688
+ };
5689
+ }
5690
+ const currentClaimed = withoutInserted(previous?.state.currentClaimed ?? EMPTY_CURRENT_CLAIMED, inserted);
5233
5691
  return {
5234
- pending,
5235
- claimed
5692
+ pending: {
5693
+ kind: "splice",
5694
+ previous: priorPending,
5695
+ start: splice.start,
5696
+ removedCount,
5697
+ inserted
5698
+ },
5699
+ currentClaimed
5236
5700
  };
5237
5701
  }
5238
- function inboxDefinition(target) {
5239
- const kind = `inbox-${target}`;
5240
- return {
5241
- kind,
5242
- match: (event) => event.type === "agent/inbox/spliced" && event.data.target === target ? {
5702
+ const NEXT_STEP_INBOX_KIND = "inbox-next-step";
5703
+ /** Persistent next-step Inbox state used to classify the current claimed batch as steering. */
5704
+ const nextStepInboxDefinition = {
5705
+ kind: NEXT_STEP_INBOX_KIND,
5706
+ match: (event) => {
5707
+ if (event.type === "agent/inbox/spliced" && event.data.target === "next-step") return {
5243
5708
  id: String(event.seq),
5244
5709
  role: "start"
5245
- } : null,
5246
- start: (_context, match, reader) => {
5247
- if (match.event.type !== "agent/inbox/spliced") throw new Error(`${kind} start requires agent/inbox/spliced`);
5248
- return applySplice(reader.previous(kind), match.event.data);
5249
- },
5250
- update: (context) => context.state,
5251
- publication: () => "none"
5252
- };
5253
- }
5254
- /** Cumulative next-turn inbox splice Definition. */
5255
- const nextTurnInboxDefinition = inboxDefinition("next-turn");
5256
- /** Cumulative next-step inbox splice Definition used to classify steering. */
5257
- const nextStepInboxDefinition = inboxDefinition("next-step");
5710
+ };
5711
+ return null;
5712
+ },
5713
+ start: (_context, match, reader) => {
5714
+ if (match.event.type !== "agent/inbox/spliced") throw new Error("inbox-next-step start requires agent/inbox/spliced");
5715
+ return applySplice(reader.previous(NEXT_STEP_INBOX_KIND), match.event.data);
5716
+ },
5717
+ update: (context) => context.state,
5718
+ publication: () => "none"
5719
+ };
5258
5720
  /**
5259
- * Register the two durable Inbox-state contributions.
5721
+ * Register the next-step Inbox state used by Chat message classification.
5260
5722
  * @param ctx - owning UI Conversation context.
5261
5723
  */
5262
5724
  function registerInboxConversationNodes(ctx) {
5263
- ctx.uiConversation.events.register(nextTurnInboxDefinition);
5264
5725
  ctx.uiConversation.events.register(nextStepInboxDefinition);
5265
5726
  }
5266
5727
  //#endregion
@@ -5290,7 +5751,7 @@ window.__ModuleLoader__.load({
5290
5751
  provenance: contextProvenance(event.data.source),
5291
5752
  form: contextForm(event.data.source)
5292
5753
  };
5293
- return reader.previous("inbox-next-step")?.state.claimed.has(String(event.data.id)) === true ? {
5754
+ return reader.previous("inbox-next-step")?.state.currentClaimed.has(String(event.data.id)) === true ? {
5294
5755
  kind: "steering",
5295
5756
  messageId: event.data.id,
5296
5757
  seq: event.seq,
@@ -5933,6 +6394,7 @@ window.__ModuleLoader__.load({
5933
6394
  turn,
5934
6395
  assistantStartByStep: /* @__PURE__ */ new Map(),
5935
6396
  messageCountByStep: /* @__PURE__ */ new Map(),
6397
+ messageCount: 0,
5936
6398
  toolCallCount: 0,
5937
6399
  subagentCount: 0
5938
6400
  };
@@ -5948,11 +6410,11 @@ window.__ModuleLoader__.load({
5948
6410
  return data.blocks.some((block) => block.kind === "tool-call") ? null : data;
5949
6411
  }
5950
6412
  function processSpec(state, turn) {
5951
- const controlAnchorSeq = Math.min(state.otherStartSeq ?? Number.POSITIVE_INFINITY, ...state.assistantStartByStep.values());
5952
- if (!Number.isFinite(controlAnchorSeq)) return null;
6413
+ const controlAnchorSeq = state.controlAnchorSeq;
6414
+ if (controlAnchorSeq === void 0) return null;
5953
6415
  const answer = latestAnswer(turn);
5954
6416
  const counts = {
5955
- messageCount: answer === null ? [...state.messageCountByStep.values()].reduce((total, count) => total + count, 0) : [...state.messageCountByStep].filter(([step]) => step < answer.step).reduce((total, [, count]) => total + count, 0),
6417
+ messageCount: answer === null ? state.messageCount : [...state.messageCountByStep].filter(([step]) => step < answer.step).reduce((total, [, count]) => total + count, 0),
5956
6418
  toolCallCount: state.toolCallCount,
5957
6419
  subagentCount: state.subagentCount
5958
6420
  };
@@ -5985,7 +6447,8 @@ window.__ModuleLoader__.load({
5985
6447
  messageCountByStep.set(event.data.step, (messageCountByStep.get(event.data.step) ?? 0) + 1);
5986
6448
  current = {
5987
6449
  ...current,
5988
- messageCountByStep
6450
+ messageCountByStep,
6451
+ messageCount: current.messageCount + 1
5989
6452
  };
5990
6453
  }
5991
6454
  if (event.type === "tool/call") {
@@ -6000,14 +6463,16 @@ window.__ModuleLoader__.load({
6000
6463
  if (evidence === void 0) return current;
6001
6464
  if (evidence.kind === "other") return current.otherStartSeq === void 0 ? {
6002
6465
  ...current,
6003
- otherStartSeq: evidence.seq
6466
+ otherStartSeq: evidence.seq,
6467
+ controlAnchorSeq: Math.min(current.controlAnchorSeq ?? Number.POSITIVE_INFINITY, evidence.seq)
6004
6468
  } : current;
6005
6469
  if (current.assistantStartByStep.has(evidence.step)) return current;
6006
6470
  const assistantStartByStep = new Map(current.assistantStartByStep);
6007
6471
  assistantStartByStep.set(evidence.step, evidence.seq);
6008
6472
  return {
6009
6473
  ...current,
6010
- assistantStartByStep
6474
+ assistantStartByStep,
6475
+ controlAnchorSeq: Math.min(current.controlAnchorSeq ?? Number.POSITIVE_INFINITY, evidence.seq)
6011
6476
  };
6012
6477
  }
6013
6478
  /** Turn-scoped process range and answer-boundary Definition. */
@@ -6033,6 +6498,7 @@ window.__ModuleLoader__.load({
6033
6498
  turn: match.event.data.turn,
6034
6499
  assistantStartByStep: /* @__PURE__ */ new Map(),
6035
6500
  messageCountByStep: /* @__PURE__ */ new Map(),
6501
+ messageCount: 0,
6036
6502
  toolCallCount: 0,
6037
6503
  subagentCount: 0
6038
6504
  };
@@ -6046,25 +6512,32 @@ window.__ModuleLoader__.load({
6046
6512
  }
6047
6513
  return "immediate";
6048
6514
  },
6049
- buildLocationData: (context, scope) => {
6515
+ buildLocationData: (context, scope, previous) => {
6050
6516
  if (scope !== "turn") return null;
6051
6517
  const state = context.state ?? fallbackState(context);
6052
6518
  if (state === void 0) return null;
6053
6519
  const turn = turnLocation$1(context);
6054
6520
  if (turn === void 0) return null;
6521
+ const current = context.current.get("chat");
6522
+ const latestStep = turn.steps.at(-1);
6523
+ if (previous?.kind === "turn" && previous.key === "turn-process" && current?.kind === "turn-process" && current.data.answerAnchorSeq === null && current.data.controlAnchorSeq === state.controlAnchorSeq && current.data.messageCount === state.messageCount && current.data.toolCallCount === state.toolCallCount && current.data.subagentCount === state.subagentCount && turn.status !== "closed" && latestStep?.status !== "closed") return previous;
6055
6524
  const spec = processSpec(state, turn);
6056
- return spec === null ? null : {
6525
+ if (spec === null) return null;
6526
+ if (previous?.kind === "turn" && previous.turn === spec.turn && previous.key === "turn-process" && sameTurnProcessSpec(previous.value, spec)) return previous;
6527
+ return {
6057
6528
  kind: "turn",
6058
6529
  turn: turn.turn,
6059
6530
  key: "turn-process",
6060
- value: encodeTurnProcess(spec)
6531
+ value: spec
6061
6532
  };
6062
6533
  },
6063
6534
  buildViewNode: (context) => {
6064
6535
  const turn = turnLocation$1(context);
6065
- const signature = turn?.data.get("turn-process");
6066
- if (turn === void 0 || signature === void 0) return null;
6067
- const data = decodeTurnProcess(signature);
6536
+ const data = turn?.data.get("turn-process");
6537
+ if (turn === void 0 || data === void 0) return null;
6538
+ const current = context.current.get("chat");
6539
+ const state = context.state;
6540
+ if (current?.kind === "turn-process" && state !== void 0 && current.data.answerAnchorSeq === null && current.data.controlAnchorSeq === state.controlAnchorSeq && current.data.messageCount === state.messageCount && current.data.toolCallCount === state.toolCallCount && current.data.subagentCount === state.subagentCount && turn.status !== "closed" && turn.steps.at(-1)?.status !== "closed" && current.location === (context.start?.location ?? context.matches[0]?.location)) return current;
6068
6541
  return chatNode(context, "turn-process", data.controlAnchorSeq + CHAT_SYNTHETIC_SEQ_OFFSETS.processControl, data);
6069
6542
  }
6070
6543
  };
@@ -6354,7 +6827,7 @@ window.__ModuleLoader__.load({
6354
6827
  return data.finalNode !== void 0 && data.blocks.some((block) => block.kind === "text" && block.text.trim() !== "");
6355
6828
  }
6356
6829
  function tailData(context) {
6357
- const end = context.state?.end ?? context.matches.find((match) => match.event.type === "turn/end");
6830
+ const end = context.state === void 0 ? context.matches.find((match) => match.event.type === "turn/end") : context.state.end;
6358
6831
  if (end?.event.type !== "turn/end") return null;
6359
6832
  const turn = turnLocation(context);
6360
6833
  if (turn === void 0) return null;
@@ -6484,7 +6957,7 @@ window.__ModuleLoader__.load({
6484
6957
  }
6485
6958
  //#endregion
6486
6959
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/details/DetailsPanel.module.css.mjs
6487
- const css$1 = "._9bk3za_root{border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex-direction:column;min-width:0;height:100%;display:flex}._9bk3za_header{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:8px;padding:14px 12px 12px;display:flex}._9bk3za_title{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}._9bk3za_close{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;display:grid}._9bk3za_close:hover{background:var(--dsw-alias-interactive-bg-hover)}._9bk3za_body{flex:1;min-height:0;padding:12px 16px;overflow-y:auto}._9bk3za_empty{color:var(--dsw-alias-label-tertiary);padding:8px 0;font-size:13px;line-height:20px}._9bk3za_section{margin-bottom:16px}._9bk3za_sectionLabel{color:var(--dsw-alias-label-secondary);margin-bottom:6px;font-size:12px;font-weight:500;line-height:18px}._9bk3za_code{background:var(--dsw-alias-markdown-code-block);font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:12px;margin:0;padding:16px;font-size:13px;line-height:22px}._9bk3za_code[data-error]{color:var(--dsw-alias-state-error-primary)}";
6960
+ const css$1 = "._9bk3za_root{border-left:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex-direction:column;min-width:0;height:100%;display:flex}._9bk3za_header{border-bottom:.5px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:8px;padding:14px 12px 12px;display:flex}._9bk3za_title{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}._9bk3za_close{corner-shape:round;width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;display:grid}._9bk3za_close:hover{background:var(--dsw-alias-interactive-bg-hover)}._9bk3za_body{flex:1;min-height:0;padding:12px 16px;overflow-y:auto}._9bk3za_empty{color:var(--dsw-alias-label-tertiary);padding:8px 0;font-size:13px;line-height:20px}._9bk3za_section{margin-bottom:16px}._9bk3za_sectionLabel{color:var(--dsw-alias-label-secondary);margin-bottom:6px;font-size:12px;font-weight:500;line-height:18px}._9bk3za_code{background:var(--dsw-alias-markdown-code-block);font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:12px;margin:0;padding:16px;font-size:13px;line-height:22px}._9bk3za_code[data-error]{color:var(--dsw-alias-state-error-primary)}";
6488
6961
  const tagId$1 = "@prettier-ai/dsh-client-ui-chat/DetailsPanel.module.css";
6489
6962
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
6490
6963
  const tag = document.createElement("style");
@@ -6612,7 +7085,7 @@ window.__ModuleLoader__.load({
6612
7085
  }
6613
7086
  //#endregion
6614
7087
  //#region \0dsh-css:/home/runner/work/dsh-publisher/dsh-publisher/upstream/packages/client/ui-chat/src/client/settings/TranscriptViewRow.module.css.mjs
6615
- const css = ".EKfs7G_row{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.EKfs7G_rowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.EKfs7G_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.EKfs7G_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.EKfs7G_selector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.EKfs7G_selector:hover{background:var(--dsw-alias-interactive-bg-hover)}.EKfs7G_chevron{flex:none}";
7088
+ const css = ".EKfs7G_row{border-bottom:.5px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.EKfs7G_rowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.EKfs7G_title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.EKfs7G_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.EKfs7G_selector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.EKfs7G_selector:hover{background:var(--dsw-alias-interactive-bg-hover)}.EKfs7G_chevron{flex:none}";
6616
7089
  const tagId = "@prettier-ai/dsh-client-ui-chat/TranscriptViewRow.module.css";
6617
7090
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
6618
7091
  const tag = document.createElement("style");
@@ -7530,12 +8003,25 @@ window.__ModuleLoader__.load({
7530
8003
  }
7531
8004
  };
7532
8005
  //#endregion
8006
+ //#region lib/types/client/chat/use-turn-data.js
8007
+ const EMPTY_SOURCE = {
8008
+ getSnapshot: () => void 0,
8009
+ subscribe: () => () => {}
8010
+ };
8011
+ /**
8012
+ * Subscribe to one value from a Turn's keyed Location-data store.
8013
+ * @param data - current Turn data store, or absence for a Node outside a Turn.
8014
+ * @param key - declaration-merged business key.
8015
+ * @returns the current value for that key.
8016
+ */
8017
+ function useTurnDataValue(data, key) {
8018
+ const source = data?.source(key) ?? EMPTY_SOURCE;
8019
+ return (0, react.useSyncExternalStore)(source.subscribe, source.getSnapshot);
8020
+ }
8021
+ //#endregion
7533
8022
  //#region lib/types/client/apply.js
7534
- const CHAT_NODE_INJECT = { hooks: { turnData: ({ useChat }, nodeKey) => function useTurnData(key) {
7535
- return useChat((snapshot) => {
7536
- const location = snapshot.nodes.get(nodeKey)?.location;
7537
- return location?.kind === "turn" || location?.kind === "step" ? location.turn.data.get(key) : void 0;
7538
- });
8023
+ const CHAT_NODE_INJECT = { hooks: { turnData: (_standard, data) => function useTurnData(key) {
8024
+ return useTurnDataValue(data, key);
7539
8025
  } } };
7540
8026
  /** Services required by the Chat target and its presentation registrations. */
7541
8027
  const inject = [
@@ -7613,10 +8099,16 @@ window.__ModuleLoader__.load({
7613
8099
  },
7614
8100
  store: chatStore,
7615
8101
  inject: (sessionId, actions) => {
7616
- const session = ctx.sessions.binding(sessionId)?.session;
7617
- if (session === void 0) throw new Error(`ui-chat: unknown session "${sessionId}"`);
8102
+ const binding = ctx.sessions.binding(sessionId);
8103
+ if (binding === void 0) throw new Error(`ui-chat: unknown session "${sessionId}"`);
8104
+ const session = binding.session;
8105
+ const chat = chatSource(binding);
7618
8106
  return {
7619
8107
  hooks: { transcriptView: transcriptView.mode },
8108
+ keyedHooks: {
8109
+ chatNode: (key) => chat.getSnapshot().nodes.source(key),
8110
+ chatNodeProcess: (key) => chat.getSnapshot().nodes.processSource(key)
8111
+ },
7620
8112
  openDetails: (target) => {
7621
8113
  actions.select(target);
7622
8114
  ctx.layout.openDetails();
@@ -7630,6 +8122,7 @@ window.__ModuleLoader__.load({
7630
8122
  loadOlder: () => {
7631
8123
  session.loadOlder();
7632
8124
  },
8125
+ loadThrough: (seq) => session.loadThrough(seq),
7633
8126
  loadImage: Object.assign((attachment) => ctx.uiConversation.imageUrl(sessionId, attachment), { peek: (attachment) => ctx.uiConversation.peekImageUrl(sessionId, attachment) }),
7634
8127
  chatScroll: {
7635
8128
  save: (position) => {