dsh-turn-outline 0.2.6 → 0.2.7

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/README.en.md CHANGED
@@ -37,16 +37,16 @@
37
37
 
38
38
  ### Option 1: one-liner from npm (recommended)
39
39
 
40
- Install better-sidebar first (stable line pinned to 0.17.x), then this plugin:
40
+ Install better-sidebar first, then this plugin:
41
41
 
42
42
  ```sh
43
- npx @deepseek-ai/dsh plugin --profile desktop add dsh-better-sidebar@0.17.1
43
+ npx @deepseek-ai/dsh plugin --profile desktop add dsh-better-sidebar
44
44
  npx @deepseek-ai/dsh plugin --profile desktop add dsh-turn-outline
45
45
  ```
46
46
 
47
47
  > Using the Web profile? Swap `--profile desktop` for `--profile web`.
48
48
  >
49
- > ⚠️ **Version-line note**: this plugin is currently validated on **better-sidebar 0.17.x + DSH 0.1.1-rc.x (stable)**. The npm `latest` better-sidebar 0.18.0 supports only DSH 0.1.2-rc.1+ and has not been co-tested with this plugin — until a 0.18-adapted release, pin `dsh-better-sidebar@0.17.1` before upgrading your host.
49
+ > ℹ️ **Version-line note**: this plugin declares the peer range `dsh-better-sidebar@^0.17.0 || ^0.18.0`, and both lines are co-tested — **better-sidebar 0.18.0 + DSH 0.1.2-rc.1** (validated since 0.2.7) and **better-sidebar 0.17.1 + DSH 0.1.1-rc.x (stable)**. Note that better-sidebar 0.18.0 supports only DSH 0.1.2-rc.1+: on an older stable host (0.1.0-rc.8 ~ 0.1.1-rc.2), pin `dsh-better-sidebar@0.17.1`.
50
50
 
51
51
  ### Option 2: install from source (development / offline)
52
52
 
@@ -78,7 +78,7 @@ npx @deepseek-ai/dsh plugin --profile desktop remove dsh-turn-outline
78
78
 
79
79
  ```
80
80
  host (Node) inject: ['webServer','sessionQuery','sessions']
81
- POST /turntrail/api/events { sessionId, afterSeq }
81
+ POST /turn-outline/api/events { sessionId, afterSeq }
82
82
  → live sessions: borrow the frozen snapshot of the live store (O(1) probe + O(delta) projection, zero deep clones)
83
83
  → cold sessions: sessionQuery persisted log + lastSeq cache (LRU + byte cap)
84
84
  → paged responses (PAGE_SIZE events per page, hasMore semantics) + field trimming (no full tool-result payloads)
@@ -99,7 +99,7 @@ client (browser) inject: ['sessions','betterSidebar'] (betterSidebar is an optio
99
99
 
100
100
  ## Permissions & data
101
101
 
102
- - **Session logs**: one read-only host route `POST /turntrail/api/events`, browser same-origin + Host-header trust fence; read-only, never writes or mutates the session.
102
+ - **Session logs**: one read-only host route `POST /turn-outline/api/events`, browser same-origin + Host-header trust fence; read-only, never writes or mutates the session.
103
103
  - **Zero LLM calls**: nothing is sent to any model; no token consumption.
104
104
  - **No credential access**: no other network/file access besides session events; no user config, no persistent storage (tab state follows better-sidebar's layout persistence).
105
105
 
package/README.md CHANGED
@@ -37,16 +37,16 @@
37
37
 
38
38
  ### 方式一:npm 一行命令(推荐)
39
39
 
40
- 先装 better-sidebar(stable 线钉 0.17.x),再装本插件:
40
+ 先装 better-sidebar,再装本插件:
41
41
 
42
42
  ```sh
43
- npx @deepseek-ai/dsh plugin --profile desktop add dsh-better-sidebar@0.17.1
43
+ npx @deepseek-ai/dsh plugin --profile desktop add dsh-better-sidebar
44
44
  npx @deepseek-ai/dsh plugin --profile desktop add dsh-turn-outline
45
45
  ```
46
46
 
47
47
  > 使用 Web profile 时把 `--profile desktop` 换成 `--profile web`。
48
48
  >
49
- > ⚠️ **版本线说明**:本插件当前验证于 **better-sidebar 0.17.x + DSH 0.1.1-rc.x(stable)**。npm `latest` 的 better-sidebar 0.18.0 仅支持 DSH 0.1.2-rc.1+,尚未与本插件联调——在 0.18 适配版发布前,升级宿主请先固定 `dsh-better-sidebar@0.17.1`。
49
+ > ℹ️ **版本线说明**:本插件 peer 声明 `dsh-better-sidebar@^0.17.0 || ^0.18.0`,双线均已联调——**better-sidebar 0.18.0 + DSH 0.1.2-rc.1**(0.2.7 起验证)与 **better-sidebar 0.17.1 + DSH 0.1.1-rc.x(stable)**。注意 better-sidebar 0.18.0 仅支持 DSH 0.1.2-rc.1+:旧 stable 宿主(0.1.0-rc.8 ~ 0.1.1-rc.2)请固定安装 `dsh-better-sidebar@0.17.1`。
50
50
 
51
51
  ### 方式二:源码本地安装(开发 / 离线)
52
52
 
@@ -78,7 +78,7 @@ npx @deepseek-ai/dsh plugin --profile desktop remove dsh-turn-outline
78
78
 
79
79
  ```
80
80
  host(Node) inject: ['webServer','sessionQuery','sessions']
81
- POST /turntrail/api/events { sessionId, afterSeq }
81
+ POST /turn-outline/api/events { sessionId, afterSeq }
82
82
  → 活跃会话:直接借用 live store 冻结快照(O(1) 探测 + O(delta) 投影,零深拷贝)
83
83
  → 冷会话:sessionQuery 持久化日志 + lastSeq 缓存(LRU + 字节上限)
84
84
  → 响应逐页(每页 PAGE_SIZE 事件,hasMore 语义)+ 字段裁剪(不发 tool 结果全文)
@@ -99,7 +99,7 @@ client(浏览器) inject: ['sessions','betterSidebar'](betterSidebar 可
99
99
 
100
100
  ## 权限与数据
101
101
 
102
- - **会话日志**:host 只读路由 `POST /turntrail/api/events`,浏览器同源 + Host 头信任围栏;只读不回写、不改动会话。
102
+ - **会话日志**:host 只读路由 `POST /turn-outline/api/events`,浏览器同源 + Host 头信任围栏;只读不回写、不改动会话。
103
103
  - **零 LLM 调用**:不向任何模型发送内容,无 token 消耗。
104
104
  - **不读取凭据**:除会话事件外无其他网络 / 文件访问;无用户配置、无持久化存储(tab 状态随 better-sidebar 布局持久化)。
105
105
 
package/lib/client.js CHANGED
@@ -1107,7 +1107,7 @@ function t(lang, key) {
1107
1107
  var React = __toESM(require("react"), 1);
1108
1108
  var MAX_PULL_PAGES = 64;
1109
1109
  function fetchTrailEvents(sessionId, afterSeq, base) {
1110
- const url = new URL("/turntrail/api/events", base ?? window.location.origin);
1110
+ const url = new URL("/turn-outline/api/events", base ?? window.location.origin);
1111
1111
  return fetch(url.toString(), {
1112
1112
  method: "POST",
1113
1113
  headers: { "content-type": "application/json", accept: "application/json" },
package/lib/client.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/client/index.tsx", "../src/client/styles.ts", "../src/client/TrailTab.tsx", "../src/trail-model.ts", "../src/client/locales.ts", "../src/client/useTrail.ts", "../src/client/trail-jump.ts"],
4
- "sourcesContent": ["/**\n * Client half of dsh-turn-outline: register the \u300C\u8F6E\u6B21\u8F68\u8FF9\u300Dtab with\n * dsh-better-sidebar (optional peer \u2014 inert without it) and inject the\n * token-driven stylesheet once.\n *\n * Bundle purity: `dsh-better-sidebar` is never value-imported \u2014 the tab is\n * registered through `ctx.betterSidebar` at runtime; `react` resolves from\n * the client module system seed; the tab glyph is an inline SVG (no\n * ui-primitives dependency), so the panel works without it.\n */\nimport * as React from 'react'\nimport { trailCss } from './styles'\nimport { TrailTab } from './TrailTab'\n\n/**\n * Tab glyph \u2014 Lucide `route` (ISC license): two round endpoints joined by an\n * S-shaped detour, reading as the trail between turns. Drawn inline at 24\n * grid with stroke 2 so it scales cleanly to the 14px tab size; keeps the tab\n * visually distinct from the source-control branch icon it previously shared\n * (ui-primitives IconBranchOutline16). Chosen over a 4-node waypoints glyph\n * after a 14px density review: 3 elements render far less crowded in the\n * selected-tab state.\n */\nfunction routeIcon(size: number): React.ReactNode {\n const children = [\n React.createElement('circle', { key: 'a', cx: 6, cy: 19, r: 3 }),\n React.createElement('path', { key: 'p', d: 'M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15' }),\n React.createElement('circle', { key: 'b', cx: 18, cy: 5, r: 3 }),\n ]\n return React.createElement(\n 'svg',\n {\n viewBox: '0 0 24 24',\n width: size,\n height: size,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': true,\n },\n children,\n )\n}\n\nexport const inject = ['sessions', 'betterSidebar']\n\nexport function apply(ctx: {\n get(name: string): unknown\n effect<T>(fn: () => T): T\n}): void {\n const style = document.createElement('style')\n style.dataset.plugin = 'dsh-turn-outline'\n style.textContent = trailCss\n document.head.appendChild(style)\n // cordis ctx.effect(fn) RUNS fn to register and treats its RETURN VALUE as\n // the cleanup. A single arrow executes style.remove() immediately (the\n // injected <style> is deleted on the spot \u2192 styles never apply). Double\n // arrow returns the cleanup so the sheet survives (matches conversation-\n // outline, the working reference).\n ctx.effect(() => () => style.remove())\n\n const betterSidebar = ctx.get('betterSidebar')\n if (\n betterSidebar === undefined || betterSidebar === null\n || typeof (betterSidebar as { registerTab?: unknown }).registerTab !== 'function'\n ) {\n // No better-sidebar: stay inert (the plan's no-op path).\n return\n }\n\n ctx.effect(() => (betterSidebar as { registerTab(descriptor: unknown): () => void }).registerTab({\n id: 'turn-outline',\n title: () => (typeof navigator !== 'undefined' && navigator.language.toLowerCase().startsWith('zh')\n ? '\u8F6E\u6B21\u8F68\u8FF9'\n : 'Turn Outline'),\n icon: (size: number) => routeIcon(size),\n order: 46,\n single: true,\n component: (props: {\n ctx: { get(name: string): unknown }\n store?: unknown\n scope: { sessionId: string; cwd?: string }\n tab: { id: string }\n visible: boolean\n }) => React.createElement(TrailTab, {\n ctx: props.ctx,\n scope: props.scope,\n visible: props.visible === true,\n tab: props.tab,\n }),\n }))\n}\n", "/**\n * styles.ts \u2014 DSH-native, token-driven CSS for the trail panel, injected once\n * by the client entry. The design language mirrors the upstream DSH trajectory\n * surface: a monochrome \"ink\" chrome (label tiers + hairline separators +\n * interactive hover washes), flat turn rows instead of cards, a 2px turn rail\n * for the located turn, semantic accent dots (success/warn/neutral) instead of\n * a per-tool rainbow, and monospace tool names/args. Every expanded-body row\n * shares one anatomy \u2014 a 6px semantic dot + a narrow name column + content \u2014\n * so input, output and tool rows read as one grid; the dot carries the whole\n * color channel (category for tools, red for failures).\n * All colors/fonts consume\n * DSW alias tokens (auto light/dark/skin); fallbacks mirror the native dark\n * values so a missing token never falls back to a foreign look.\n *\n * Radius scale (native): 2px micro tag \u00B7 4px chip/step hover \u00B7 6px buttons \u00B7\n * 8px floating surfaces. No 999px pills. Class names are prefixed `ttl-` and\n * are OUR contract (never matched against DSH internals).\n */\nexport const trailCss = `\n.ttl-root {\n box-sizing: border-box;\n position: relative;\n height: 100%;\n min-width: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: var(--dsw-alias-bg-layer-1, #232324);\n color: var(--dsw-alias-label-primary, #f9fafb);\n font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"Helvetica Neue\", Helvetica, Arial, sans-serif);\n font-size: 12px;\n line-height: 18px;\n container: trail-tab / inline-size;\n}\n\n/* \u2500\u2500 Header (36px, hairline) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-header {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 8px;\n flex: none;\n height: 36px;\n padding: 0 8px 0 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));\n}\n.ttl-title {\n flex: none;\n min-width: 0;\n max-width: 55%;\n margin: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-primary, #f9fafb);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ttl-count {\n flex: none;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 12px;\n line-height: 18px;\n font-variant-numeric: tabular-nums;\n}\n.ttl-headerSpacer { flex: 1; min-width: 0; }\n/* Follow-latest: native ghost button + status dot (no checkbox pill). */\n.ttl-toggle {\n box-sizing: border-box;\n flex: none;\n height: 24px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 0 8px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n white-space: nowrap;\n}\n.ttl-toggle:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-label-primary, #f9fafb);\n}\n.ttl-toggleDot {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: transparent;\n box-shadow: inset 0 0 0 1px var(--dsw-alias-label-tertiary, #adb2b8);\n}\n.ttl-toggle[aria-pressed=\"true\"] { color: var(--dsw-alias-label-primary, #f9fafb); }\n.ttl-toggle[aria-pressed=\"true\"] .ttl-toggleDot {\n background: var(--dsw-alias-state-success-primary, #22c55e);\n box-shadow: none;\n}\n\n/* \u2500\u2500 Filter row (flat text chips + semantic dots) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-filterRow {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: 2px;\n flex: none;\n padding: 4px 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));\n overflow-x: auto;\n scrollbar-width: thin;\n scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;\n}\n.ttl-filterRow::-webkit-scrollbar { height: 4px; }\n.ttl-filterRow::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, #545557);\n border-radius: 2px;\n}\n.ttl-chip {\n box-sizing: border-box;\n flex: none;\n height: 22px;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 0 8px;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font: inherit;\n font-size: 12px;\n line-height: 16px;\n white-space: nowrap;\n cursor: pointer;\n}\n.ttl-chip:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-label-primary, #f9fafb);\n}\n/* Active chip = business-blue TEXT on the hover wash (native detailTab). */\n.ttl-chip[aria-pressed=\"true\"] {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-state-business-primary, #4176e6);\n}\n.ttl-chipNum {\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-variant-numeric: tabular-nums;\n font-weight: 400;\n}\n.ttl-chip[aria-pressed=\"true\"] .ttl-chipNum { color: inherit; opacity: 0.72; }\n.ttl-chipDot {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n}\n.ttl-dot-success { background: var(--dsw-alias-state-success-primary, #22c55e); }\n.ttl-dot-warn { background: var(--dsw-alias-state-warn-primary, #f59e0b); }\n.ttl-dot-neutral { background: var(--dsw-alias-label-tertiary, #adb2b8); }\n.ttl-moreChip { color: var(--dsw-alias-label-tertiary, #adb2b8); }\n.ttl-moreArrow {\n flex: none;\n display: inline-flex;\n transition: transform 120ms var(--ds-ease-in-out, ease);\n}\n.ttl-moreArrowOpen { transform: rotate(180deg); }\n/* \"More\" dropdown: native elevated menu (layer-2 + hairline + 8px + shadow). */\n.ttl-toolDropdown {\n box-sizing: border-box;\n position: absolute;\n z-index: 9;\n top: 64px;\n left: 8px;\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n width: max-content;\n max-width: calc(100% - 16px);\n max-height: 180px;\n overflow-y: auto;\n padding: 6px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2, #2c2c2e);\n box-shadow: 0 4px 16px var(--dsw-alias-bg-mask-1, rgba(0, 0, 0, 0.24));\n scrollbar-width: thin;\n scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;\n}\n\n/* \u2500\u2500 Turn list (flat rows, hairline separators) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 8px 8px;\n scrollbar-width: thin;\n scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;\n}\n.ttl-list::-webkit-scrollbar { width: 6px; }\n.ttl-list::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, #545557);\n border-radius: 3px;\n}\n.ttl-status {\n margin: 14px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 12px;\n line-height: 18px;\n}\n.ttl-error {\n margin: 14px;\n color: var(--dsw-alias-state-error-primary, #f25a5a);\n font-size: 12px;\n line-height: 18px;\n}\n\n/* \u2500\u2500 Turn row (no card; 2px rail marks the located turn) \u2500\u2500\u2500 */\n.ttl-turnRow {\n position: relative;\n border-top: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));\n background: transparent;\n}\n.ttl-turnRow:first-child { border-top: none; }\n.ttl-turnRow[data-active=\"true\"]::before {\n content: \"\";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 22%, var(--dsw-alias-bg-layer-1, #232324));\n}\n.ttl-turnRow[data-active=\"true\"][data-error=\"true\"]::before {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #f25a5a) 22%, var(--dsw-alias-bg-layer-1, #232324));\n}\n.ttl-turnHead {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 0;\n min-height: 32px;\n padding: 0 6px 0 10px;\n}\n.ttl-turnHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n}\n.ttl-turnToggle {\n box-sizing: border-box;\n min-width: 0;\n flex: 1;\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 4px 0;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.ttl-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n transition: transform 120ms var(--ds-ease-in-out, ease);\n}\n.ttl-chevronOpen { transform: rotate(90deg); }\n/* \"\u8F6E\u6B21 N\": native turnLabel-style micro tag (mono, module bg, 2px radius). */\n.ttl-turnTag {\n box-sizing: border-box;\n flex: none;\n padding: 1px 5px;\n border-radius: 2px;\n background: var(--dsw-alias-bg-module-platform, #353638);\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-family: var(--ds-font-family-code, \"SF Mono\", \"JetBrains Mono\", \"Fira Code\", Consolas, \"Liberation Mono\", Menlo, Courier, \"PingFang SC\", \"Microsoft YaHei\", monospace);\n font-size: 10px;\n font-weight: 500;\n line-height: 12px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n user-select: none;\n}\n.ttl-turnRow[data-active=\"true\"] .ttl-turnTag {\n color: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 55%, var(--dsw-alias-label-tertiary, #adb2b8));\n background: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 14%, var(--dsw-alias-bg-layer-1, #232324));\n}\n.ttl-turnPreview {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font-size: 12px;\n line-height: 18px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ttl-turnMeta {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-left: 8px;\n}\n/* Step count: a bare tabular number (never a colored badge). */\n.ttl-stepsNum {\n flex: none;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 11px;\n line-height: 16px;\n font-variant-numeric: tabular-nums;\n}\n.ttl-errorDot {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--dsw-alias-state-error-primary, #f25a5a);\n}\n.ttl-locateBtn {\n box-sizing: border-box;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n cursor: pointer;\n padding: 0;\n}\n.ttl-locateBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-label-primary, #f9fafb);\n}\n\n/* \u2500\u2500 Expanded body (transparent, indented under the rail) \u2500\u2500 */\n.ttl-turnBody {\n box-sizing: border-box;\n padding: 2px 6px 6px 34px;\n background: transparent;\n}\n/* \u2500\u2500 Unified row anatomy: [semantic dot] + [name] + [content] \u2500\u2500\n * One skeleton for every row. The 6px dot carries the whole color channel\n * (blue = user input, violet = assistant output, green/amber/neutral = tool\n * category matching the filter chips' dots, red = failure \u2014 replacing \u2717/\u2713),\n * the name names the row (monospace for tools), content follows. The name\n * column has a narrow 30px min-width: common short names (bash/edit/read,\n * \u8F93\u5165/\u8F93\u51FA) align without dead space; long names just push content. */\n.ttl-row {\n box-sizing: border-box;\n width: 100%;\n display: flex;\n align-items: flex-start;\n gap: 7px;\n padding: 3px 6px;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f9fafb);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n text-align: left;\n cursor: pointer;\n}\n.ttl-row:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n}\n.ttl-rowDot {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n margin-top: 6px;\n}\n.ttl-dot-blue {\n background: var(--dsw-alias-state-business-primary, #4176e6);\n}\n.ttl-dot-violet {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color, #5686fe) 60%, var(--dsw-alias-state-error-secondary, #f25a5a));\n}\n.ttl-dot-red {\n background: var(--dsw-alias-state-error-primary, #f25a5a);\n}\n.ttl-rowName {\n flex: none;\n min-width: 30px;\n max-width: 40%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n.ttl-rowNameMono {\n font-family: var(--ds-font-family-code, \"SF Mono\", \"JetBrains Mono\", \"Fira Code\", Consolas, \"Liberation Mono\", Menlo, Courier, \"PingFang SC\", \"Microsoft YaHei\", monospace);\n}\n.ttl-rowText {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-primary, #f9fafb);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3;\n line-clamp: 3;\n overflow: hidden;\n}\n.ttl-rowText2 {\n -webkit-line-clamp: 2;\n line-clamp: 2;\n}\n.ttl-rowTextMuted { color: var(--dsw-alias-label-tertiary, #adb2b8); }\n.ttl-rowArgs {\n flex: 1;\n min-width: 0;\n font-family: var(--ds-font-family-code, \"SF Mono\", \"JetBrains Mono\", \"Fira Code\", Consolas, \"Liberation Mono\", Menlo, Courier, \"PingFang SC\", \"Microsoft YaHei\", monospace);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n/* Error rows read red across dot and payload. */\n.ttl-row[data-step-kind=\"error\"] .ttl-rowArgs {\n color: var(--dsw-alias-state-error-primary, #f25a5a);\n}\n.ttl-emptyInline {\n padding: 6px 8px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 11px;\n line-height: 16px;\n}\n.ttl-showEarlier {\n box-sizing: border-box;\n width: 100%;\n height: 26px;\n margin: 2px 0 4px;\n padding: 0 8px;\n border: none;\n border-bottom: 1px dashed var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));\n border-radius: 0;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font: inherit;\n font-size: 11px;\n line-height: 16px;\n text-align: left;\n cursor: pointer;\n}\n.ttl-showEarlier:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n/* \u2500\u2500 Status bar / jump feedback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-statusBar {\n box-sizing: border-box;\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 30px;\n padding: 0 14px;\n border-top: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 12px;\n line-height: 18px;\n}\n.ttl-spin {\n display: inline-flex;\n color: var(--dsw-alias-state-business-primary, #4176e6);\n animation: ttl-spin 1s linear infinite;\n}\n@keyframes ttl-spin { to { transform: rotate(360deg); } }\n.ttl-jumpBanner {\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n display: flex;\n align-items: center;\n gap: 7px;\n max-width: 90%;\n padding: 5px 10px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));\n border-radius: 8px;\n background: var(--dsw-alias-button-floating-fill, #2c2c2e);\n color: var(--dsw-alias-label-primary, #f9fafb);\n font-size: 12px;\n font-weight: 400;\n line-height: 18px;\n white-space: nowrap;\n box-shadow: 0 4px 16px var(--dsw-alias-bg-mask-1, rgba(0, 0, 0, 0.24));\n}\n.ttl-toBottom {\n position: absolute;\n right: 12px;\n bottom: 12px;\n z-index: 5;\n box-sizing: border-box;\n height: 28px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));\n border-radius: 8px;\n background: var(--dsw-alias-button-floating-fill, #2c2c2e);\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n}\n.ttl-toBottom:hover {\n background: var(--dsw-alias-interactive-bg-hover-solid, #353638);\n color: var(--dsw-alias-label-primary, #f9fafb);\n}\n\n/* \u2500\u2500 Jump highlight flash (on the main conversation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-flash { animation: ttl-flash 1.6s ease-out; }\n@keyframes ttl-flash {\n 0% { box-shadow: 0 0 0 3px var(--dsw-alias-state-business-primary, #4176e6); }\n 100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ttl-spin { animation: none; }\n}\n`\n", "/**\n * TrailTab.tsx \u2014 the \u300C\u8F6E\u6B21\u8F68\u8FF9\u300Dpanel as a compact vertical turn timeline,\n * styled in the DSH-native design language (ink chrome, hairline separators,\n * flat rows \u2014 see styles.ts): a one-line header per turn (chevron + \"\u8F6E\u6B21 N\"\n * micro tag + input preview + bare tabular step count), an expanded flat list\n * of unified rows \u2014 [semantic dot] + [name] + [content] where the dot carries\n * the color channel (blue=input, violet=output, green/amber/neutral=tool\n * category, red=failure) \u2014 filter chips with matching category dots,\n * follow-latest auto-scroll, and click-to-locate (jump back into the main\n * conversation).\n *\n * Rendering cost model (v0.2.1): foldTurns ran over the WHOLE log and rebuilt\n * every TrailTurn object per poll, so every row re-rendered and index-based\n * keys + tail truncation remounted rows on each burst. Now:\n * - useTrail reuses closed TrailTurn objects (identity stable) \u2014 each poll\n * only the open turn's object changes;\n * - rows are keyed by event seq (stable), so appended steps reconcile in\n * place instead of shifting every key;\n * - each turn renders as a memoized TurnRow, so closed turns are skipped\n * entirely on the hot path.\n */\nimport * as React from 'react'\nimport type { TurnItem, TrailTurn, TrailStep, SessionEventLike } from '../trail-model'\nimport { stepsOf } from '../trail-model'\nimport { detectLang, t, type Lang } from './locales'\nimport { fetchTrailEvents, useTrail, type SessionsServiceLike } from './useTrail'\nimport { jumpToItem, type JumpTarget, type SessionFace } from './trail-jump'\n\nexport interface TrailTabProps {\n /** cordis client context (used only for ctx.get('sessions')). */\n ctx?: { get(name: string): unknown }\n scope: { sessionId: string; cwd?: string }\n visible: boolean\n tab: { id: string }\n /** test seam: override the events transport. */\n fetchEventsOverride?: (sessionId: string, afterSeq: number) => Promise<{ events: SessionEventLike[]; hasMore?: boolean }>\n}\n\ntype StepFilter = 'all' | 'failed' | string\n\nfunction sessionsOf(ctx: TrailTabProps['ctx']): SessionsServiceLike | undefined {\n if (ctx === undefined || typeof ctx.get !== 'function') return undefined\n const service = ctx.get('sessions')\n return service !== undefined && service !== null && typeof (service as { binding?: unknown }).binding === 'function'\n ? service as SessionsServiceLike\n : undefined\n}\n\nfunction turnByIdentity(turn: TrailTurn): string {\n return turn.turn !== null ? `turn:${turn.turn}` : `input:${turn.anchorSeq}`\n}\n\n/** Compact big counts: 1500 \u2192 \"1.5k\", 2200 \u2192 \"2.2k\", 384 \u2192 \"384\". */\nfunction formatCount(n: number): string {\n if (n < 1000) return String(n)\n const k = n / 1000\n return `${k >= 10 ? String(Math.round(k)) : String(Math.round(k * 10) / 10)}k`\n}\n\n/** Max step/items rows mounted per expanded turn (bounds the DOM). */\nconst STEP_CAP = 250\n\n/** Full tooltip text cap (server already trims to TEXT_CAP; belt and braces). */\nconst TITLE_CAP = 512\n\nfunction clipTitle(value: string): string {\n return value.length > TITLE_CAP ? `${value.slice(0, TITLE_CAP)}\u2026` : value\n}\n\n/** Semantic dot tone for a tool name (native-restrained: 3 tones + neutral). */\nfunction toolDotClass(name: string): 'success' | 'warn' | 'neutral' {\n const n = name.toLowerCase()\n if (n.includes('edit') || n.includes('write') || n.includes('patch')) return 'warn'\n if (n.includes('bash') || n.includes('command') || n.includes('exec') || n.includes('run')) return 'success'\n return 'neutral'\n}\n\n/** Stable per-item key: event seq (never shifts when new steps arrive). */\nfunction keyOfItem(item: TurnItem): string {\n return `seq:${item.kind === 'tool' ? item.step.seq : item.seq}`\n}\n\n/** The fold item a click targets (seq + kind + call id). */\nfunction targetFor(item: TurnItem): JumpTarget {\n if (item.kind === 'tool') {\n return {\n seq: item.step.seq,\n kind: item.step.kind === 'error' ? 'error' : 'tool',\n ...(item.step.callId !== null ? { callId: item.step.callId } : {}),\n }\n }\n return { seq: item.seq, kind: item.kind }\n}\n\ninterface TurnRowProps {\n turn: TrailTurn\n /** Position in the CURRENT visible list (drives default-latest semantics). */\n index: number\n expanded: boolean\n active: boolean\n showFull: boolean\n filter: StepFilter\n isToolFilter: boolean\n lang: Lang\n onToggle: (id: string) => void\n onShowEarlier: (id: string) => void\n onJump: (target: JumpTarget, turn: TrailTurn) => void\n}\n\nconst TurnRow = React.memo(function TurnRow(props: TurnRowProps): React.ReactElement {\n const { turn, index, expanded, active, showFull, filter, isToolFilter, lang, onToggle, onShowEarlier, onJump } = props\n const id = turnByIdentity(turn)\n\n const stepMatches = (step: TrailStep): boolean => {\n if (filter === 'all') return true\n if (filter === 'failed') return step.kind === 'error'\n return step.kind === 'tool' && step.name === filter\n }\n\n const items = turn.items.filter((item) => {\n if (item.kind === 'tool') return stepMatches(item.step)\n if (item.kind === 'input') return false\n return !isToolFilter\n })\n const turnTag = (t(lang, 'turnPrefix') as (n: number) => string)(turn.turn ?? index + 1)\n const preview = turn.inputPreview\n const hasError = turn.hasError\n /** The turn's user input; rendered as the first row of the expanded body. */\n const inputItem = turn.items.find((item): item is Extract<TurnItem, { kind: 'input' }> => item.kind === 'input')\n\n /** Unified row anatomy: [semantic dot] + [name] + [content]. */\n const dot = (tone: string): React.ReactElement => (\n <span className={`ttl-rowDot ttl-dot-${tone}`} aria-hidden=\"true\" />\n )\n const name = (label: string, mono: boolean): React.ReactElement => (\n <span className={`ttl-rowName${mono ? ' ttl-rowNameMono' : ''}`}>{label}</span>\n )\n\n /** First row of the expanded body: full input (3-line clamp), click = locate. */\n const renderInputRow = (item: Extract<TurnItem, { kind: 'input' }>): React.ReactElement => (\n <button\n type=\"button\"\n className=\"ttl-row\"\n data-testid=\"ttl-item-input\"\n title={clipTitle(item.text)}\n onClick={() => { onJump({ seq: item.seq, kind: 'input' }, turn) }}\n >\n {dot('blue')}\n {name(t(lang, 'input') as string, false)}\n <span className=\"ttl-rowText\">{item.text}</span>\n </button>\n )\n\n const renderStep = (step: TrailStep, key: string): React.ReactElement => {\n const isError = step.kind === 'error'\n const text = isError ? step.error : step.argsPreview\n const tone = isError ? 'red' : toolDotClass(step.name)\n return (\n <button\n type=\"button\"\n className=\"ttl-row\"\n key={key}\n data-testid=\"ttl-step\"\n data-step-kind={step.kind}\n title={text}\n onClick={() => { onJump(targetFor({ kind: 'tool', step }), turn) }}\n >\n {dot(tone)}\n {name(step.name, true)}\n <span className=\"ttl-rowArgs\">{text}</span>\n </button>\n )\n }\n\n const renderItem = (item: TurnItem, key: string): React.ReactElement | null => {\n if (item.kind === 'tool') return renderStep(item.step, key)\n if (item.kind === 'input') return null // shown as the body's first row\n if (item.kind === 'output') {\n return (\n <button\n type=\"button\"\n key={key}\n className=\"ttl-row\"\n data-testid=\"ttl-item-output\"\n title={clipTitle(item.text)}\n onClick={() => { onJump(targetFor(item), turn) }}\n >\n {dot('violet')}\n {name(t(lang, 'output') as string, false)}\n <span className=\"ttl-rowText ttl-rowText2\">{item.text}</span>\n </button>\n )\n }\n return (\n <button\n type=\"button\"\n key={key}\n className=\"ttl-row\"\n data-testid=\"ttl-item-compaction\"\n title={clipTitle(item.preview)}\n onClick={() => { onJump(targetFor(item), turn) }}\n >\n {dot('neutral')}\n {name(t(lang, 'compactionTag') as string, false)}\n <span className=\"ttl-rowText ttl-rowText2 ttl-rowTextMuted\">{item.preview}</span>\n </button>\n )\n }\n\n return (\n <section\n className=\"ttl-turnRow\"\n data-testid=\"ttl-turn\"\n data-collapsed={expanded ? 'false' : 'true'}\n data-active={active ? 'true' : undefined}\n data-error={hasError ? 'true' : undefined}\n >\n <div className=\"ttl-turnHead\">\n <button\n type=\"button\"\n className=\"ttl-turnToggle\"\n aria-expanded={expanded}\n onClick={() => { onToggle(id) }}\n >\n <span className={`ttl-chevron${expanded ? ' ttl-chevronOpen' : ''}`}>{chevronIcon()}</span>\n <span className=\"ttl-turnTag\" data-testid=\"ttl-turn-tag\">{turnTag}</span>\n {preview.length > 0\n ? <span className=\"ttl-turnPreview\" title={clipTitle(preview)}>{preview}</span>\n : null}\n </button>\n <span className=\"ttl-turnMeta\">\n {hasError ? <span className=\"ttl-errorDot\" data-testid=\"ttl-error-dot\" title={t(lang, 'error') as string} /> : null}\n <span\n className=\"ttl-stepsNum\"\n data-testid=\"ttl-steps-count\"\n title={t(lang, 'stepsLabel') as string}\n >{formatCount(turn.stepsCount)}</span>\n <button\n type=\"button\"\n className=\"ttl-locateBtn\"\n data-testid=\"ttl-locate\"\n title={t(lang, 'locate') as string}\n onClick={() => { onJump({ seq: turn.anchorSeq, kind: 'input' }, turn) }}\n >{targetIcon()}</button>\n </span>\n </div>\n {expanded\n ? (() => {\n // Bound the expanded body so a turn with thousands of steps\n // doesn't mount thousands of rows (and re-render them every\n // throttled update). Show the most recent STEP_CAP items plus a\n // \"show earlier\" affordance. Keys are seq-based, so appended\n // steps reconcile in place (rows keep their DOM nodes).\n const truncated = items.length > STEP_CAP && !showFull\n const shownItems = truncated ? items.slice(items.length - STEP_CAP) : items\n const hiddenCount = items.length - shownItems.length\n return (\n <div className=\"ttl-turnBody\">\n {filter === 'all' && inputItem !== undefined ? renderInputRow(inputItem) : null}\n {hiddenCount > 0\n ? <button\n type=\"button\"\n className=\"ttl-showEarlier\"\n data-testid=\"ttl-show-earlier\"\n onClick={() => { onShowEarlier(id) }}\n >{(t(lang, 'showEarlierSteps') as (n: number) => string)(hiddenCount)}</button>\n : null}\n {items.length === 0\n ? <div className=\"ttl-emptyInline\">{t(lang, 'emptyFiltered') as string}</div>\n : shownItems.map((item) => renderItem(item, keyOfItem(item)))}\n </div>\n )\n })()\n : null}\n </section>\n )\n})\n\nexport function TrailTab(props: TrailTabProps): React.ReactElement | null {\n const lang: Lang = detectLang()\n const sessionId = props.scope?.sessionId ?? ''\n const visible = props.visible === true\n const sessionsService = React.useMemo(() => sessionsOf(props.ctx), [props.ctx])\n const fetchEvents = React.useMemo(\n () => props.fetchEventsOverride !== undefined\n ? props.fetchEventsOverride\n : (sid: string, afterSeq: number) => fetchTrailEvents(sid, afterSeq),\n [props.fetchEventsOverride],\n )\n\n const { turns, stats, status, error, refresh } = useTrail(sessionId, visible, fetchEvents, sessionsService)\n\n const [filter, setFilter] = React.useState<StepFilter>('all')\n const [expandedTurns, setExpandedTurns] = React.useState<Set<string> | null>(null)\n const [jumping, setJumping] = React.useState<number | null>(null)\n const [jumpPages, setJumpPages] = React.useState(0)\n const [jumpMessage, setJumpMessage] = React.useState<string | null>(null)\n const [activeTurnId, setActiveTurnId] = React.useState<string | null>(null)\n const [followLatest, setFollowLatest] = React.useState(true)\n const [followPaused, setFollowPaused] = React.useState(false)\n const [moreToolsOpen, setMoreToolsOpen] = React.useState(false)\n const [showFullTurn, setShowFullTurn] = React.useState<Set<string>>(new Set())\n const listRef = React.useRef<HTMLDivElement | null>(null)\n const jumpingRef = React.useRef(false)\n\n // \u2500\u2500 Follow-latest auto-scroll (rAF-coalesced; no forced reflow per burst) \u2500\n const onScroll = React.useCallback((): void => {\n const el = listRef.current\n if (el === null) return\n const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 32\n setFollowPaused(!nearBottom)\n }, [])\n\n React.useEffect(() => {\n if (!followLatest || followPaused) return undefined\n const el = listRef.current\n if (el === null) return undefined\n if (typeof window.requestAnimationFrame === 'function') {\n const raf = window.requestAnimationFrame(() => { el.scrollTop = el.scrollHeight })\n return () => window.cancelAnimationFrame(raf)\n }\n const timeout = window.setTimeout(() => { el.scrollTop = el.scrollHeight }, 0)\n return () => window.clearTimeout(timeout)\n }, [turns, followLatest, followPaused])\n\n const scrollToBottom = React.useCallback((): void => {\n const el = listRef.current\n if (el === null) return\n el.scrollTop = el.scrollHeight\n setFollowPaused(false)\n }, [])\n\n // \u2500\u2500 Expansion helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const latestIdRef = React.useRef('')\n React.useEffect(() => {\n latestIdRef.current = turns.length > 0 ? turnByIdentity(turns[turns.length - 1]) : ''\n }, [turns])\n\n const isTurnExpanded = React.useCallback((id: string, index: number): boolean => {\n if (expandedTurns === null) return index === turns.length - 1\n return expandedTurns.has(id)\n }, [expandedTurns, turns.length])\n\n const toggleTurn = React.useCallback((id: string): void => {\n setExpandedTurns((current) => {\n if (current === null) {\n // Default state = only the latest turn expanded. Materialize THAT\n // single set, then toggle just the clicked turn \u2014 never \"expand\n // every other turn\" (that inverted the default on first click).\n const latestId = latestIdRef.current\n const next = new Set<string>()\n if (latestId.length > 0) next.add(latestId)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n }\n const next = new Set(current)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }, [])\n\n const toggleShowFull = React.useCallback((id: string): void => {\n setShowFullTurn((current) => {\n const next = new Set(current)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }, [])\n\n // \u2500\u2500 Locate (jump) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const sessionForJump = React.useMemo<SessionFace | null>(() => {\n if (sessionsService === undefined) return null\n const binding = sessionsService.binding(sessionId)\n const session = binding !== undefined && binding.session !== undefined ? binding.session : undefined\n if (session === undefined) return null\n const face = session as unknown as { loadOlder?: unknown; getSnapshot?: unknown }\n if (typeof face.loadOlder !== 'function' || typeof face.getSnapshot !== 'function') return null\n return session as unknown as SessionFace\n }, [sessionsService, sessionId])\n\n const doJump = React.useCallback(async (target: JumpTarget, turn: TrailTurn): Promise<void> => {\n if (jumpingRef.current) return // ignore re-entry while a jump is in flight\n setActiveTurnId(turnByIdentity(turn))\n if (sessionForJump === null) {\n setJumpMessage(t(lang, 'jumpFailed') as string)\n return\n }\n jumpingRef.current = true\n setJumping(target.seq)\n setJumpPages(0)\n setJumpMessage(null)\n const outcome = await jumpToItem(sessionForJump, target, {\n document: window.document,\n onProgress: (pages) => setJumpPages(pages),\n })\n jumpingRef.current = false\n setJumping(null)\n if (outcome.ok) {\n setJumpMessage(t(lang, outcome.mode === 'trajectory' ? 'jumpTrajectory' : 'jumpDone') as string)\n } else {\n setJumpMessage(t(lang, 'jumpFailed') as string)\n }\n }, [sessionForJump, lang])\n\n // \u2500\u2500 Filtered rendering helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const stepMatches = (step: TrailStep): boolean => {\n if (filter === 'all') return true\n if (filter === 'failed') return step.kind === 'error'\n return step.kind === 'tool' && step.name === filter\n }\n\n const visibleTurns = React.useMemo(\n () => (filter === 'all' || filter === 'failed' ? turns : turns.filter((turn) => stepsOf(turn).some((step) => stepMatches(step)))),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [turns, filter],\n )\n\n const toolChips = stats.tools.slice(0, 4)\n const hasMoreTools = stats.tools.length > 4\n const isToolFilter = filter !== 'all' && filter !== 'failed'\n\n if (!visible) return null\n\n // \u2500\u2500 Render pieces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const chipButton = (value: StepFilter, label: string, count: number, dot?: 'success' | 'warn' | 'neutral'): React.ReactElement => (\n <button\n type=\"button\"\n key={value}\n className=\"ttl-chip\"\n aria-pressed={filter === value ? 'true' : 'false'}\n data-testid={`ttl-chip-${value}`}\n onClick={() => { setFilter((current) => (current === value ? 'all' : value)) }}\n >\n {dot ? <span className={`ttl-chipDot ttl-dot-${dot}`} aria-hidden=\"true\" /> : null}\n <span>{label}</span>\n {count > 0 ? <span className=\"ttl-chipNum\">{String(count)}</span> : null}\n </button>\n )\n\n const header = (\n <div className=\"ttl-header\">\n <h2 className=\"ttl-title\">{t(lang, 'tabTitle') as string}</h2>\n <span className=\"ttl-count\" data-testid=\"ttl-count\">{(t(lang, 'turnsCount') as (n: number) => string)(turns.length)}</span>\n <span className=\"ttl-headerSpacer\" />\n <button\n type=\"button\"\n className=\"ttl-toggle\"\n aria-pressed={followLatest ? 'true' : 'false'}\n data-testid=\"ttl-follow-toggle\"\n onClick={() => { setFollowLatest((current) => !current) }}\n >\n <span className=\"ttl-toggleDot\" aria-hidden=\"true\" />\n {t(lang, 'followLatest') as string}\n </button>\n </div>\n )\n\n const chips = (\n <>\n <div className=\"ttl-filterRow\" role=\"group\" data-testid=\"ttl-chips\">\n {chipButton('all', t(lang, 'filterAll') as string, stats.total)}\n {stats.failed > 0 ? chipButton('failed', t(lang, 'filterFailed') as string, stats.failed) : null}\n {toolChips.map((tool) => chipButton(tool.name, tool.name, tool.count, toolDotClass(tool.name)))}\n {hasMoreTools\n ? <button\n type=\"button\"\n key=\"more\"\n className=\"ttl-chip ttl-moreChip\"\n aria-expanded={moreToolsOpen ? 'true' : 'false'}\n data-testid=\"ttl-chip-more\"\n onClick={() => { setMoreToolsOpen((current) => !current) }}\n >\n {t(lang, moreToolsOpen ? 'filterCollapse' : 'filterMore') as string}\n <span className={`ttl-moreArrow${moreToolsOpen ? ' ttl-moreArrowOpen' : ''}`}>{chevronIcon()}</span>\n </button>\n : null}\n </div>\n {moreToolsOpen && hasMoreTools\n ? <div className=\"ttl-toolDropdown\" data-testid=\"ttl-tool-dropdown\">\n {stats.tools.map((tool) => chipButton(tool.name, tool.name, tool.count, toolDotClass(tool.name)))}\n </div>\n : null}\n </>\n )\n\n let body: React.ReactElement\n if (status === 'loading') {\n body = <div className=\"ttl-status\" data-testid=\"ttl-status\">{t(lang, 'loading') as string}</div>\n } else if (status === 'error' && turns.length === 0) {\n body = (\n <div>\n <div className=\"ttl-error\" data-testid=\"ttl-error\">{error ?? t(lang, 'loadFailed') as string}</div>\n <div className=\"ttl-filterRow\">\n <button type=\"button\" className=\"ttl-chip\" onClick={refresh} data-testid=\"ttl-retry\">{t(lang, 'retry') as string}</button>\n </div>\n </div>\n )\n } else if (turns.length === 0) {\n body = <div className=\"ttl-status\" data-testid=\"ttl-empty\">{t(lang, 'emptyNoTurns') as string}</div>\n } else if (visibleTurns.length === 0) {\n body = (\n <div className=\"ttl-status\" data-testid=\"ttl-empty-filtered\">\n {t(lang, 'emptyFiltered') as string}\n <button type=\"button\" className=\"ttl-chip\" style={{ marginLeft: 8 }} onClick={() => { setFilter('all') }}>{t(lang, 'clearFilter') as string}</button>\n </div>\n )\n } else {\n body = (\n <div className=\"ttl-list\" ref={listRef} onScroll={onScroll} data-testid=\"ttl-list\">\n {visibleTurns.map((turn, index) => (\n <TurnRow\n key={turnByIdentity(turn)}\n turn={turn}\n index={index}\n expanded={isTurnExpanded(turnByIdentity(turn), index)}\n active={activeTurnId === turnByIdentity(turn)}\n showFull={showFullTurn.has(turnByIdentity(turn))}\n filter={filter}\n isToolFilter={isToolFilter}\n lang={lang}\n onToggle={toggleTurn}\n onShowEarlier={toggleShowFull}\n onJump={doJump}\n />\n ))}\n </div>\n )\n }\n\n const statusBar = jumping !== null || jumpMessage !== null\n ? (\n <div className=\"ttl-statusBar\" data-testid=\"ttl-statusbar\">\n {jumping !== null ? <span className=\"ttl-spin\">{spinnerIcon()}</span> : null}\n <span>\n {jumping !== null ? `${(t(lang, 'jumpProgress') as (n: number) => string)(jumpPages)} ` : ''}\n {jumpMessage ?? ''}\n </span>\n </div>\n )\n : null\n\n return (\n <div className=\"ttl-root\" role=\"complementary\" aria-label={t(lang, 'tabTitle') as string} data-testid=\"ttl-root\">\n {header}\n {chips}\n {body}\n {statusBar}\n {jumping !== null\n ? <div className=\"ttl-jumpBanner\" data-testid=\"ttl-jump-banner\" role=\"status\">\n <span className=\"ttl-spin\">{spinnerIcon()}</span>\n {(t(lang, 'jumpProgress') as (n: number) => string)(jumpPages)}\n </div>\n : null}\n {followPaused && turns.length > 0\n ? <button type=\"button\" className=\"ttl-toBottom\" data-testid=\"ttl-to-bottom\" onClick={scrollToBottom}>{t(lang, 'newUpdates') as string}</button>\n : null}\n </div>\n )\n}\n\nfunction chevronIcon(): React.ReactElement {\n return (\n <svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path d=\"M6 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\nfunction spinnerIcon(): React.ReactElement {\n return (\n <svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path d=\"M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2.5v3h-3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\nfunction targetIcon(): React.ReactElement {\n return (\n <svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <circle cx=\"8\" cy=\"8\" r=\"5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <circle cx=\"8\" cy=\"8\" r=\"1.6\" fill=\"currentColor\" />\n </svg>\n )\n}\n", "/**\n * trail-model.ts \u2014 pure event \u2192 turn folding for dsh-turn-outline.\n *\n * Framework-agnostic (no React / DOM / Node): a single foldTurns() maps a\n * session event stream onto per-user-turn summaries:\n *\n * turn boundary = `turn/start .. turn/end` (the `data.turn` number is the\n * turn identity \u2014 stable under parallel steps / steering).\n * input = first `user/message` with source.kind === 'user'.\n * context rows = `steering` / `context` user messages (kept as grey rows).\n * tool steps = `tool/call` (+ `tool/result` error rows).\n * tool groups = consecutive tool/call steps (>=2) merged, broken by an\n * assistant output, an error, a compaction, a user\n * message or a turn end.\n * output = `assistant/message` text (last text block wins).\n * compaction = `compaction/summary` shown as a checkpoint row.\n * command/run = ignored (mirrors the trajectory view).\n *\n * Fallback mode: a stream with NO turn/start events folds on\n * `user/message` boundaries, so the panel still works for odd logs.\n *\n * Field asymmetry (verified against DSH 0.1.1-rc.2):\n * user/message text lives in data.content (block array or bare string),\n * assistant/message text lives in data.message.content.\n */\n\nexport interface SessionEventLike {\n type: string\n seq: number\n time?: number\n data?: Record<string, unknown> | null\n}\n\nexport type TurnItem =\n | { kind: 'input'; seq: number; text: string; preview: string }\n | { kind: 'tool'; step: TrailStep }\n | { kind: 'output'; seq: number; text: string; preview: string }\n | { kind: 'compaction'; seq: number; preview: string }\n\nexport interface TrailStep {\n kind: 'tool' | 'error'\n seq: number\n name: string\n argsPreview: string\n callId: string | null\n error: string\n}\n\nexport interface TrailTurn {\n index: number\n turn: number | null\n /** The seq a jump targets: the user input, else the first item. */\n anchorSeq: number\n inputPreview: string\n outputPreview: string\n status: 'pending' | 'running' | 'done' | 'error'\n startedAt?: number\n endedAt?: number\n items: TurnItem[]\n stepsCount: number\n /** Number of injected system-reminders / steering for this turn (not rendered). */\n contextCount: number\n hasError: boolean\n}\n\n/** Aggregated step statistics (kept incrementally, not re-derived per pull). */\nexport interface TrailStats {\n total: number\n failed: number\n tools: Array<{ name: string; count: number }>\n}\n\n/** Preview length for one-line summaries. */\nexport const PREVIEW_MAX = 120\n\n/** Flatten every tool/error step of a turn. */\nexport function stepsOf(turn: TrailTurn): TrailStep[] {\n const out: TrailStep[] = []\n for (const item of turn.items) {\n if (item.kind === 'tool') out.push(item.step)\n }\n return out\n}\n\nexport function compact(value: string, max = PREVIEW_MAX): string {\n const oneLine = value.replace(/\\s+/g, ' ').trim()\n if (oneLine.length <= max) return oneLine\n return `${oneLine.slice(0, max - 1).trimEnd()}\u2026`\n}\n\n/** Extract text from a content block array (first non-empty text block). */\nexport function contentPreview(blocks: unknown): string {\n if (typeof blocks === 'string') return compact(blocks)\n if (!Array.isArray(blocks)) return ''\n for (const block of blocks) {\n if (\n block !== null && typeof block === 'object'\n && (block as { type?: unknown }).type === 'text'\n && typeof (block as { text?: unknown }).text === 'string'\n && (block as { text: string }).text.trim().length > 0\n ) {\n return compact((block as { text: string }).text)\n }\n }\n return ''\n}\n\n/** Last text block of an assistant message content array. */\nexport function lastTextBlock(blocks: unknown): string {\n if (typeof blocks === 'string') return blocks\n if (!Array.isArray(blocks)) return ''\n let last = ''\n for (const block of blocks) {\n if (\n block !== null && typeof block === 'object'\n && (block as { type?: unknown }).type === 'text'\n && typeof (block as { text?: unknown }).text === 'string'\n && (block as { text: string }).text.trim().length > 0\n ) {\n last = (block as { text: string }).text\n }\n }\n return last\n}\n\nfunction objectData(event: SessionEventLike): Record<string, unknown> {\n return event.data !== null && typeof event.data === 'object' ? event.data : {}\n}\n\n/** source.kind: 'user' | 'steering' | 'context' | 'plugin' | ... */\nfunction sourceKindOf(event: SessionEventLike, data: Record<string, unknown>): string | undefined {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const source = message !== null && typeof (message as { source?: unknown }).source === 'object'\n ? (message as { source: { kind?: unknown } }).source\n : (data.source !== null && typeof data.source === 'object'\n ? (data.source as { kind?: unknown })\n : null)\n if (source !== null && typeof source.kind === 'string') return source.kind\n return undefined\n}\n\nexport function toolCallIdOf(data: Record<string, unknown>): string | null {\n if (typeof data.callId === 'string' && data.callId.length > 0) return data.callId\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const source = message !== null && typeof (message as { source?: unknown }).source === 'object'\n ? (message as { source: { callId?: unknown } }).source\n : null\n if (source !== null && typeof source.callId === 'string' && source.callId.length > 0) {\n return source.callId\n }\n return null\n}\n\ninterface Builder {\n turn: number | null\n startSeq: number | null\n endSeq: number | null\n input: string\n inputSeq: number | null\n output: string\n outputSeq: number | null\n items: TurnItem[]\n steps: TrailStep[]\n contexts: Array<{ seq: number; text: string; preview: string }>\n hasError: boolean\n startedAt?: number\n endedAt?: number\n /** Turn-mode order position (set by TrailFolder); tracks frozen slots. */\n position?: number\n}\n\nfunction newBuilder(turn: number | null): Builder {\n return {\n turn,\n startSeq: null,\n endSeq: null,\n input: '',\n inputSeq: null,\n output: '',\n outputSeq: null,\n items: [],\n steps: [],\n contexts: [],\n hasError: false,\n }\n}\n\nfunction appendItem(builder: Builder, item: TurnItem): void {\n builder.items.push(item)\n if (item.kind === 'tool') {\n builder.steps.push(item.step)\n if (item.step.kind === 'error') builder.hasError = true\n }\n}\n\nfunction finishBuilder(builder: Builder, index: number, boundarySeq: number): TrailTurn {\n const anchorSeq = builder.inputSeq !== null ? builder.inputSeq : boundarySeq\n const status: TrailTurn['status'] = builder.hasError\n ? 'error'\n : builder.endSeq === null\n ? (builder.items.length > 0 ? 'running' : 'pending')\n : 'done'\n return {\n index,\n turn: builder.turn,\n anchorSeq,\n inputPreview: compact(builder.input),\n outputPreview: compact(builder.output),\n status,\n ...(builder.startedAt !== undefined ? { startedAt: builder.startedAt } : {}),\n ...(builder.endedAt !== undefined ? { endedAt: builder.endedAt } : {}),\n items: builder.items,\n stepsCount: builder.steps.length,\n contextCount: builder.contexts.length,\n hasError: builder.hasError,\n }\n}\n\n/** First seq-bearing entry of an item list (used as a fallback boundary). */\nfunction firstItemSeq(items: TurnItem[]): number {\n for (const item of items) {\n if (item.kind === 'tool') return item.step.seq\n return item.seq\n }\n return 0\n}\n\n/**\n * Fold an event stream onto TrailTurn[].\n * @param events - session events ordered by seq.\n * @returns the folded turns in stream order.\n */\nexport function foldTurns(events: readonly SessionEventLike[]): TrailTurn[] {\n const out: TrailTurn[] = []\n if (!Array.isArray(events) || events.length === 0) return out\n\n const hasTurnEvents = events.some((event) => (\n event !== null && typeof event === 'object'\n && typeof (event as { type?: unknown }).type === 'string'\n && (event as { type: string }).type === 'turn/start'\n ))\n if (!hasTurnEvents) return foldByUserBoundaries(events)\n\n const index = new Map<number, Builder>()\n const order: number[] = []\n const pendingUser: Array<{ seq: number; text: string }> = []\n let current: Builder | null = null\n\n const builderOf = (turn: number): Builder => {\n let builder = index.get(turn)\n if (builder === undefined) {\n builder = newBuilder(turn)\n index.set(turn, builder)\n order.push(turn)\n }\n return builder\n }\n\n for (const event of events) {\n if (event === null || typeof event !== 'object' || typeof event.type !== 'string') continue\n const data = objectData(event)\n const seq = typeof event.seq === 'number' ? event.seq : 0\n const time = typeof event.time === 'number' ? event.time : undefined\n\n switch (event.type) {\n case 'turn/start': {\n const raw = data.turn\n if (typeof raw === 'number') {\n const builder = builderOf(raw)\n if (builder.startSeq === null) builder.startSeq = seq\n if (builder.startedAt === undefined) builder.startedAt = time\n for (const pending of pendingUser.splice(0)) {\n builder.input = pending.text\n builder.inputSeq = pending.seq\n builder.items.unshift({ kind: 'input', seq: pending.seq, text: pending.text, preview: compact(pending.text) })\n }\n current = builder\n }\n break\n }\n case 'turn/end': {\n const raw = data.turn\n const target = typeof raw === 'number' ? builderOf(raw) : current\n if (target !== null) {\n target.endSeq = seq\n target.endedAt = time\n current = null\n }\n break\n }\n case 'user/message': {\n const preview = contentPreview(data.content)\n if (preview.length === 0) break\n const kind = sourceKindOf(event, data)\n const text = typeof data.content === 'string'\n ? data.content\n : (typeof data.message === 'object' && data.message !== null\n && typeof (data.message as { content?: unknown }).content === 'string'\n ? (data.message as { content: string }).content\n : preview)\n if (kind === 'user') {\n if (current === null) {\n pendingUser.push({ seq, text })\n } else {\n current.input = text\n current.inputSeq = seq\n appendItem(current, { kind: 'input', seq, text, preview })\n }\n } else if (current !== null) {\n current.contexts.push({ seq, text, preview })\n }\n break\n }\n case 'assistant/message': {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const content = typeof (message as { content?: unknown }).content !== 'undefined'\n ? (message as { content: unknown }).content\n : data.content\n const text = lastTextBlock(content)\n if (text.length === 0) break\n if (current !== null) {\n current.output = text\n current.outputSeq = seq\n appendItem(current, { kind: 'output', seq, text, preview: compact(text) })\n }\n break\n }\n case 'tool/call': {\n if (current === null) break\n const name = typeof data.name === 'string' && data.name.length > 0 ? data.name : 'tool'\n const args = typeof data.arguments === 'string' && data.arguments.length > 0 ? data.arguments : ''\n const step: TrailStep = {\n kind: 'tool',\n seq,\n name,\n argsPreview: compact(args),\n callId: toolCallIdOf(data),\n error: '',\n }\n appendItem(current, { kind: 'tool', step })\n break\n }\n case 'tool/result': {\n const error = (data.error !== null && typeof data.error === 'object' ? data.error : null) as {\n name?: unknown\n code?: unknown\n } | null\n if (error === null) break\n if (current === null) break\n const name = typeof error.name === 'string' && error.name.length > 0 ? error.name : 'tool'\n const detail = typeof error.code === 'string' && error.code.length > 0 ? ` \u00B7 ${error.code}` : ''\n const step: TrailStep = {\n kind: 'error',\n seq,\n name,\n argsPreview: '',\n callId: toolCallIdOf(data),\n error: compact(`${name}${detail}`),\n }\n appendItem(current, { kind: 'tool', step })\n break\n }\n case 'compaction/summary': {\n const preview = contentPreview(data.summary)\n if (preview.length === 0) break\n if (current !== null) appendItem(current, { kind: 'compaction', seq, preview })\n break\n }\n default:\n // command/run and anything else: ignored.\n break\n }\n }\n\n let counter = 0\n for (const turn of order) {\n const builder = index.get(turn)\n if (builder === undefined) continue\n const boundarySeq = builder.startSeq !== null ? builder.startSeq : (builder.inputSeq ?? firstItemSeq(builder.items))\n out.push(finishBuilder(builder, counter, boundarySeq))\n counter += 1\n }\n // Trailing user messages that never found a turn.\n for (const pending of pendingUser) {\n const builder = newBuilder(null)\n builder.input = pending.text\n builder.inputSeq = pending.seq\n appendItem(builder, { kind: 'input', seq: pending.seq, text: pending.text, preview: compact(pending.text) })\n out.push(finishBuilder(builder, counter, pending.seq))\n counter += 1\n }\n return out\n}\n\n/** Fallback: fold on user/message boundaries when no turn/start exists. */\nexport function foldByUserBoundaries(events: readonly SessionEventLike[]): TrailTurn[] {\n const out: TrailTurn[] = []\n let builder: Builder | null = null\n let counter = 0\n\n const close = (): void => {\n if (builder === null) return\n const boundarySeq = builder.inputSeq !== null ? builder.inputSeq : builder.startSeq ?? 0\n out.push(finishBuilder(builder, counter, boundarySeq))\n counter += 1\n builder = null\n }\n\n for (const event of events) {\n if (event === null || typeof event !== 'object' || typeof event.type !== 'string') continue\n const data = objectData(event)\n const seq = typeof event.seq === 'number' ? event.seq : 0\n const time = typeof event.time === 'number' ? event.time : undefined\n\n if (event.type === 'user/message') {\n const kind = sourceKindOf(event, data)\n if (kind !== 'user') continue\n close()\n const text = typeof data.content === 'string' ? data.content : contentPreview(data.content)\n if (text.length === 0) continue\n builder = newBuilder(null)\n builder.startSeq = seq\n builder.startedAt = time\n builder.input = text\n builder.inputSeq = seq\n appendItem(builder, { kind: 'input', seq, text, preview: compact(text) })\n continue\n }\n if (builder === null) continue\n if (event.type === 'assistant/message') {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const text = lastTextBlock((message as { content?: unknown }).content ?? data.content)\n if (text.length === 0) continue\n builder.output = text\n builder.outputSeq = seq\n appendItem(builder, { kind: 'output', seq, text, preview: compact(text) })\n continue\n }\n if (event.type === 'tool/call') {\n const name = typeof data.name === 'string' && data.name.length > 0 ? data.name : 'tool'\n const args = typeof data.arguments === 'string' && data.arguments.length > 0 ? data.arguments : ''\n const step: TrailStep = {\n kind: 'tool',\n seq,\n name,\n argsPreview: compact(args),\n callId: toolCallIdOf(data),\n error: '',\n }\n // appendItem records the step once (in items AND steps); a redundant\n // direct steps.push here would double-count stepsCount.\n appendItem(builder, { kind: 'tool', step })\n continue\n }\n if (event.type === 'tool/result') {\n const error = (data.error !== null && typeof data.error === 'object' ? data.error : null) as {\n name?: unknown\n code?: unknown\n } | null\n if (error === null) continue\n const name = typeof error.name === 'string' && error.name.length > 0 ? error.name : 'tool'\n const detail = typeof error.code === 'string' && error.code.length > 0 ? ` \u00B7 ${error.code}` : ''\n const step: TrailStep = {\n kind: 'error',\n seq,\n name,\n argsPreview: '',\n callId: toolCallIdOf(data),\n error: compact(`${name}${detail}`),\n }\n appendItem(builder, { kind: 'tool', step })\n continue\n }\n }\n close()\n return out\n}\n\n// \u2500\u2500 Incremental folding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Materialize the open user-mode turn (fallback folding) at its list index. */\nfunction finishUserTurn(builder: Builder, index: number): TrailTurn {\n const boundarySeq = builder.inputSeq !== null ? builder.inputSeq : builder.startSeq ?? 0\n return finishBuilder(builder, index, boundarySeq)\n}\n\n/**\n * TrailFolder \u2014 incremental variant of foldTurns for live (append-only) event\n * streams. Events are consumed ONCE via push(); snapshot() materializes the\n * current turn list. The key property for rendering cost:\n *\n * closed turns are materialized once and REUSED (object identity is stable\n * across snapshots), so React.memo row components skip them; only the open\n * turn and the trailing state re-materialize per push burst \u2014 O(delta) work\n * per pull instead of a full O(n) re-fold of the whole log.\n *\n * Semantics mirror foldTurns(events) exactly for any stream \u2014 including the\n * user-boundary fallback mode and the mode flip when the first turn/start\n * arrives after fallback turns were already folded (the flip reconstructs\n * what a from-scratch turn-mode fold would hold: only pending user inputs\n * survive; pre-turn outputs/steps are dropped, matching turn-mode replay):\n * - mode 'unknown': no boundary seen yet \u2014 every fold mode would drop the\n * events, so they are dropped.\n * - mode 'turn': turn/start boundaries (foldTurns' primary mode).\n * - mode 'user': no turn/start anywhere yet (foldByUserBoundaries).\n *\n * push() additionally skips events whose seq is not strictly greater than the\n * last consumed seq, which makes the folder idempotent against duplicate or\n * re-sent pages from the transport.\n */\nexport class TrailFolder {\n private mode: 'unknown' | 'turn' | 'user' = 'unknown'\n\n // turn-mode state\n private readonly builders = new Map<number, Builder>()\n private readonly order: number[] = []\n private readonly frozen: Array<TrailTurn | null> = []\n private readonly pendingUser: Array<{ seq: number; text: string }> = []\n private current: Builder | null = null\n\n // user-mode state\n private readonly userClosed: TrailTurn[] = []\n private userActive: Builder | null = null\n private userActiveTurn: TrailTurn | null = null\n private userCounter = 0\n\n private lastSeq = -1\n private total = 0\n private failed = 0\n private readonly byTool = new Map<string, number>()\n\n /** Highest event seq consumed so far (duplicates / re-pages are skipped). */\n get latestSeq(): number {\n return this.lastSeq\n }\n\n push(event: SessionEventLike): void {\n if (event === null || typeof event !== 'object' || typeof event.type !== 'string') return\n const seq = typeof event.seq === 'number' ? event.seq : Number.NaN\n if (!Number.isFinite(seq) || seq <= this.lastSeq) return\n const data = objectData(event)\n const time = typeof event.time === 'number' ? event.time : undefined\n this.lastSeq = seq\n\n if (this.mode === 'turn') {\n this.applyTurnEvent(event.type, data, seq, time)\n return\n }\n if (this.mode === 'user') {\n if (event.type === 'turn/start' && typeof data.turn === 'number') {\n this.flipToTurnMode()\n this.applyTurnEvent(event.type, data, seq, time)\n } else {\n this.applyUserEvent(event.type, data, seq, time)\n }\n return\n }\n // mode 'unknown': commit on the first mode-discriminating event.\n if (event.type === 'turn/start' && typeof data.turn === 'number') {\n this.mode = 'turn'\n this.applyTurnEvent(event.type, data, seq, time)\n return\n }\n if (event.type === 'user/message' && sourceKindOf(event, data) === 'user') {\n this.mode = 'user'\n this.applyUserEvent(event.type, data, seq, time)\n return\n }\n // No boundary yet: both fold modes would drop this event (no open turn).\n }\n\n /** The current turn list; closed turns keep their object identity. */\n snapshot(): TrailTurn[] {\n if (this.mode === 'unknown') return []\n if (this.mode === 'user') {\n const out = this.userClosed.slice()\n if (this.userActive !== null) {\n if (this.userActiveTurn === null) {\n this.userActiveTurn = finishUserTurn(this.userActive, this.userClosed.length)\n }\n out.push(this.userActiveTurn)\n }\n return out\n }\n const out: TrailTurn[] = []\n for (let i = 0; i < this.order.length; i += 1) {\n const materialized = this.frozen[i]\n if (materialized !== null) {\n out.push(materialized)\n continue\n }\n const builder = this.builders.get(this.order[i])\n if (builder === undefined) continue\n out.push(this.materializeTurn(builder, i))\n }\n // Trailing user messages that never found a turn.\n let counter = this.order.length\n for (const pending of this.pendingUser) {\n const builder = newBuilder(null)\n builder.input = pending.text\n builder.inputSeq = pending.seq\n appendItem(builder, { kind: 'input', seq: pending.seq, text: pending.text, preview: compact(pending.text) })\n out.push(finishBuilder(builder, counter, pending.seq))\n counter += 1\n }\n return out\n }\n\n /** Incrementally maintained step statistics (total / failed / by-tool). */\n stats(): TrailStats {\n const tools = [...this.byTool.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([name, count]) => ({ name, count }))\n return { total: this.total, failed: this.failed, tools }\n }\n\n // \u2500\u2500 turn-mode application (mirrors foldTurns' switch verbatim) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private builderOf(turn: number): Builder {\n let builder = this.builders.get(turn)\n if (builder === undefined) {\n builder = newBuilder(turn)\n builder.position = this.order.length\n this.builders.set(turn, builder)\n this.order.push(turn)\n this.frozen.push(null)\n }\n return builder\n }\n\n private markDirty(builder: Builder): void {\n if (this.mode === 'user') {\n if (builder === this.userActive) this.userActiveTurn = null\n return\n }\n const position = builder.position\n if (position !== undefined && position >= 0 && position < this.frozen.length) {\n this.frozen[position] = null\n }\n }\n\n private addStep(builder: Builder, item: TurnItem & { kind: 'tool' }): void {\n appendItem(builder, item)\n this.total += 1\n if (item.step.kind === 'error') this.failed += 1\n else this.byTool.set(item.step.name, (this.byTool.get(item.step.name) ?? 0) + 1)\n this.markDirty(builder)\n }\n\n private materializeTurn(builder: Builder, position: number): TrailTurn {\n const boundarySeq = builder.startSeq !== null ? builder.startSeq : (builder.inputSeq ?? firstItemSeq(builder.items))\n const materialized = finishBuilder(builder, position, boundarySeq)\n this.frozen[position] = materialized\n return materialized\n }\n\n private applyTurnEvent(type: string, data: Record<string, unknown>, seq: number, time?: number): void {\n switch (type) {\n case 'turn/start': {\n const raw = data.turn\n if (typeof raw !== 'number') break\n const builder = this.builderOf(raw)\n if (builder.startSeq === null) builder.startSeq = seq\n if (builder.startedAt === undefined) builder.startedAt = time\n for (const pending of this.pendingUser.splice(0)) {\n builder.input = pending.text\n builder.inputSeq = pending.seq\n builder.items.unshift({ kind: 'input', seq: pending.seq, text: pending.text, preview: compact(pending.text) })\n }\n this.current = builder\n this.markDirty(builder)\n break\n }\n case 'turn/end': {\n const raw = data.turn\n const target = typeof raw === 'number' ? this.builderOf(raw) : this.current\n if (target !== null) {\n target.endSeq = seq\n target.endedAt = time\n this.current = null\n this.markDirty(target)\n }\n break\n }\n case 'user/message': {\n const preview = contentPreview(data.content)\n if (preview.length === 0) break\n const kind = sourceKindOf({ type, seq, data }, data)\n const text = typeof data.content === 'string'\n ? data.content\n : (typeof data.message === 'object' && data.message !== null\n && typeof (data.message as { content?: unknown }).content === 'string'\n ? (data.message as { content: string }).content\n : preview)\n if (kind === 'user') {\n if (this.current === null) {\n this.pendingUser.push({ seq, text })\n } else {\n this.current.input = text\n this.current.inputSeq = seq\n appendItem(this.current, { kind: 'input', seq, text, preview })\n this.markDirty(this.current)\n }\n } else if (this.current !== null) {\n this.current.contexts.push({ seq, text, preview })\n this.markDirty(this.current)\n }\n break\n }\n case 'assistant/message': {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const content = typeof (message as { content?: unknown }).content !== 'undefined'\n ? (message as { content: unknown }).content\n : data.content\n const text = lastTextBlock(content)\n if (text.length === 0) break\n if (this.current !== null) {\n this.current.output = text\n this.current.outputSeq = seq\n appendItem(this.current, { kind: 'output', seq, text, preview: compact(text) })\n this.markDirty(this.current)\n }\n break\n }\n case 'tool/call': {\n if (this.current === null) break\n const name = typeof data.name === 'string' && data.name.length > 0 ? data.name : 'tool'\n const args = typeof data.arguments === 'string' && data.arguments.length > 0 ? data.arguments : ''\n const step: TrailStep = {\n kind: 'tool',\n seq,\n name,\n argsPreview: compact(args),\n callId: toolCallIdOf(data),\n error: '',\n }\n this.addStep(this.current, { kind: 'tool', step })\n break\n }\n case 'tool/result': {\n const error = (data.error !== null && typeof data.error === 'object' ? data.error : null) as {\n name?: unknown\n code?: unknown\n } | null\n if (error === null) break\n if (this.current === null) break\n const name = typeof error.name === 'string' && error.name.length > 0 ? error.name : 'tool'\n const detail = typeof error.code === 'string' && error.code.length > 0 ? ` \u00B7 ${error.code}` : ''\n const step: TrailStep = {\n kind: 'error',\n seq,\n name,\n argsPreview: '',\n callId: toolCallIdOf(data),\n error: compact(`${name}${detail}`),\n }\n this.addStep(this.current, { kind: 'tool', step })\n break\n }\n case 'compaction/summary': {\n const preview = contentPreview(data.summary)\n if (preview.length === 0) break\n if (this.current !== null) {\n appendItem(this.current, { kind: 'compaction', seq, preview })\n this.markDirty(this.current)\n }\n break\n }\n default:\n // command/run and anything else: ignored (mirrors the trajectory view).\n break\n }\n }\n\n // \u2500\u2500 user-mode application (mirrors foldByUserBoundaries verbatim) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private closeUserTurn(): void {\n if (this.userActive === null) return\n this.userClosed.push(finishUserTurn(this.userActive, this.userCounter))\n this.userCounter += 1\n this.userActive = null\n this.userActiveTurn = null\n }\n\n private applyUserEvent(type: string, data: Record<string, unknown>, seq: number, time?: number): void {\n if (type === 'user/message') {\n const kind = sourceKindOf({ type, seq, data }, data)\n if (kind !== 'user') return\n this.closeUserTurn()\n const text = typeof data.content === 'string' ? data.content : contentPreview(data.content)\n if (text.length === 0) return\n const builder = newBuilder(null)\n builder.startSeq = seq\n builder.startedAt = time\n builder.input = text\n builder.inputSeq = seq\n appendItem(builder, { kind: 'input', seq, text, preview: compact(text) })\n this.userActive = builder\n this.userActiveTurn = null\n return\n }\n if (this.userActive === null) return\n if (type === 'assistant/message') {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const text = lastTextBlock((message as { content?: unknown }).content ?? data.content)\n if (text.length === 0) return\n this.userActive.output = text\n this.userActive.outputSeq = seq\n appendItem(this.userActive, { kind: 'output', seq, text, preview: compact(text) })\n this.userActiveTurn = null\n return\n }\n if (type === 'tool/call') {\n const name = typeof data.name === 'string' && data.name.length > 0 ? data.name : 'tool'\n const args = typeof data.arguments === 'string' && data.arguments.length > 0 ? data.arguments : ''\n const step: TrailStep = {\n kind: 'tool',\n seq,\n name,\n argsPreview: compact(args),\n callId: toolCallIdOf(data),\n error: '',\n }\n this.addStep(this.userActive, { kind: 'tool', step })\n return\n }\n if (type === 'tool/result') {\n const error = (data.error !== null && typeof data.error === 'object' ? data.error : null) as {\n name?: unknown\n code?: unknown\n } | null\n if (error === null) return\n const name = typeof error.name === 'string' && error.name.length > 0 ? error.name : 'tool'\n const detail = typeof error.code === 'string' && error.code.length > 0 ? ` \u00B7 ${error.code}` : ''\n const step: TrailStep = {\n kind: 'error',\n seq,\n name,\n argsPreview: '',\n callId: toolCallIdOf(data),\n error: compact(`${name}${detail}`),\n }\n this.addStep(this.userActive, { kind: 'tool', step })\n return\n }\n // compaction & anything else: foldByUserBoundaries ignores them.\n }\n\n /**\n * A turn/start arrived after user-boundary turns were folded. Reconstruct\n * what a from-scratch turn-mode fold of the consumed prefix would hold:\n * every user input becomes a pending input (in arrival order), and the\n * pre-turn outputs / tool steps are dropped \u2014 turn-mode replay attaches\n * nothing while no turn is open. Statistics reset accordingly.\n */\n private flipToTurnMode(): void {\n const pending: Array<{ seq: number; text: string }> = []\n const take = (turn: TrailTurn): void => {\n for (const item of turn.items) {\n if (item.kind === 'input') {\n pending.push({ seq: item.seq, text: item.text })\n return\n }\n }\n }\n for (const turn of this.userClosed) take(turn)\n // Read the active builder directly: it may never have been snapshot()'d.\n if (this.userActive !== null && this.userActive.inputSeq !== null) {\n pending.push({ seq: this.userActive.inputSeq, text: this.userActive.input })\n }\n this.userClosed.length = 0\n this.userActive = null\n this.userActiveTurn = null\n this.userCounter = 0\n this.pendingUser.length = 0\n this.pendingUser.push(...pending)\n this.total = 0\n this.failed = 0\n this.byTool.clear()\n this.mode = 'turn'\n }\n}\n", "/**\n * locales.ts \u2014 zh/en dictionaries for the trail panel, following the\n * browser language (matching conversation-outline's i18n approach).\n */\n\nexport type Lang = 'zh' | 'en'\n\nexport function detectLang(): Lang {\n if (typeof navigator === 'undefined') return 'en'\n const raw = navigator.language ?? ''\n return raw.toLowerCase().startsWith('zh') ? 'zh' : 'en'\n}\n\nexport interface Dict {\n tabTitle: string\n turnsCount: (n: number) => string\n followLatest: string\n followPaused: string\n newUpdates: string\n filterAll: string\n filterFailed: string\n filterMore: string\n clearFilter: string\n emptyNoTurns: string\n emptyFiltered: string\n loadFailed: string\n loading: string\n retry: string\n stepsCount: (n: number) => string\n toolCalls: (n: number) => string\n input: string\n output: string\n context: string\n contextCount: (n: number) => string\n compaction: string\n /** Short compaction label for the row tag column. */\n compactionTag: string\n jumpProgress: (n: number) => string\n jumpDone: string\n jumpFailed: string\n jumpTrajectory: string\n previewExpand: string\n previewClose: string\n previewTitle: (label: string) => string\n copyTurn: string\n copied: string\n running: string\n error: string\n turnPrefix: (n: number) => string\n expandAll: string\n collapseAll: string\n locate: string\n stepsLabel: string\n filterCollapse: string\n showEarlierSteps: (n: number) => string\n}\n\nconst zh: Dict = {\n tabTitle: '\u8F6E\u6B21\u8F68\u8FF9',\n turnsCount: (n) => `${n} \u8F6E`,\n followLatest: '\u8DDF\u968F\u6700\u65B0',\n followPaused: '\u5DF2\u6682\u505C\u8DDF\u968F',\n newUpdates: '\u6709\u65B0\u66F4\u65B0 \u2193',\n filterAll: '\u5168\u90E8',\n filterFailed: '\u5931\u8D25',\n filterMore: '\u66F4\u591A',\n clearFilter: '\u6E05\u7A7A\u7B5B\u9009',\n emptyNoTurns: '\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u8F6E\u6B21',\n emptyFiltered: '\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u7684\u6B65\u9AA4',\n loadFailed: '\u4F1A\u8BDD\u6570\u636E\u52A0\u8F7D\u5931\u8D25',\n loading: '\u6B63\u5728\u52A0\u8F7D\u4F1A\u8BDD\u6570\u636E\u2026',\n retry: '\u91CD\u8BD5',\n stepsCount: (n) => `${n} \u6B65\u9AA4`,\n toolCalls: (n) => `${n} \u6B21\u8C03\u7528`,\n input: '\u8F93\u5165',\n output: '\u8F93\u51FA',\n context: '\u4E0A\u4E0B\u6587',\n contextCount: (n) => `${n} \u6761`,\n compaction: '\u538B\u7F29\u68C0\u67E5\u70B9',\n compactionTag: '\u538B\u7F29',\n jumpProgress: (n) => `\u6B63\u5728\u5B9A\u4F4D\u66F4\u65E9\u7684\u5BF9\u8BDD\uFF08\u5DF2\u52A0\u8F7D ${n} \u9875\uFF09\u2026`,\n jumpDone: '\u5DF2\u5B9A\u4F4D',\n jumpFailed: '\u65E0\u6CD5\u5B9A\u4F4D\u8BE5\u6D88\u606F',\n jumpTrajectory: '\u5DF2\u5728\u8F68\u8FF9\u89C6\u56FE\u5B9A\u4F4D',\n previewExpand: '\u5C55\u5F00',\n previewClose: '\u5173\u95ED',\n previewTitle: (label) => label,\n copyTurn: '\u590D\u5236\u672C\u8F6E',\n copied: '\u5DF2\u590D\u5236',\n running: '\u8FDB\u884C\u4E2D',\n error: '\u9519\u8BEF',\n turnPrefix: (n) => `\u8F6E\u6B21 ${n}`,\n expandAll: '\u5C55\u5F00\u5168\u90E8',\n collapseAll: '\u5168\u90E8\u6298\u53E0',\n locate: '\u5B9A\u4F4D',\n stepsLabel: '\u5DE5\u5177\u6B65\u9AA4\u6570',\n filterCollapse: '\u6536\u8D77',\n showEarlierSteps: (n) => `\u663E\u793A\u66F4\u65E9 ${n} \u6761`,\n}\n\nconst en: Dict = {\n tabTitle: 'Turn Outline',\n turnsCount: (n) => `${n} turns`,\n followLatest: 'Follow latest',\n followPaused: 'Follow paused',\n newUpdates: 'New updates \u2193',\n filterAll: 'All',\n filterFailed: 'Failed',\n filterMore: 'More',\n clearFilter: 'Clear filter',\n emptyNoTurns: 'No turns in this session yet',\n emptyFiltered: 'No steps match the filter',\n loadFailed: 'Failed to load session data',\n loading: 'Loading session data\u2026',\n retry: 'Retry',\n stepsCount: (n) => `${n} steps`,\n toolCalls: (n) => `${n} calls`,\n input: 'Input',\n output: 'Output',\n context: 'Context',\n contextCount: (n) => `${n} items`,\n compaction: 'Compaction checkpoint',\n compactionTag: 'Fold',\n jumpProgress: (n) => `Locating earlier conversation (loaded ${n} page${n === 1 ? '' : 's'})\u2026`,\n jumpDone: 'Located',\n jumpFailed: 'Could not locate that message',\n jumpTrajectory: 'Located in the trajectory view',\n previewExpand: 'Expand',\n previewClose: 'Close',\n previewTitle: (label) => label,\n copyTurn: 'Copy turn',\n copied: 'Copied',\n running: 'running',\n error: 'error',\n turnPrefix: (n) => `Turn ${n}`,\n expandAll: 'Expand all',\n collapseAll: 'Collapse all',\n locate: 'Locate',\n stepsLabel: 'tool steps',\n filterCollapse: 'Collapse',\n showEarlierSteps: (n) => `Show ${n} earlier`,\n}\n\nconst dicts: Record<Lang, Dict> = { zh, en }\n\nexport function t(lang: Lang, key: keyof Dict): string | number | ((...args: never[]) => unknown) {\n return dicts[lang][key]\n}\n", "/**\n * useTrail.ts \u2014 subscription-driven session-event hook for the trail panel.\n *\n * Refreshing strategy (v0.2+): no timer polling. Pull once on activation,\n * then subscribe to the client `sessions` binding snapshot; any snapshot\n * change (running flip, new steps, older pages) re-pulls the delta. The tab's\n * `visible` gate pauses the subscription entirely.\n *\n * Incremental folding (v0.2.1): incoming events are pushed ONCE into a\n * TrailFolder instead of re-folding the whole accumulated array on every\n * pull. Closed turns keep their object identity across snapshots, so\n * memoized row components skip re-rendering; each throttled pull costs\n * O(delta) folding + O(page) appends instead of a full O(n) re-fold.\n * Duplicate / re-sent pages are dropped by the folder's seq watermark.\n *\n * One pull drains at most MAX_PULL_PAGES pages; if pages remain pending\n * (first open of a huge cold session), the throttled path schedules a\n * continuation pull so the trail is never silently truncated. The chain is\n * guarded: it stops when the tab hides, the session/generation changes, or a\n * pull makes zero seq progress (stalled / looping transport).\n */\nimport * as React from 'react'\nimport type { SessionEventLike } from '../trail-model'\nimport { TrailFolder, type TrailStats, type TrailTurn } from '../trail-model'\n\nexport interface FetchEventsResult {\n events: SessionEventLike[]\n /** True when the server held more events back (page); re-pull to drain. */\n hasMore?: boolean\n}\n\nexport type FetchEvents = (sessionId: string, afterSeq: number) => Promise<FetchEventsResult>\n\nexport interface SessionsServiceLike {\n binding(sessionId: string): {\n session?: SessionFaceLike\n } | undefined\n}\n\nexport interface SessionFaceLike {\n subscribe(listener: () => void): () => void\n getSnapshot(): { running?: boolean } | null\n}\n\nexport type TrailStatus = 'idle' | 'loading' | 'ready' | 'error'\n\nexport interface TrailState {\n turns: TrailTurn[]\n stats: TrailStats\n status: TrailStatus\n error: string | null\n refresh: () => void\n}\n\n/** Cap on drained pages per pull (each page \u2264 PAGE_SIZE events). */\nexport const MAX_PULL_PAGES = 64\n\n/** Default transport: POST the /turntrail/api/events route. */\nexport function fetchTrailEvents(sessionId: string, afterSeq: number, base?: string): Promise<FetchEventsResult> {\n const url = new URL('/turntrail/api/events', base ?? window.location.origin)\n return fetch(url.toString(), {\n method: 'POST',\n headers: { 'content-type': 'application/json', accept: 'application/json' },\n body: JSON.stringify({ sessionId, afterSeq }),\n }).then(async (response) => {\n const data = (await response.json()) as { events?: SessionEventLike[]; hasMore?: boolean; error?: string }\n if (!response.ok || data === null || typeof data !== 'object' || !Array.isArray(data.events)) {\n throw new Error(data !== null && typeof data === 'object' && typeof data.error === 'string'\n ? data.error\n : `HTTP ${response.status}`)\n }\n return { events: data.events, hasMore: data.hasMore === true }\n })\n}\n\nconst EMPTY: TrailTurn[] = []\nconst EMPTY_STATS: TrailStats = { total: 0, failed: 0, tools: [] }\n\nexport function useTrail(\n sessionId: string,\n visible: boolean,\n fetchEvents: FetchEvents,\n sessionsService?: SessionsServiceLike,\n): TrailState {\n const [turns, setTurns] = React.useState<TrailTurn[]>(EMPTY)\n const [stats, setStats] = React.useState<TrailStats>(EMPTY_STATS)\n const [status, setStatus] = React.useState<TrailStatus>('idle')\n const [error, setError] = React.useState<string | null>(null)\n const folderRef = React.useRef<TrailFolder | null>(null)\n const seqRef = React.useRef(0)\n const sessionIdRef = React.useRef(sessionId)\n const fetchRef = React.useRef(fetchEvents)\n const requestGen = React.useRef(0)\n // Throttle the subscription-driven refresh. During live generation DSH emits\n // many events; a network round-trip + fold + re-render on EVERY event\n // hammers the (shared) main thread. Coalesce a burst into one update.\n const lastPullAt = React.useRef(0)\n const throttleTimer = React.useRef<number | null>(null)\n // Mirrors for async callbacks: pull() must know the current visibility and\n // reach the throttled scheduler (defined below it) without re-creating.\n const visibleRef = React.useRef(visible)\n const schedulePullRef = React.useRef<(() => void) | null>(null)\n\n React.useEffect(() => { fetchRef.current = fetchEvents }, [fetchEvents])\n React.useEffect(() => { visibleRef.current = visible }, [visible])\n\n // Cancel any pending throttled pull on teardown.\n React.useEffect(() => () => {\n if (throttleTimer.current !== null) window.clearTimeout(throttleTimer.current)\n schedulePullRef.current = null\n }, [])\n\n const pull = React.useCallback(async (): Promise<void> => {\n const generation = ++requestGen.current\n const sid = sessionIdRef.current\n const seqBefore = seqRef.current\n // True when the drain loop hit MAX_PULL_PAGES with pages still pending.\n let drainCapped = false\n try {\n // Drain possibly-paginated pages until the server log is current.\n for (let page = 0; page < MAX_PULL_PAGES; page += 1) {\n const { events, hasMore } = await fetchRef.current(sid, seqRef.current)\n if (generation !== requestGen.current || sid !== sessionIdRef.current) return\n if (events.length > 0) {\n let folder = folderRef.current\n if (folder === null) folder = folderRef.current = new TrailFolder()\n let anyPushed = false\n for (const event of events) {\n const before = folder.latestSeq\n folder.push(event)\n if (folder.latestSeq !== before) anyPushed = true\n }\n if (anyPushed) {\n const last = events[events.length - 1]\n if (typeof last.seq === 'number' && last.seq > seqRef.current) seqRef.current = last.seq\n setTurns(folder.snapshot())\n setStats(folder.stats())\n }\n }\n if (hasMore !== true || events.length === 0) {\n drainCapped = false\n break\n }\n drainCapped = page === MAX_PULL_PAGES - 1\n }\n setError(null)\n setStatus('ready')\n // A capped drain with pages pending (first open of a huge cold session)\n // must not truncate silently: keep draining through the throttled path.\n // Guards stop a misbehaving transport from looping forever: the tab is\n // still visible, the pull is still current, and THIS pull made real seq\n // progress (a stalled transport yields none, so scheduling stops).\n if (\n drainCapped\n && seqRef.current !== seqBefore\n && generation === requestGen.current\n && sid === sessionIdRef.current\n && visibleRef.current\n && schedulePullRef.current !== null\n ) {\n schedulePullRef.current()\n }\n } catch (reason) {\n if (generation !== requestGen.current) return\n setError(reason instanceof Error ? reason.message : String(reason))\n setStatus((current) => (current === 'ready' ? current : 'error'))\n }\n }, [])\n\n const refresh = React.useCallback(() => { void pull() }, [pull])\n\n /**\n * Throttled pull for the subscription path: at most one run per MIN_GAP ms,\n * trailing catch-up so the last event of a burst is never dropped.\n */\n const schedulePull = React.useCallback((): void => {\n const MIN_GAP = 120\n const now = Date.now()\n const since = now - lastPullAt.current\n if (since >= MIN_GAP) {\n lastPullAt.current = now\n void pull()\n return\n }\n if (throttleTimer.current !== null) return\n throttleTimer.current = window.setTimeout(() => {\n throttleTimer.current = null\n lastPullAt.current = Date.now()\n void pull()\n }, MIN_GAP - since)\n }, [pull])\n // Expose the scheduler to pull()'s cap-continuation (declared above it).\n React.useEffect(() => {\n schedulePullRef.current = schedulePull\n }, [schedulePull])\n\n // Session switch: reset the accumulator.\n React.useEffect(() => {\n sessionIdRef.current = sessionId\n folderRef.current = null\n seqRef.current = 0\n setTurns(EMPTY)\n setStats(EMPTY_STATS)\n setError(null)\n setStatus(sessionId.length > 0 ? 'loading' : 'idle')\n }, [sessionId])\n\n // Activation + subscription-driven refresh (paused while invisible).\n React.useEffect(() => {\n if (sessionId.length === 0 || !visible) return undefined\n void pull()\n const binding = sessionsService?.binding(sessionId)\n const session = binding !== undefined && binding.session !== undefined ? binding.session : undefined\n if (session === undefined || typeof session.subscribe !== 'function') return undefined\n return session.subscribe(() => { schedulePull() })\n }, [sessionId, visible, pull, schedulePull, sessionsService])\n\n return { turns, stats, status, error, refresh }\n}\n", "/**\n * trail-jump.ts \u2014 structured jump-back into the main conversation, via the\n * client `sessions` service (no DOM text matching, no \"load older\" button\n * hacking).\n *\n * chat view: session.loadOlder() loop \u2192 snapshot.chat.nodes (Map,\n * keyed by node key, each with anchorSeq) \u2192 find the node\n * nearest to the target seq \u2192 `[data-chat-anchor-key]`\n * row \u2192 scrollIntoView + flash.\n * trajectory view: snapshot.views.get('trajectory') \u2192 { eventNodes,\n * requests } mapped to `[data-trajectory-row-key]` rows;\n * collapsed summaries are expanded first.\n *\n * All dependencies (session face, document) are injected so the module stays\n * unit-testable.\n */\n\n/** A fold item the user clicked \u2014 anything with a seq. */\nexport interface JumpTarget {\n seq: number\n kind: 'user' | 'assistant' | 'tool' | 'error' | 'compaction' | 'context' | 'input' | 'output'\n callId?: string\n turn?: number\n step?: number\n}\n\nexport interface ChatNodeLike {\n key: string\n anchorSeq: number\n visibility?: string\n kind?: string\n turn?: number\n step?: number\n callId?: string\n}\n\nexport interface SessionSnapshotLike {\n hasMore?: boolean\n chat?: {\n nodes?: {\n values(): Iterable<ChatNodeLike>\n }\n }\n views?: {\n get(kind: string): unknown\n }\n}\n\nexport interface SessionFace {\n loadOlder(): Promise<unknown>\n getSnapshot(): SessionSnapshotLike\n}\n\nexport interface DocumentLike {\n querySelectorAll(selector: string): ArrayLike<{ dataset: Record<string, string>; click?(): void; scrollIntoView(opts?: unknown): void }>\n querySelector(selector: string): { scrollIntoView(opts?: unknown): void; dataset?: Record<string, string>; classList?: { add(name: string): void } } | null\n}\n\nexport interface JumpOptions {\n document: DocumentLike\n guard?: number\n onProgress?: (pages: number) => void\n}\n\nexport type JumpOutcome =\n | { ok: true; mode: 'chat' | 'trajectory' }\n | { ok: false; error: string }\n\n/** Whether the chat view lists a seq inside its current render window. */\nexport function hasSeqInWindow(snapshot: SessionSnapshotLike, seq: number): boolean {\n const store = snapshot.chat?.nodes\n if (store === undefined || typeof store.values !== 'function') return false\n let min = Number.POSITIVE_INFINITY\n let max = Number.NEGATIVE_INFINITY\n for (const node of store.values()) {\n if (node === null || typeof node !== 'object' || typeof node.anchorSeq !== 'number') continue\n if (node.anchorSeq < min) min = node.anchorSeq\n if (node.anchorSeq > max) max = node.anchorSeq\n }\n return min <= seq && seq <= max\n}\n\n/**\n * Find the visible node nearest to a seq (exact \u2192 next \u2192 prev). Hidden\n * nodes (e.g. an injected system-reminder) are skipped, so a hidden input\n * falls back to the next visible row naturally.\n */\nexport function findNodeForSeq(snapshot: SessionSnapshotLike, seq: number): ChatNodeLike | null {\n const store = snapshot.chat?.nodes\n if (store === undefined || typeof store.values !== 'function') return null\n let exact: ChatNodeLike | null = null\n let next: ChatNodeLike | null = null\n let prev: ChatNodeLike | null = null\n for (const node of store.values()) {\n if (node === null || typeof node !== 'object' || node.visibility === 'hidden') continue\n const anchor = node.anchorSeq\n if (typeof anchor !== 'number') continue\n if (Math.abs(anchor - seq) < 0.5) {\n if (exact === null) exact = node\n continue\n }\n if (anchor >= seq && (next === null || anchor < next.anchorSeq)) next = node\n if (anchor <= seq && (prev === null || anchor > prev.anchorSeq)) prev = node\n }\n return exact !== null ? exact : next !== null ? next : prev\n}\n\nfunction isTrajectoryViewActive(document: DocumentLike): boolean {\n return document.querySelector('[data-trajectory-scroll]') !== null\n}\n\n/** trajectory view \u2192 a stable `data-trajectory-row-key` value (or null). */\nexport function trajectoryRecordIdFor(item: JumpTarget, snapshot: SessionSnapshotLike): string | null {\n const views = snapshot.views\n if (views === undefined || typeof views.get !== 'function') return null\n const trajectory = views.get('trajectory')\n if (trajectory === null || typeof trajectory !== 'object') return null\n const view = trajectory as { eventNodes?: unknown[]; requests?: unknown[] }\n const nodes = Array.isArray(view.eventNodes) ? view.eventNodes : []\n const requests = Array.isArray(view.requests) ? view.requests : []\n const nodeWithKind = (kinds: string[], seq: number): { seq?: number; kind?: string; turn?: number; step?: number; callId?: string } | undefined => {\n const matches = nodes.filter((node): node is Record<string, unknown> => (\n node !== null && typeof node === 'object'\n && kinds.includes(String((node as { kind?: unknown }).kind))\n && typeof (node as { seq?: unknown }).seq === 'number'\n ))\n return (matches.find((node) => node.seq === seq)\n ?? matches.filter((node) => (node.seq as number) >= seq).sort((a, b) => (a.seq as number) - (b.seq as number))[0]) as\n { seq?: number; kind?: string; turn?: number; step?: number; callId?: string } | undefined\n }\n\n if (item.kind === 'user' || item.kind === 'context' || item.kind === 'input') {\n const target = nodeWithKind(['user', 'steering', 'context'], item.seq)\n if (target === undefined || typeof target.seq !== 'number') return null\n const rowKind = target.kind === 'steering' ? 'user' : target.kind\n return `${rowKind}\\u0000seq\\u0000${target.seq}`\n }\n if (item.kind === 'assistant' || item.kind === 'output') {\n const target = nodeWithKind(['assistant'], item.seq)\n if (target === undefined || typeof target.turn !== 'number' || typeof target.step !== 'number') return null\n return `assistant\\u0000${target.turn}\\u0000${target.step}`\n }\n if (item.kind === 'tool' || item.kind === 'error') {\n if (typeof item.callId === 'string' && item.callId.length > 0) return `tool\\u0000call\\u0000${item.callId}`\n const target = nodeWithKind(['tool-result'], item.seq)\n if (target === undefined || typeof target.callId !== 'string') return null\n return `tool\\u0000call\\u0000${target.callId}`\n }\n if (item.kind === 'compaction') {\n const compactions: Array<{ purpose?: unknown; startSeq?: number; resultSeq?: number }> = []\n for (const request of requests) {\n if (request === null || typeof request !== 'object') continue\n const record = request as { purpose?: unknown; startSeq?: unknown; resultSeq?: unknown }\n if (record.purpose === 'compaction' && typeof record.startSeq === 'number') {\n compactions.push({ purpose: record.purpose, startSeq: record.startSeq, resultSeq: typeof record.resultSeq === 'number' ? record.resultSeq : undefined })\n }\n }\n const target = compactions.find((request) => request.resultSeq === item.seq)\n ?? compactions.filter((request) => (request.resultSeq ?? -1) >= item.seq)\n .sort((a, b) => (a.resultSeq ?? -1) - (b.resultSeq ?? -1))[0]\n if (target === undefined) return null\n return `compacted\\u0000seq\\u0000${target.startSeq}`\n }\n return null\n}\n\nfunction findTrajectoryRow(document: DocumentLike, key: string): { dataset: Record<string, string>; click?(): void; scrollIntoView(opts?: unknown): void } | null {\n const encoded = encodeURIComponent(key)\n const rows = document.querySelectorAll('tr[data-trajectory-row-key]')\n for (let index = 0; index < rows.length; index += 1) {\n const row = rows[index]\n const value = row.dataset !== undefined ? row.dataset.trajectoryRowKey : undefined\n if (value === encoded || value === key) return row\n }\n return null\n}\n\nfunction findTrajectoryRowBySeq(document: DocumentLike, item: JumpTarget): { dataset: Record<string, string>; click?(): void; scrollIntoView(opts?: unknown): void } | null {\n const rows = document.querySelectorAll('tr[data-trajectory-row-key]')\n for (let index = 0; index < rows.length; index += 1) {\n const row = rows[index]\n const raw = row.dataset.trajectoryRowKey ?? ''\n let id = ''\n try {\n id = decodeURIComponent(raw)\n } catch {\n continue\n }\n if (id.includes(`\\u0000seq\\u0000${item.seq}`)) return row\n if (typeof item.callId === 'string' && id.includes(`\\u0000call\\u0000${item.callId}`)) return row\n }\n return null\n}\n\nfunction expandCollapsedSummaries(document: DocumentLike): void {\n const rows = document.querySelectorAll('tr[data-collapsed-summary]')\n for (let index = 0; index < rows.length; index += 1) {\n const row = rows[index]\n if (typeof row.click === 'function') row.click()\n }\n}\n\nfunction waitForNextFrame(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => resolve())\n } else {\n resolve()\n }\n })\n}\n\n/**\n * Jump to the nearest rendered row for a fold item. Returns the outcome and\n * reports paging progress through onProgress.\n */\nexport async function jumpToItem(\n session: SessionFace,\n item: JumpTarget,\n options: JumpOptions,\n): Promise<JumpOutcome> {\n const guard = Number.isFinite(Number(options.guard)) && (options.guard ?? 0) > 0\n ? Math.min(500, Math.max(1, Math.floor(options.guard ?? 200)))\n : 200\n const document = options.document\n\n try {\n let snapshot = session.getSnapshot()\n const trajectoryActive = isTrajectoryViewActive(document)\n\n if (trajectoryActive) {\n let key = trajectoryRecordIdFor(item, snapshot)\n let row = key === null ? null : findTrajectoryRow(document, key)\n let pages = 0\n if (row === null && snapshot.hasMore === true) {\n while (snapshot.hasMore === true && pages < guard) {\n await session.loadOlder()\n snapshot = session.getSnapshot()\n pages += 1\n options.onProgress?.(pages)\n }\n key = trajectoryRecordIdFor(item, snapshot)\n row = key === null ? null : findTrajectoryRow(document, key)\n }\n if (row === null) {\n expandCollapsedSummaries(document)\n await waitForNextFrame()\n row = key === null ? null : findTrajectoryRow(document, key)\n }\n if (row === null) {\n row = findTrajectoryRowBySeq(document, item)\n }\n if (row === null) return { ok: false, error: 'trajectory-row-not-rendered' }\n row.scrollIntoView({ behavior: 'smooth', block: 'center' })\n return { ok: true, mode: 'trajectory' }\n }\n\n // Chat view: load older pages until the target is in the window.\n let pages = 0\n while (snapshot.hasMore === true && !hasSeqInWindow(snapshot, item.seq) && pages < guard) {\n await session.loadOlder()\n snapshot = session.getSnapshot()\n pages += 1\n options.onProgress?.(pages)\n }\n const node = findNodeForSeq(snapshot, item.seq)\n if (node === null) return { ok: false, error: 'seq-not-rendered' }\n // After loadOlder() the session data updates immediately, but the chat\n // DOM commits a tick later \u2014 give the freshly loaded row a few frames to\n // mount before declaring failure. (Without this the FIRST attempt used to\n // report \"cannot locate\" and the user had to click a second time.)\n const selector = `[data-chat-anchor-key=\"${cssEscape(node.key)}\"]`\n let row = document.querySelector(selector)\n let retries = 0\n while (row === null && retries < 6) {\n await waitForNextFrame()\n row = document.querySelector(selector)\n retries += 1\n }\n if (row === null) return { ok: false, error: 'row-not-in-chat-view' }\n row.scrollIntoView({ behavior: 'smooth', block: 'center' })\n if (row.classList !== undefined && typeof row.classList.add === 'function') {\n row.classList.add('ttl-flash')\n }\n return { ok: true, mode: 'chat' }\n } catch (reason) {\n return { ok: false, error: reason instanceof Error ? reason.message : String(reason) }\n }\n}\n\nfunction cssEscape(value: string): string {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') return CSS.escape(value)\n return value.replace(/[\"\\\\]/g, '\\\\$&')\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,IAAAA,SAAuB;;;ACQhB,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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",
4
+ "sourcesContent": ["/**\n * Client half of dsh-turn-outline: register the \u300C\u8F6E\u6B21\u8F68\u8FF9\u300Dtab with\n * dsh-better-sidebar (optional peer \u2014 inert without it) and inject the\n * token-driven stylesheet once.\n *\n * Bundle purity: `dsh-better-sidebar` is never value-imported \u2014 the tab is\n * registered through `ctx.betterSidebar` at runtime; `react` resolves from\n * the client module system seed; the tab glyph is an inline SVG (no\n * ui-primitives dependency), so the panel works without it.\n */\nimport * as React from 'react'\nimport { trailCss } from './styles'\nimport { TrailTab } from './TrailTab'\n\n/**\n * Tab glyph \u2014 Lucide `route` (ISC license): two round endpoints joined by an\n * S-shaped detour, reading as the trail between turns. Drawn inline at 24\n * grid with stroke 2 so it scales cleanly to the 14px tab size; keeps the tab\n * visually distinct from the source-control branch icon it previously shared\n * (ui-primitives IconBranchOutline16). Chosen over a 4-node waypoints glyph\n * after a 14px density review: 3 elements render far less crowded in the\n * selected-tab state.\n */\nfunction routeIcon(size: number): React.ReactNode {\n const children = [\n React.createElement('circle', { key: 'a', cx: 6, cy: 19, r: 3 }),\n React.createElement('path', { key: 'p', d: 'M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15' }),\n React.createElement('circle', { key: 'b', cx: 18, cy: 5, r: 3 }),\n ]\n return React.createElement(\n 'svg',\n {\n viewBox: '0 0 24 24',\n width: size,\n height: size,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': true,\n },\n children,\n )\n}\n\nexport const inject = ['sessions', 'betterSidebar']\n\nexport function apply(ctx: {\n get(name: string): unknown\n effect<T>(fn: () => T): T\n}): void {\n const style = document.createElement('style')\n style.dataset.plugin = 'dsh-turn-outline'\n style.textContent = trailCss\n document.head.appendChild(style)\n // cordis ctx.effect(fn) RUNS fn to register and treats its RETURN VALUE as\n // the cleanup. A single arrow executes style.remove() immediately (the\n // injected <style> is deleted on the spot \u2192 styles never apply). Double\n // arrow returns the cleanup so the sheet survives (matches conversation-\n // outline, the working reference).\n ctx.effect(() => () => style.remove())\n\n const betterSidebar = ctx.get('betterSidebar')\n if (\n betterSidebar === undefined || betterSidebar === null\n || typeof (betterSidebar as { registerTab?: unknown }).registerTab !== 'function'\n ) {\n // No better-sidebar: stay inert (the plan's no-op path).\n return\n }\n\n ctx.effect(() => (betterSidebar as { registerTab(descriptor: unknown): () => void }).registerTab({\n id: 'turn-outline',\n title: () => (typeof navigator !== 'undefined' && navigator.language.toLowerCase().startsWith('zh')\n ? '\u8F6E\u6B21\u8F68\u8FF9'\n : 'Turn Outline'),\n icon: (size: number) => routeIcon(size),\n order: 46,\n single: true,\n component: (props: {\n ctx: { get(name: string): unknown }\n store?: unknown\n scope: { sessionId: string; cwd?: string }\n tab: { id: string }\n visible: boolean\n }) => React.createElement(TrailTab, {\n ctx: props.ctx,\n scope: props.scope,\n visible: props.visible === true,\n tab: props.tab,\n }),\n }))\n}\n", "/**\n * styles.ts \u2014 DSH-native, token-driven CSS for the trail panel, injected once\n * by the client entry. The design language mirrors the upstream DSH trajectory\n * surface: a monochrome \"ink\" chrome (label tiers + hairline separators +\n * interactive hover washes), flat turn rows instead of cards, a 2px turn rail\n * for the located turn, semantic accent dots (success/warn/neutral) instead of\n * a per-tool rainbow, and monospace tool names/args. Every expanded-body row\n * shares one anatomy \u2014 a 6px semantic dot + a narrow name column + content \u2014\n * so input, output and tool rows read as one grid; the dot carries the whole\n * color channel (category for tools, red for failures).\n * All colors/fonts consume\n * DSW alias tokens (auto light/dark/skin); fallbacks mirror the native dark\n * values so a missing token never falls back to a foreign look.\n *\n * Radius scale (native): 2px micro tag \u00B7 4px chip/step hover \u00B7 6px buttons \u00B7\n * 8px floating surfaces. No 999px pills. Class names are prefixed `ttl-` and\n * are OUR contract (never matched against DSH internals).\n */\nexport const trailCss = `\n.ttl-root {\n box-sizing: border-box;\n position: relative;\n height: 100%;\n min-width: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: var(--dsw-alias-bg-layer-1, #232324);\n color: var(--dsw-alias-label-primary, #f9fafb);\n font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"Helvetica Neue\", Helvetica, Arial, sans-serif);\n font-size: 12px;\n line-height: 18px;\n container: trail-tab / inline-size;\n}\n\n/* \u2500\u2500 Header (36px, hairline) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-header {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 8px;\n flex: none;\n height: 36px;\n padding: 0 8px 0 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));\n}\n.ttl-title {\n flex: none;\n min-width: 0;\n max-width: 55%;\n margin: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-primary, #f9fafb);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ttl-count {\n flex: none;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 12px;\n line-height: 18px;\n font-variant-numeric: tabular-nums;\n}\n.ttl-headerSpacer { flex: 1; min-width: 0; }\n/* Follow-latest: native ghost button + status dot (no checkbox pill). */\n.ttl-toggle {\n box-sizing: border-box;\n flex: none;\n height: 24px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 0 8px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n white-space: nowrap;\n}\n.ttl-toggle:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-label-primary, #f9fafb);\n}\n.ttl-toggleDot {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: transparent;\n box-shadow: inset 0 0 0 1px var(--dsw-alias-label-tertiary, #adb2b8);\n}\n.ttl-toggle[aria-pressed=\"true\"] { color: var(--dsw-alias-label-primary, #f9fafb); }\n.ttl-toggle[aria-pressed=\"true\"] .ttl-toggleDot {\n background: var(--dsw-alias-state-success-primary, #22c55e);\n box-shadow: none;\n}\n\n/* \u2500\u2500 Filter row (flat text chips + semantic dots) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-filterRow {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: 2px;\n flex: none;\n padding: 4px 8px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));\n overflow-x: auto;\n scrollbar-width: thin;\n scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;\n}\n.ttl-filterRow::-webkit-scrollbar { height: 4px; }\n.ttl-filterRow::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, #545557);\n border-radius: 2px;\n}\n.ttl-chip {\n box-sizing: border-box;\n flex: none;\n height: 22px;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 0 8px;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font: inherit;\n font-size: 12px;\n line-height: 16px;\n white-space: nowrap;\n cursor: pointer;\n}\n.ttl-chip:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-label-primary, #f9fafb);\n}\n/* Active chip = business-blue TEXT on the hover wash (native detailTab). */\n.ttl-chip[aria-pressed=\"true\"] {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-state-business-primary, #4176e6);\n}\n.ttl-chipNum {\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-variant-numeric: tabular-nums;\n font-weight: 400;\n}\n.ttl-chip[aria-pressed=\"true\"] .ttl-chipNum { color: inherit; opacity: 0.72; }\n.ttl-chipDot {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n}\n.ttl-dot-success { background: var(--dsw-alias-state-success-primary, #22c55e); }\n.ttl-dot-warn { background: var(--dsw-alias-state-warn-primary, #f59e0b); }\n.ttl-dot-neutral { background: var(--dsw-alias-label-tertiary, #adb2b8); }\n.ttl-moreChip { color: var(--dsw-alias-label-tertiary, #adb2b8); }\n.ttl-moreArrow {\n flex: none;\n display: inline-flex;\n transition: transform 120ms var(--ds-ease-in-out, ease);\n}\n.ttl-moreArrowOpen { transform: rotate(180deg); }\n/* \"More\" dropdown: native elevated menu (layer-2 + hairline + 8px + shadow). */\n.ttl-toolDropdown {\n box-sizing: border-box;\n position: absolute;\n z-index: 9;\n top: 64px;\n left: 8px;\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n width: max-content;\n max-width: calc(100% - 16px);\n max-height: 180px;\n overflow-y: auto;\n padding: 6px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2, #2c2c2e);\n box-shadow: 0 4px 16px var(--dsw-alias-bg-mask-1, rgba(0, 0, 0, 0.24));\n scrollbar-width: thin;\n scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;\n}\n\n/* \u2500\u2500 Turn list (flat rows, hairline separators) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 8px 8px;\n scrollbar-width: thin;\n scrollbar-color: var(--dsw-alias-scrollbar-bg-l2, #545557) transparent;\n}\n.ttl-list::-webkit-scrollbar { width: 6px; }\n.ttl-list::-webkit-scrollbar-thumb {\n background: var(--dsw-alias-scrollbar-bg-l2, #545557);\n border-radius: 3px;\n}\n.ttl-status {\n margin: 14px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 12px;\n line-height: 18px;\n}\n.ttl-error {\n margin: 14px;\n color: var(--dsw-alias-state-error-primary, #f25a5a);\n font-size: 12px;\n line-height: 18px;\n}\n\n/* \u2500\u2500 Turn row (no card; 2px rail marks the located turn) \u2500\u2500\u2500 */\n.ttl-turnRow {\n position: relative;\n border-top: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));\n background: transparent;\n}\n.ttl-turnRow:first-child { border-top: none; }\n.ttl-turnRow[data-active=\"true\"]::before {\n content: \"\";\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 22%, var(--dsw-alias-bg-layer-1, #232324));\n}\n.ttl-turnRow[data-active=\"true\"][data-error=\"true\"]::before {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #f25a5a) 22%, var(--dsw-alias-bg-layer-1, #232324));\n}\n.ttl-turnHead {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 0;\n min-height: 32px;\n padding: 0 6px 0 10px;\n}\n.ttl-turnHead:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n}\n.ttl-turnToggle {\n box-sizing: border-box;\n min-width: 0;\n flex: 1;\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 4px 0;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.ttl-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n transition: transform 120ms var(--ds-ease-in-out, ease);\n}\n.ttl-chevronOpen { transform: rotate(90deg); }\n/* \"\u8F6E\u6B21 N\": native turnLabel-style micro tag (mono, module bg, 2px radius). */\n.ttl-turnTag {\n box-sizing: border-box;\n flex: none;\n padding: 1px 5px;\n border-radius: 2px;\n background: var(--dsw-alias-bg-module-platform, #353638);\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-family: var(--ds-font-family-code, \"SF Mono\", \"JetBrains Mono\", \"Fira Code\", Consolas, \"Liberation Mono\", Menlo, Courier, \"PingFang SC\", \"Microsoft YaHei\", monospace);\n font-size: 10px;\n font-weight: 500;\n line-height: 12px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n user-select: none;\n}\n.ttl-turnRow[data-active=\"true\"] .ttl-turnTag {\n color: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 55%, var(--dsw-alias-label-tertiary, #adb2b8));\n background: color-mix(in srgb, var(--dsw-static-blue-500, #3b82f6) 14%, var(--dsw-alias-bg-layer-1, #232324));\n}\n.ttl-turnPreview {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font-size: 12px;\n line-height: 18px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ttl-turnMeta {\n flex: none;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-left: 8px;\n}\n/* Step count: a bare tabular number (never a colored badge). */\n.ttl-stepsNum {\n flex: none;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 11px;\n line-height: 16px;\n font-variant-numeric: tabular-nums;\n}\n.ttl-errorDot {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--dsw-alias-state-error-primary, #f25a5a);\n}\n.ttl-locateBtn {\n box-sizing: border-box;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n cursor: pointer;\n padding: 0;\n}\n.ttl-locateBtn:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-label-primary, #f9fafb);\n}\n\n/* \u2500\u2500 Expanded body (transparent, indented under the rail) \u2500\u2500 */\n.ttl-turnBody {\n box-sizing: border-box;\n padding: 2px 6px 6px 34px;\n background: transparent;\n}\n/* \u2500\u2500 Unified row anatomy: [semantic dot] + [name] + [content] \u2500\u2500\n * One skeleton for every row. The 6px dot carries the whole color channel\n * (blue = user input, violet = assistant output, green/amber/neutral = tool\n * category matching the filter chips' dots, red = failure \u2014 replacing \u2717/\u2713),\n * the name names the row (monospace for tools), content follows. The name\n * column has a narrow 30px min-width: common short names (bash/edit/read,\n * \u8F93\u5165/\u8F93\u51FA) align without dead space; long names just push content. */\n.ttl-row {\n box-sizing: border-box;\n width: 100%;\n display: flex;\n align-items: flex-start;\n gap: 7px;\n padding: 3px 6px;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--dsw-alias-label-primary, #f9fafb);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n text-align: left;\n cursor: pointer;\n}\n.ttl-row:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n}\n.ttl-rowDot {\n flex: none;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n margin-top: 6px;\n}\n.ttl-dot-blue {\n background: var(--dsw-alias-state-business-primary, #4176e6);\n}\n.ttl-dot-violet {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary-new-colorprimary-new-color, #5686fe) 60%, var(--dsw-alias-state-error-secondary, #f25a5a));\n}\n.ttl-dot-red {\n background: var(--dsw-alias-state-error-primary, #f25a5a);\n}\n.ttl-rowName {\n flex: none;\n min-width: 30px;\n max-width: 40%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n.ttl-rowNameMono {\n font-family: var(--ds-font-family-code, \"SF Mono\", \"JetBrains Mono\", \"Fira Code\", Consolas, \"Liberation Mono\", Menlo, Courier, \"PingFang SC\", \"Microsoft YaHei\", monospace);\n}\n.ttl-rowText {\n flex: 1;\n min-width: 0;\n color: var(--dsw-alias-label-primary, #f9fafb);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3;\n line-clamp: 3;\n overflow: hidden;\n}\n.ttl-rowText2 {\n -webkit-line-clamp: 2;\n line-clamp: 2;\n}\n.ttl-rowTextMuted { color: var(--dsw-alias-label-tertiary, #adb2b8); }\n.ttl-rowArgs {\n flex: 1;\n min-width: 0;\n font-family: var(--ds-font-family-code, \"SF Mono\", \"JetBrains Mono\", \"Fira Code\", Consolas, \"Liberation Mono\", Menlo, Courier, \"PingFang SC\", \"Microsoft YaHei\", monospace);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n/* Error rows read red across dot and payload. */\n.ttl-row[data-step-kind=\"error\"] .ttl-rowArgs {\n color: var(--dsw-alias-state-error-primary, #f25a5a);\n}\n.ttl-emptyInline {\n padding: 6px 8px;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 11px;\n line-height: 16px;\n}\n.ttl-showEarlier {\n box-sizing: border-box;\n width: 100%;\n height: 26px;\n margin: 2px 0 4px;\n padding: 0 8px;\n border: none;\n border-bottom: 1px dashed var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));\n border-radius: 0;\n background: transparent;\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font: inherit;\n font-size: 11px;\n line-height: 16px;\n text-align: left;\n cursor: pointer;\n}\n.ttl-showEarlier:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n}\n/* \u2500\u2500 Status bar / jump feedback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-statusBar {\n box-sizing: border-box;\n flex: none;\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 30px;\n padding: 0 14px;\n border-top: 1px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.06));\n color: var(--dsw-alias-label-tertiary, #adb2b8);\n font-size: 12px;\n line-height: 18px;\n}\n.ttl-spin {\n display: inline-flex;\n color: var(--dsw-alias-state-business-primary, #4176e6);\n animation: ttl-spin 1s linear infinite;\n}\n@keyframes ttl-spin { to { transform: rotate(360deg); } }\n.ttl-jumpBanner {\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 8;\n display: flex;\n align-items: center;\n gap: 7px;\n max-width: 90%;\n padding: 5px 10px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));\n border-radius: 8px;\n background: var(--dsw-alias-button-floating-fill, #2c2c2e);\n color: var(--dsw-alias-label-primary, #f9fafb);\n font-size: 12px;\n font-weight: 400;\n line-height: 18px;\n white-space: nowrap;\n box-shadow: 0 4px 16px var(--dsw-alias-bg-mask-1, rgba(0, 0, 0, 0.24));\n}\n.ttl-toBottom {\n position: absolute;\n right: 12px;\n bottom: 12px;\n z-index: 5;\n box-sizing: border-box;\n height: 28px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.12));\n border-radius: 8px;\n background: var(--dsw-alias-button-floating-fill, #2c2c2e);\n color: var(--dsw-alias-label-secondary, #cfd3d6);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n}\n.ttl-toBottom:hover {\n background: var(--dsw-alias-interactive-bg-hover-solid, #353638);\n color: var(--dsw-alias-label-primary, #f9fafb);\n}\n\n/* \u2500\u2500 Jump highlight flash (on the main conversation) \u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.ttl-flash { animation: ttl-flash 1.6s ease-out; }\n@keyframes ttl-flash {\n 0% { box-shadow: 0 0 0 3px var(--dsw-alias-state-business-primary, #4176e6); }\n 100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ttl-spin { animation: none; }\n}\n`\n", "/**\n * TrailTab.tsx \u2014 the \u300C\u8F6E\u6B21\u8F68\u8FF9\u300Dpanel as a compact vertical turn timeline,\n * styled in the DSH-native design language (ink chrome, hairline separators,\n * flat rows \u2014 see styles.ts): a one-line header per turn (chevron + \"\u8F6E\u6B21 N\"\n * micro tag + input preview + bare tabular step count), an expanded flat list\n * of unified rows \u2014 [semantic dot] + [name] + [content] where the dot carries\n * the color channel (blue=input, violet=output, green/amber/neutral=tool\n * category, red=failure) \u2014 filter chips with matching category dots,\n * follow-latest auto-scroll, and click-to-locate (jump back into the main\n * conversation).\n *\n * Rendering cost model (v0.2.1): foldTurns ran over the WHOLE log and rebuilt\n * every TrailTurn object per poll, so every row re-rendered and index-based\n * keys + tail truncation remounted rows on each burst. Now:\n * - useTrail reuses closed TrailTurn objects (identity stable) \u2014 each poll\n * only the open turn's object changes;\n * - rows are keyed by event seq (stable), so appended steps reconcile in\n * place instead of shifting every key;\n * - each turn renders as a memoized TurnRow, so closed turns are skipped\n * entirely on the hot path.\n */\nimport * as React from 'react'\nimport type { TurnItem, TrailTurn, TrailStep, SessionEventLike } from '../trail-model'\nimport { stepsOf } from '../trail-model'\nimport { detectLang, t, type Lang } from './locales'\nimport { fetchTrailEvents, useTrail, type SessionsServiceLike } from './useTrail'\nimport { jumpToItem, type JumpTarget, type SessionFace } from './trail-jump'\n\nexport interface TrailTabProps {\n /** cordis client context (used only for ctx.get('sessions')). */\n ctx?: { get(name: string): unknown }\n scope: { sessionId: string; cwd?: string }\n visible: boolean\n tab: { id: string }\n /** test seam: override the events transport. */\n fetchEventsOverride?: (sessionId: string, afterSeq: number) => Promise<{ events: SessionEventLike[]; hasMore?: boolean }>\n}\n\ntype StepFilter = 'all' | 'failed' | string\n\nfunction sessionsOf(ctx: TrailTabProps['ctx']): SessionsServiceLike | undefined {\n if (ctx === undefined || typeof ctx.get !== 'function') return undefined\n const service = ctx.get('sessions')\n return service !== undefined && service !== null && typeof (service as { binding?: unknown }).binding === 'function'\n ? service as SessionsServiceLike\n : undefined\n}\n\nfunction turnByIdentity(turn: TrailTurn): string {\n return turn.turn !== null ? `turn:${turn.turn}` : `input:${turn.anchorSeq}`\n}\n\n/** Compact big counts: 1500 \u2192 \"1.5k\", 2200 \u2192 \"2.2k\", 384 \u2192 \"384\". */\nfunction formatCount(n: number): string {\n if (n < 1000) return String(n)\n const k = n / 1000\n return `${k >= 10 ? String(Math.round(k)) : String(Math.round(k * 10) / 10)}k`\n}\n\n/** Max step/items rows mounted per expanded turn (bounds the DOM). */\nconst STEP_CAP = 250\n\n/** Full tooltip text cap (server already trims to TEXT_CAP; belt and braces). */\nconst TITLE_CAP = 512\n\nfunction clipTitle(value: string): string {\n return value.length > TITLE_CAP ? `${value.slice(0, TITLE_CAP)}\u2026` : value\n}\n\n/** Semantic dot tone for a tool name (native-restrained: 3 tones + neutral). */\nfunction toolDotClass(name: string): 'success' | 'warn' | 'neutral' {\n const n = name.toLowerCase()\n if (n.includes('edit') || n.includes('write') || n.includes('patch')) return 'warn'\n if (n.includes('bash') || n.includes('command') || n.includes('exec') || n.includes('run')) return 'success'\n return 'neutral'\n}\n\n/** Stable per-item key: event seq (never shifts when new steps arrive). */\nfunction keyOfItem(item: TurnItem): string {\n return `seq:${item.kind === 'tool' ? item.step.seq : item.seq}`\n}\n\n/** The fold item a click targets (seq + kind + call id). */\nfunction targetFor(item: TurnItem): JumpTarget {\n if (item.kind === 'tool') {\n return {\n seq: item.step.seq,\n kind: item.step.kind === 'error' ? 'error' : 'tool',\n ...(item.step.callId !== null ? { callId: item.step.callId } : {}),\n }\n }\n return { seq: item.seq, kind: item.kind }\n}\n\ninterface TurnRowProps {\n turn: TrailTurn\n /** Position in the CURRENT visible list (drives default-latest semantics). */\n index: number\n expanded: boolean\n active: boolean\n showFull: boolean\n filter: StepFilter\n isToolFilter: boolean\n lang: Lang\n onToggle: (id: string) => void\n onShowEarlier: (id: string) => void\n onJump: (target: JumpTarget, turn: TrailTurn) => void\n}\n\nconst TurnRow = React.memo(function TurnRow(props: TurnRowProps): React.ReactElement {\n const { turn, index, expanded, active, showFull, filter, isToolFilter, lang, onToggle, onShowEarlier, onJump } = props\n const id = turnByIdentity(turn)\n\n const stepMatches = (step: TrailStep): boolean => {\n if (filter === 'all') return true\n if (filter === 'failed') return step.kind === 'error'\n return step.kind === 'tool' && step.name === filter\n }\n\n const items = turn.items.filter((item) => {\n if (item.kind === 'tool') return stepMatches(item.step)\n if (item.kind === 'input') return false\n return !isToolFilter\n })\n const turnTag = (t(lang, 'turnPrefix') as (n: number) => string)(turn.turn ?? index + 1)\n const preview = turn.inputPreview\n const hasError = turn.hasError\n /** The turn's user input; rendered as the first row of the expanded body. */\n const inputItem = turn.items.find((item): item is Extract<TurnItem, { kind: 'input' }> => item.kind === 'input')\n\n /** Unified row anatomy: [semantic dot] + [name] + [content]. */\n const dot = (tone: string): React.ReactElement => (\n <span className={`ttl-rowDot ttl-dot-${tone}`} aria-hidden=\"true\" />\n )\n const name = (label: string, mono: boolean): React.ReactElement => (\n <span className={`ttl-rowName${mono ? ' ttl-rowNameMono' : ''}`}>{label}</span>\n )\n\n /** First row of the expanded body: full input (3-line clamp), click = locate. */\n const renderInputRow = (item: Extract<TurnItem, { kind: 'input' }>): React.ReactElement => (\n <button\n type=\"button\"\n className=\"ttl-row\"\n data-testid=\"ttl-item-input\"\n title={clipTitle(item.text)}\n onClick={() => { onJump({ seq: item.seq, kind: 'input' }, turn) }}\n >\n {dot('blue')}\n {name(t(lang, 'input') as string, false)}\n <span className=\"ttl-rowText\">{item.text}</span>\n </button>\n )\n\n const renderStep = (step: TrailStep, key: string): React.ReactElement => {\n const isError = step.kind === 'error'\n const text = isError ? step.error : step.argsPreview\n const tone = isError ? 'red' : toolDotClass(step.name)\n return (\n <button\n type=\"button\"\n className=\"ttl-row\"\n key={key}\n data-testid=\"ttl-step\"\n data-step-kind={step.kind}\n title={text}\n onClick={() => { onJump(targetFor({ kind: 'tool', step }), turn) }}\n >\n {dot(tone)}\n {name(step.name, true)}\n <span className=\"ttl-rowArgs\">{text}</span>\n </button>\n )\n }\n\n const renderItem = (item: TurnItem, key: string): React.ReactElement | null => {\n if (item.kind === 'tool') return renderStep(item.step, key)\n if (item.kind === 'input') return null // shown as the body's first row\n if (item.kind === 'output') {\n return (\n <button\n type=\"button\"\n key={key}\n className=\"ttl-row\"\n data-testid=\"ttl-item-output\"\n title={clipTitle(item.text)}\n onClick={() => { onJump(targetFor(item), turn) }}\n >\n {dot('violet')}\n {name(t(lang, 'output') as string, false)}\n <span className=\"ttl-rowText ttl-rowText2\">{item.text}</span>\n </button>\n )\n }\n return (\n <button\n type=\"button\"\n key={key}\n className=\"ttl-row\"\n data-testid=\"ttl-item-compaction\"\n title={clipTitle(item.preview)}\n onClick={() => { onJump(targetFor(item), turn) }}\n >\n {dot('neutral')}\n {name(t(lang, 'compactionTag') as string, false)}\n <span className=\"ttl-rowText ttl-rowText2 ttl-rowTextMuted\">{item.preview}</span>\n </button>\n )\n }\n\n return (\n <section\n className=\"ttl-turnRow\"\n data-testid=\"ttl-turn\"\n data-collapsed={expanded ? 'false' : 'true'}\n data-active={active ? 'true' : undefined}\n data-error={hasError ? 'true' : undefined}\n >\n <div className=\"ttl-turnHead\">\n <button\n type=\"button\"\n className=\"ttl-turnToggle\"\n aria-expanded={expanded}\n onClick={() => { onToggle(id) }}\n >\n <span className={`ttl-chevron${expanded ? ' ttl-chevronOpen' : ''}`}>{chevronIcon()}</span>\n <span className=\"ttl-turnTag\" data-testid=\"ttl-turn-tag\">{turnTag}</span>\n {preview.length > 0\n ? <span className=\"ttl-turnPreview\" title={clipTitle(preview)}>{preview}</span>\n : null}\n </button>\n <span className=\"ttl-turnMeta\">\n {hasError ? <span className=\"ttl-errorDot\" data-testid=\"ttl-error-dot\" title={t(lang, 'error') as string} /> : null}\n <span\n className=\"ttl-stepsNum\"\n data-testid=\"ttl-steps-count\"\n title={t(lang, 'stepsLabel') as string}\n >{formatCount(turn.stepsCount)}</span>\n <button\n type=\"button\"\n className=\"ttl-locateBtn\"\n data-testid=\"ttl-locate\"\n title={t(lang, 'locate') as string}\n onClick={() => { onJump({ seq: turn.anchorSeq, kind: 'input' }, turn) }}\n >{targetIcon()}</button>\n </span>\n </div>\n {expanded\n ? (() => {\n // Bound the expanded body so a turn with thousands of steps\n // doesn't mount thousands of rows (and re-render them every\n // throttled update). Show the most recent STEP_CAP items plus a\n // \"show earlier\" affordance. Keys are seq-based, so appended\n // steps reconcile in place (rows keep their DOM nodes).\n const truncated = items.length > STEP_CAP && !showFull\n const shownItems = truncated ? items.slice(items.length - STEP_CAP) : items\n const hiddenCount = items.length - shownItems.length\n return (\n <div className=\"ttl-turnBody\">\n {filter === 'all' && inputItem !== undefined ? renderInputRow(inputItem) : null}\n {hiddenCount > 0\n ? <button\n type=\"button\"\n className=\"ttl-showEarlier\"\n data-testid=\"ttl-show-earlier\"\n onClick={() => { onShowEarlier(id) }}\n >{(t(lang, 'showEarlierSteps') as (n: number) => string)(hiddenCount)}</button>\n : null}\n {items.length === 0\n ? <div className=\"ttl-emptyInline\">{t(lang, 'emptyFiltered') as string}</div>\n : shownItems.map((item) => renderItem(item, keyOfItem(item)))}\n </div>\n )\n })()\n : null}\n </section>\n )\n})\n\nexport function TrailTab(props: TrailTabProps): React.ReactElement | null {\n const lang: Lang = detectLang()\n const sessionId = props.scope?.sessionId ?? ''\n const visible = props.visible === true\n const sessionsService = React.useMemo(() => sessionsOf(props.ctx), [props.ctx])\n const fetchEvents = React.useMemo(\n () => props.fetchEventsOverride !== undefined\n ? props.fetchEventsOverride\n : (sid: string, afterSeq: number) => fetchTrailEvents(sid, afterSeq),\n [props.fetchEventsOverride],\n )\n\n const { turns, stats, status, error, refresh } = useTrail(sessionId, visible, fetchEvents, sessionsService)\n\n const [filter, setFilter] = React.useState<StepFilter>('all')\n const [expandedTurns, setExpandedTurns] = React.useState<Set<string> | null>(null)\n const [jumping, setJumping] = React.useState<number | null>(null)\n const [jumpPages, setJumpPages] = React.useState(0)\n const [jumpMessage, setJumpMessage] = React.useState<string | null>(null)\n const [activeTurnId, setActiveTurnId] = React.useState<string | null>(null)\n const [followLatest, setFollowLatest] = React.useState(true)\n const [followPaused, setFollowPaused] = React.useState(false)\n const [moreToolsOpen, setMoreToolsOpen] = React.useState(false)\n const [showFullTurn, setShowFullTurn] = React.useState<Set<string>>(new Set())\n const listRef = React.useRef<HTMLDivElement | null>(null)\n const jumpingRef = React.useRef(false)\n\n // \u2500\u2500 Follow-latest auto-scroll (rAF-coalesced; no forced reflow per burst) \u2500\n const onScroll = React.useCallback((): void => {\n const el = listRef.current\n if (el === null) return\n const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 32\n setFollowPaused(!nearBottom)\n }, [])\n\n React.useEffect(() => {\n if (!followLatest || followPaused) return undefined\n const el = listRef.current\n if (el === null) return undefined\n if (typeof window.requestAnimationFrame === 'function') {\n const raf = window.requestAnimationFrame(() => { el.scrollTop = el.scrollHeight })\n return () => window.cancelAnimationFrame(raf)\n }\n const timeout = window.setTimeout(() => { el.scrollTop = el.scrollHeight }, 0)\n return () => window.clearTimeout(timeout)\n }, [turns, followLatest, followPaused])\n\n const scrollToBottom = React.useCallback((): void => {\n const el = listRef.current\n if (el === null) return\n el.scrollTop = el.scrollHeight\n setFollowPaused(false)\n }, [])\n\n // \u2500\u2500 Expansion helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const latestIdRef = React.useRef('')\n React.useEffect(() => {\n latestIdRef.current = turns.length > 0 ? turnByIdentity(turns[turns.length - 1]) : ''\n }, [turns])\n\n const isTurnExpanded = React.useCallback((id: string, index: number): boolean => {\n if (expandedTurns === null) return index === turns.length - 1\n return expandedTurns.has(id)\n }, [expandedTurns, turns.length])\n\n const toggleTurn = React.useCallback((id: string): void => {\n setExpandedTurns((current) => {\n if (current === null) {\n // Default state = only the latest turn expanded. Materialize THAT\n // single set, then toggle just the clicked turn \u2014 never \"expand\n // every other turn\" (that inverted the default on first click).\n const latestId = latestIdRef.current\n const next = new Set<string>()\n if (latestId.length > 0) next.add(latestId)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n }\n const next = new Set(current)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }, [])\n\n const toggleShowFull = React.useCallback((id: string): void => {\n setShowFullTurn((current) => {\n const next = new Set(current)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }, [])\n\n // \u2500\u2500 Locate (jump) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const sessionForJump = React.useMemo<SessionFace | null>(() => {\n if (sessionsService === undefined) return null\n const binding = sessionsService.binding(sessionId)\n const session = binding !== undefined && binding.session !== undefined ? binding.session : undefined\n if (session === undefined) return null\n const face = session as unknown as { loadOlder?: unknown; getSnapshot?: unknown }\n if (typeof face.loadOlder !== 'function' || typeof face.getSnapshot !== 'function') return null\n return session as unknown as SessionFace\n }, [sessionsService, sessionId])\n\n const doJump = React.useCallback(async (target: JumpTarget, turn: TrailTurn): Promise<void> => {\n if (jumpingRef.current) return // ignore re-entry while a jump is in flight\n setActiveTurnId(turnByIdentity(turn))\n if (sessionForJump === null) {\n setJumpMessage(t(lang, 'jumpFailed') as string)\n return\n }\n jumpingRef.current = true\n setJumping(target.seq)\n setJumpPages(0)\n setJumpMessage(null)\n const outcome = await jumpToItem(sessionForJump, target, {\n document: window.document,\n onProgress: (pages) => setJumpPages(pages),\n })\n jumpingRef.current = false\n setJumping(null)\n if (outcome.ok) {\n setJumpMessage(t(lang, outcome.mode === 'trajectory' ? 'jumpTrajectory' : 'jumpDone') as string)\n } else {\n setJumpMessage(t(lang, 'jumpFailed') as string)\n }\n }, [sessionForJump, lang])\n\n // \u2500\u2500 Filtered rendering helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const stepMatches = (step: TrailStep): boolean => {\n if (filter === 'all') return true\n if (filter === 'failed') return step.kind === 'error'\n return step.kind === 'tool' && step.name === filter\n }\n\n const visibleTurns = React.useMemo(\n () => (filter === 'all' || filter === 'failed' ? turns : turns.filter((turn) => stepsOf(turn).some((step) => stepMatches(step)))),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [turns, filter],\n )\n\n const toolChips = stats.tools.slice(0, 4)\n const hasMoreTools = stats.tools.length > 4\n const isToolFilter = filter !== 'all' && filter !== 'failed'\n\n if (!visible) return null\n\n // \u2500\u2500 Render pieces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const chipButton = (value: StepFilter, label: string, count: number, dot?: 'success' | 'warn' | 'neutral'): React.ReactElement => (\n <button\n type=\"button\"\n key={value}\n className=\"ttl-chip\"\n aria-pressed={filter === value ? 'true' : 'false'}\n data-testid={`ttl-chip-${value}`}\n onClick={() => { setFilter((current) => (current === value ? 'all' : value)) }}\n >\n {dot ? <span className={`ttl-chipDot ttl-dot-${dot}`} aria-hidden=\"true\" /> : null}\n <span>{label}</span>\n {count > 0 ? <span className=\"ttl-chipNum\">{String(count)}</span> : null}\n </button>\n )\n\n const header = (\n <div className=\"ttl-header\">\n <h2 className=\"ttl-title\">{t(lang, 'tabTitle') as string}</h2>\n <span className=\"ttl-count\" data-testid=\"ttl-count\">{(t(lang, 'turnsCount') as (n: number) => string)(turns.length)}</span>\n <span className=\"ttl-headerSpacer\" />\n <button\n type=\"button\"\n className=\"ttl-toggle\"\n aria-pressed={followLatest ? 'true' : 'false'}\n data-testid=\"ttl-follow-toggle\"\n onClick={() => { setFollowLatest((current) => !current) }}\n >\n <span className=\"ttl-toggleDot\" aria-hidden=\"true\" />\n {t(lang, 'followLatest') as string}\n </button>\n </div>\n )\n\n const chips = (\n <>\n <div className=\"ttl-filterRow\" role=\"group\" data-testid=\"ttl-chips\">\n {chipButton('all', t(lang, 'filterAll') as string, stats.total)}\n {stats.failed > 0 ? chipButton('failed', t(lang, 'filterFailed') as string, stats.failed) : null}\n {toolChips.map((tool) => chipButton(tool.name, tool.name, tool.count, toolDotClass(tool.name)))}\n {hasMoreTools\n ? <button\n type=\"button\"\n key=\"more\"\n className=\"ttl-chip ttl-moreChip\"\n aria-expanded={moreToolsOpen ? 'true' : 'false'}\n data-testid=\"ttl-chip-more\"\n onClick={() => { setMoreToolsOpen((current) => !current) }}\n >\n {t(lang, moreToolsOpen ? 'filterCollapse' : 'filterMore') as string}\n <span className={`ttl-moreArrow${moreToolsOpen ? ' ttl-moreArrowOpen' : ''}`}>{chevronIcon()}</span>\n </button>\n : null}\n </div>\n {moreToolsOpen && hasMoreTools\n ? <div className=\"ttl-toolDropdown\" data-testid=\"ttl-tool-dropdown\">\n {stats.tools.map((tool) => chipButton(tool.name, tool.name, tool.count, toolDotClass(tool.name)))}\n </div>\n : null}\n </>\n )\n\n let body: React.ReactElement\n if (status === 'loading') {\n body = <div className=\"ttl-status\" data-testid=\"ttl-status\">{t(lang, 'loading') as string}</div>\n } else if (status === 'error' && turns.length === 0) {\n body = (\n <div>\n <div className=\"ttl-error\" data-testid=\"ttl-error\">{error ?? t(lang, 'loadFailed') as string}</div>\n <div className=\"ttl-filterRow\">\n <button type=\"button\" className=\"ttl-chip\" onClick={refresh} data-testid=\"ttl-retry\">{t(lang, 'retry') as string}</button>\n </div>\n </div>\n )\n } else if (turns.length === 0) {\n body = <div className=\"ttl-status\" data-testid=\"ttl-empty\">{t(lang, 'emptyNoTurns') as string}</div>\n } else if (visibleTurns.length === 0) {\n body = (\n <div className=\"ttl-status\" data-testid=\"ttl-empty-filtered\">\n {t(lang, 'emptyFiltered') as string}\n <button type=\"button\" className=\"ttl-chip\" style={{ marginLeft: 8 }} onClick={() => { setFilter('all') }}>{t(lang, 'clearFilter') as string}</button>\n </div>\n )\n } else {\n body = (\n <div className=\"ttl-list\" ref={listRef} onScroll={onScroll} data-testid=\"ttl-list\">\n {visibleTurns.map((turn, index) => (\n <TurnRow\n key={turnByIdentity(turn)}\n turn={turn}\n index={index}\n expanded={isTurnExpanded(turnByIdentity(turn), index)}\n active={activeTurnId === turnByIdentity(turn)}\n showFull={showFullTurn.has(turnByIdentity(turn))}\n filter={filter}\n isToolFilter={isToolFilter}\n lang={lang}\n onToggle={toggleTurn}\n onShowEarlier={toggleShowFull}\n onJump={doJump}\n />\n ))}\n </div>\n )\n }\n\n const statusBar = jumping !== null || jumpMessage !== null\n ? (\n <div className=\"ttl-statusBar\" data-testid=\"ttl-statusbar\">\n {jumping !== null ? <span className=\"ttl-spin\">{spinnerIcon()}</span> : null}\n <span>\n {jumping !== null ? `${(t(lang, 'jumpProgress') as (n: number) => string)(jumpPages)} ` : ''}\n {jumpMessage ?? ''}\n </span>\n </div>\n )\n : null\n\n return (\n <div className=\"ttl-root\" role=\"complementary\" aria-label={t(lang, 'tabTitle') as string} data-testid=\"ttl-root\">\n {header}\n {chips}\n {body}\n {statusBar}\n {jumping !== null\n ? <div className=\"ttl-jumpBanner\" data-testid=\"ttl-jump-banner\" role=\"status\">\n <span className=\"ttl-spin\">{spinnerIcon()}</span>\n {(t(lang, 'jumpProgress') as (n: number) => string)(jumpPages)}\n </div>\n : null}\n {followPaused && turns.length > 0\n ? <button type=\"button\" className=\"ttl-toBottom\" data-testid=\"ttl-to-bottom\" onClick={scrollToBottom}>{t(lang, 'newUpdates') as string}</button>\n : null}\n </div>\n )\n}\n\nfunction chevronIcon(): React.ReactElement {\n return (\n <svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path d=\"M6 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\nfunction spinnerIcon(): React.ReactElement {\n return (\n <svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path d=\"M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2.5v3h-3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\nfunction targetIcon(): React.ReactElement {\n return (\n <svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <circle cx=\"8\" cy=\"8\" r=\"5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <circle cx=\"8\" cy=\"8\" r=\"1.6\" fill=\"currentColor\" />\n </svg>\n )\n}\n", "/**\n * trail-model.ts \u2014 pure event \u2192 turn folding for dsh-turn-outline.\n *\n * Framework-agnostic (no React / DOM / Node): a single foldTurns() maps a\n * session event stream onto per-user-turn summaries:\n *\n * turn boundary = `turn/start .. turn/end` (the `data.turn` number is the\n * turn identity \u2014 stable under parallel steps / steering).\n * input = first `user/message` with source.kind === 'user'.\n * context rows = `steering` / `context` user messages (kept as grey rows).\n * tool steps = `tool/call` (+ `tool/result` error rows).\n * tool groups = consecutive tool/call steps (>=2) merged, broken by an\n * assistant output, an error, a compaction, a user\n * message or a turn end.\n * output = `assistant/message` text (last text block wins).\n * compaction = `compaction/summary` shown as a checkpoint row.\n * command/run = ignored (mirrors the trajectory view).\n *\n * Fallback mode: a stream with NO turn/start events folds on\n * `user/message` boundaries, so the panel still works for odd logs.\n *\n * Field asymmetry (verified against DSH 0.1.1-rc.2):\n * user/message text lives in data.content (block array or bare string),\n * assistant/message text lives in data.message.content.\n */\n\nexport interface SessionEventLike {\n type: string\n seq: number\n time?: number\n data?: Record<string, unknown> | null\n}\n\nexport type TurnItem =\n | { kind: 'input'; seq: number; text: string; preview: string }\n | { kind: 'tool'; step: TrailStep }\n | { kind: 'output'; seq: number; text: string; preview: string }\n | { kind: 'compaction'; seq: number; preview: string }\n\nexport interface TrailStep {\n kind: 'tool' | 'error'\n seq: number\n name: string\n argsPreview: string\n callId: string | null\n error: string\n}\n\nexport interface TrailTurn {\n index: number\n turn: number | null\n /** The seq a jump targets: the user input, else the first item. */\n anchorSeq: number\n inputPreview: string\n outputPreview: string\n status: 'pending' | 'running' | 'done' | 'error'\n startedAt?: number\n endedAt?: number\n items: TurnItem[]\n stepsCount: number\n /** Number of injected system-reminders / steering for this turn (not rendered). */\n contextCount: number\n hasError: boolean\n}\n\n/** Aggregated step statistics (kept incrementally, not re-derived per pull). */\nexport interface TrailStats {\n total: number\n failed: number\n tools: Array<{ name: string; count: number }>\n}\n\n/** Preview length for one-line summaries. */\nexport const PREVIEW_MAX = 120\n\n/** Flatten every tool/error step of a turn. */\nexport function stepsOf(turn: TrailTurn): TrailStep[] {\n const out: TrailStep[] = []\n for (const item of turn.items) {\n if (item.kind === 'tool') out.push(item.step)\n }\n return out\n}\n\nexport function compact(value: string, max = PREVIEW_MAX): string {\n const oneLine = value.replace(/\\s+/g, ' ').trim()\n if (oneLine.length <= max) return oneLine\n return `${oneLine.slice(0, max - 1).trimEnd()}\u2026`\n}\n\n/** Extract text from a content block array (first non-empty text block). */\nexport function contentPreview(blocks: unknown): string {\n if (typeof blocks === 'string') return compact(blocks)\n if (!Array.isArray(blocks)) return ''\n for (const block of blocks) {\n if (\n block !== null && typeof block === 'object'\n && (block as { type?: unknown }).type === 'text'\n && typeof (block as { text?: unknown }).text === 'string'\n && (block as { text: string }).text.trim().length > 0\n ) {\n return compact((block as { text: string }).text)\n }\n }\n return ''\n}\n\n/** Last text block of an assistant message content array. */\nexport function lastTextBlock(blocks: unknown): string {\n if (typeof blocks === 'string') return blocks\n if (!Array.isArray(blocks)) return ''\n let last = ''\n for (const block of blocks) {\n if (\n block !== null && typeof block === 'object'\n && (block as { type?: unknown }).type === 'text'\n && typeof (block as { text?: unknown }).text === 'string'\n && (block as { text: string }).text.trim().length > 0\n ) {\n last = (block as { text: string }).text\n }\n }\n return last\n}\n\nfunction objectData(event: SessionEventLike): Record<string, unknown> {\n return event.data !== null && typeof event.data === 'object' ? event.data : {}\n}\n\n/** source.kind: 'user' | 'steering' | 'context' | 'plugin' | ... */\nfunction sourceKindOf(event: SessionEventLike, data: Record<string, unknown>): string | undefined {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const source = message !== null && typeof (message as { source?: unknown }).source === 'object'\n ? (message as { source: { kind?: unknown } }).source\n : (data.source !== null && typeof data.source === 'object'\n ? (data.source as { kind?: unknown })\n : null)\n if (source !== null && typeof source.kind === 'string') return source.kind\n return undefined\n}\n\nexport function toolCallIdOf(data: Record<string, unknown>): string | null {\n if (typeof data.callId === 'string' && data.callId.length > 0) return data.callId\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const source = message !== null && typeof (message as { source?: unknown }).source === 'object'\n ? (message as { source: { callId?: unknown } }).source\n : null\n if (source !== null && typeof source.callId === 'string' && source.callId.length > 0) {\n return source.callId\n }\n return null\n}\n\ninterface Builder {\n turn: number | null\n startSeq: number | null\n endSeq: number | null\n input: string\n inputSeq: number | null\n output: string\n outputSeq: number | null\n items: TurnItem[]\n steps: TrailStep[]\n contexts: Array<{ seq: number; text: string; preview: string }>\n hasError: boolean\n startedAt?: number\n endedAt?: number\n /** Turn-mode order position (set by TrailFolder); tracks frozen slots. */\n position?: number\n}\n\nfunction newBuilder(turn: number | null): Builder {\n return {\n turn,\n startSeq: null,\n endSeq: null,\n input: '',\n inputSeq: null,\n output: '',\n outputSeq: null,\n items: [],\n steps: [],\n contexts: [],\n hasError: false,\n }\n}\n\nfunction appendItem(builder: Builder, item: TurnItem): void {\n builder.items.push(item)\n if (item.kind === 'tool') {\n builder.steps.push(item.step)\n if (item.step.kind === 'error') builder.hasError = true\n }\n}\n\nfunction finishBuilder(builder: Builder, index: number, boundarySeq: number): TrailTurn {\n const anchorSeq = builder.inputSeq !== null ? builder.inputSeq : boundarySeq\n const status: TrailTurn['status'] = builder.hasError\n ? 'error'\n : builder.endSeq === null\n ? (builder.items.length > 0 ? 'running' : 'pending')\n : 'done'\n return {\n index,\n turn: builder.turn,\n anchorSeq,\n inputPreview: compact(builder.input),\n outputPreview: compact(builder.output),\n status,\n ...(builder.startedAt !== undefined ? { startedAt: builder.startedAt } : {}),\n ...(builder.endedAt !== undefined ? { endedAt: builder.endedAt } : {}),\n items: builder.items,\n stepsCount: builder.steps.length,\n contextCount: builder.contexts.length,\n hasError: builder.hasError,\n }\n}\n\n/** First seq-bearing entry of an item list (used as a fallback boundary). */\nfunction firstItemSeq(items: TurnItem[]): number {\n for (const item of items) {\n if (item.kind === 'tool') return item.step.seq\n return item.seq\n }\n return 0\n}\n\n/**\n * Fold an event stream onto TrailTurn[].\n * @param events - session events ordered by seq.\n * @returns the folded turns in stream order.\n */\nexport function foldTurns(events: readonly SessionEventLike[]): TrailTurn[] {\n const out: TrailTurn[] = []\n if (!Array.isArray(events) || events.length === 0) return out\n\n const hasTurnEvents = events.some((event) => (\n event !== null && typeof event === 'object'\n && typeof (event as { type?: unknown }).type === 'string'\n && (event as { type: string }).type === 'turn/start'\n ))\n if (!hasTurnEvents) return foldByUserBoundaries(events)\n\n const index = new Map<number, Builder>()\n const order: number[] = []\n const pendingUser: Array<{ seq: number; text: string }> = []\n let current: Builder | null = null\n\n const builderOf = (turn: number): Builder => {\n let builder = index.get(turn)\n if (builder === undefined) {\n builder = newBuilder(turn)\n index.set(turn, builder)\n order.push(turn)\n }\n return builder\n }\n\n for (const event of events) {\n if (event === null || typeof event !== 'object' || typeof event.type !== 'string') continue\n const data = objectData(event)\n const seq = typeof event.seq === 'number' ? event.seq : 0\n const time = typeof event.time === 'number' ? event.time : undefined\n\n switch (event.type) {\n case 'turn/start': {\n const raw = data.turn\n if (typeof raw === 'number') {\n const builder = builderOf(raw)\n if (builder.startSeq === null) builder.startSeq = seq\n if (builder.startedAt === undefined) builder.startedAt = time\n for (const pending of pendingUser.splice(0)) {\n builder.input = pending.text\n builder.inputSeq = pending.seq\n builder.items.unshift({ kind: 'input', seq: pending.seq, text: pending.text, preview: compact(pending.text) })\n }\n current = builder\n }\n break\n }\n case 'turn/end': {\n const raw = data.turn\n const target = typeof raw === 'number' ? builderOf(raw) : current\n if (target !== null) {\n target.endSeq = seq\n target.endedAt = time\n current = null\n }\n break\n }\n case 'user/message': {\n const preview = contentPreview(data.content)\n if (preview.length === 0) break\n const kind = sourceKindOf(event, data)\n const text = typeof data.content === 'string'\n ? data.content\n : (typeof data.message === 'object' && data.message !== null\n && typeof (data.message as { content?: unknown }).content === 'string'\n ? (data.message as { content: string }).content\n : preview)\n if (kind === 'user') {\n if (current === null) {\n pendingUser.push({ seq, text })\n } else {\n current.input = text\n current.inputSeq = seq\n appendItem(current, { kind: 'input', seq, text, preview })\n }\n } else if (current !== null) {\n current.contexts.push({ seq, text, preview })\n }\n break\n }\n case 'assistant/message': {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const content = typeof (message as { content?: unknown }).content !== 'undefined'\n ? (message as { content: unknown }).content\n : data.content\n const text = lastTextBlock(content)\n if (text.length === 0) break\n if (current !== null) {\n current.output = text\n current.outputSeq = seq\n appendItem(current, { kind: 'output', seq, text, preview: compact(text) })\n }\n break\n }\n case 'tool/call': {\n if (current === null) break\n const name = typeof data.name === 'string' && data.name.length > 0 ? data.name : 'tool'\n const args = typeof data.arguments === 'string' && data.arguments.length > 0 ? data.arguments : ''\n const step: TrailStep = {\n kind: 'tool',\n seq,\n name,\n argsPreview: compact(args),\n callId: toolCallIdOf(data),\n error: '',\n }\n appendItem(current, { kind: 'tool', step })\n break\n }\n case 'tool/result': {\n const error = (data.error !== null && typeof data.error === 'object' ? data.error : null) as {\n name?: unknown\n code?: unknown\n } | null\n if (error === null) break\n if (current === null) break\n const name = typeof error.name === 'string' && error.name.length > 0 ? error.name : 'tool'\n const detail = typeof error.code === 'string' && error.code.length > 0 ? ` \u00B7 ${error.code}` : ''\n const step: TrailStep = {\n kind: 'error',\n seq,\n name,\n argsPreview: '',\n callId: toolCallIdOf(data),\n error: compact(`${name}${detail}`),\n }\n appendItem(current, { kind: 'tool', step })\n break\n }\n case 'compaction/summary': {\n const preview = contentPreview(data.summary)\n if (preview.length === 0) break\n if (current !== null) appendItem(current, { kind: 'compaction', seq, preview })\n break\n }\n default:\n // command/run and anything else: ignored.\n break\n }\n }\n\n let counter = 0\n for (const turn of order) {\n const builder = index.get(turn)\n if (builder === undefined) continue\n const boundarySeq = builder.startSeq !== null ? builder.startSeq : (builder.inputSeq ?? firstItemSeq(builder.items))\n out.push(finishBuilder(builder, counter, boundarySeq))\n counter += 1\n }\n // Trailing user messages that never found a turn.\n for (const pending of pendingUser) {\n const builder = newBuilder(null)\n builder.input = pending.text\n builder.inputSeq = pending.seq\n appendItem(builder, { kind: 'input', seq: pending.seq, text: pending.text, preview: compact(pending.text) })\n out.push(finishBuilder(builder, counter, pending.seq))\n counter += 1\n }\n return out\n}\n\n/** Fallback: fold on user/message boundaries when no turn/start exists. */\nexport function foldByUserBoundaries(events: readonly SessionEventLike[]): TrailTurn[] {\n const out: TrailTurn[] = []\n let builder: Builder | null = null\n let counter = 0\n\n const close = (): void => {\n if (builder === null) return\n const boundarySeq = builder.inputSeq !== null ? builder.inputSeq : builder.startSeq ?? 0\n out.push(finishBuilder(builder, counter, boundarySeq))\n counter += 1\n builder = null\n }\n\n for (const event of events) {\n if (event === null || typeof event !== 'object' || typeof event.type !== 'string') continue\n const data = objectData(event)\n const seq = typeof event.seq === 'number' ? event.seq : 0\n const time = typeof event.time === 'number' ? event.time : undefined\n\n if (event.type === 'user/message') {\n const kind = sourceKindOf(event, data)\n if (kind !== 'user') continue\n close()\n const text = typeof data.content === 'string' ? data.content : contentPreview(data.content)\n if (text.length === 0) continue\n builder = newBuilder(null)\n builder.startSeq = seq\n builder.startedAt = time\n builder.input = text\n builder.inputSeq = seq\n appendItem(builder, { kind: 'input', seq, text, preview: compact(text) })\n continue\n }\n if (builder === null) continue\n if (event.type === 'assistant/message') {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const text = lastTextBlock((message as { content?: unknown }).content ?? data.content)\n if (text.length === 0) continue\n builder.output = text\n builder.outputSeq = seq\n appendItem(builder, { kind: 'output', seq, text, preview: compact(text) })\n continue\n }\n if (event.type === 'tool/call') {\n const name = typeof data.name === 'string' && data.name.length > 0 ? data.name : 'tool'\n const args = typeof data.arguments === 'string' && data.arguments.length > 0 ? data.arguments : ''\n const step: TrailStep = {\n kind: 'tool',\n seq,\n name,\n argsPreview: compact(args),\n callId: toolCallIdOf(data),\n error: '',\n }\n // appendItem records the step once (in items AND steps); a redundant\n // direct steps.push here would double-count stepsCount.\n appendItem(builder, { kind: 'tool', step })\n continue\n }\n if (event.type === 'tool/result') {\n const error = (data.error !== null && typeof data.error === 'object' ? data.error : null) as {\n name?: unknown\n code?: unknown\n } | null\n if (error === null) continue\n const name = typeof error.name === 'string' && error.name.length > 0 ? error.name : 'tool'\n const detail = typeof error.code === 'string' && error.code.length > 0 ? ` \u00B7 ${error.code}` : ''\n const step: TrailStep = {\n kind: 'error',\n seq,\n name,\n argsPreview: '',\n callId: toolCallIdOf(data),\n error: compact(`${name}${detail}`),\n }\n appendItem(builder, { kind: 'tool', step })\n continue\n }\n }\n close()\n return out\n}\n\n// \u2500\u2500 Incremental folding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Materialize the open user-mode turn (fallback folding) at its list index. */\nfunction finishUserTurn(builder: Builder, index: number): TrailTurn {\n const boundarySeq = builder.inputSeq !== null ? builder.inputSeq : builder.startSeq ?? 0\n return finishBuilder(builder, index, boundarySeq)\n}\n\n/**\n * TrailFolder \u2014 incremental variant of foldTurns for live (append-only) event\n * streams. Events are consumed ONCE via push(); snapshot() materializes the\n * current turn list. The key property for rendering cost:\n *\n * closed turns are materialized once and REUSED (object identity is stable\n * across snapshots), so React.memo row components skip them; only the open\n * turn and the trailing state re-materialize per push burst \u2014 O(delta) work\n * per pull instead of a full O(n) re-fold of the whole log.\n *\n * Semantics mirror foldTurns(events) exactly for any stream \u2014 including the\n * user-boundary fallback mode and the mode flip when the first turn/start\n * arrives after fallback turns were already folded (the flip reconstructs\n * what a from-scratch turn-mode fold would hold: only pending user inputs\n * survive; pre-turn outputs/steps are dropped, matching turn-mode replay):\n * - mode 'unknown': no boundary seen yet \u2014 every fold mode would drop the\n * events, so they are dropped.\n * - mode 'turn': turn/start boundaries (foldTurns' primary mode).\n * - mode 'user': no turn/start anywhere yet (foldByUserBoundaries).\n *\n * push() additionally skips events whose seq is not strictly greater than the\n * last consumed seq, which makes the folder idempotent against duplicate or\n * re-sent pages from the transport.\n */\nexport class TrailFolder {\n private mode: 'unknown' | 'turn' | 'user' = 'unknown'\n\n // turn-mode state\n private readonly builders = new Map<number, Builder>()\n private readonly order: number[] = []\n private readonly frozen: Array<TrailTurn | null> = []\n private readonly pendingUser: Array<{ seq: number; text: string }> = []\n private current: Builder | null = null\n\n // user-mode state\n private readonly userClosed: TrailTurn[] = []\n private userActive: Builder | null = null\n private userActiveTurn: TrailTurn | null = null\n private userCounter = 0\n\n private lastSeq = -1\n private total = 0\n private failed = 0\n private readonly byTool = new Map<string, number>()\n\n /** Highest event seq consumed so far (duplicates / re-pages are skipped). */\n get latestSeq(): number {\n return this.lastSeq\n }\n\n push(event: SessionEventLike): void {\n if (event === null || typeof event !== 'object' || typeof event.type !== 'string') return\n const seq = typeof event.seq === 'number' ? event.seq : Number.NaN\n if (!Number.isFinite(seq) || seq <= this.lastSeq) return\n const data = objectData(event)\n const time = typeof event.time === 'number' ? event.time : undefined\n this.lastSeq = seq\n\n if (this.mode === 'turn') {\n this.applyTurnEvent(event.type, data, seq, time)\n return\n }\n if (this.mode === 'user') {\n if (event.type === 'turn/start' && typeof data.turn === 'number') {\n this.flipToTurnMode()\n this.applyTurnEvent(event.type, data, seq, time)\n } else {\n this.applyUserEvent(event.type, data, seq, time)\n }\n return\n }\n // mode 'unknown': commit on the first mode-discriminating event.\n if (event.type === 'turn/start' && typeof data.turn === 'number') {\n this.mode = 'turn'\n this.applyTurnEvent(event.type, data, seq, time)\n return\n }\n if (event.type === 'user/message' && sourceKindOf(event, data) === 'user') {\n this.mode = 'user'\n this.applyUserEvent(event.type, data, seq, time)\n return\n }\n // No boundary yet: both fold modes would drop this event (no open turn).\n }\n\n /** The current turn list; closed turns keep their object identity. */\n snapshot(): TrailTurn[] {\n if (this.mode === 'unknown') return []\n if (this.mode === 'user') {\n const out = this.userClosed.slice()\n if (this.userActive !== null) {\n if (this.userActiveTurn === null) {\n this.userActiveTurn = finishUserTurn(this.userActive, this.userClosed.length)\n }\n out.push(this.userActiveTurn)\n }\n return out\n }\n const out: TrailTurn[] = []\n for (let i = 0; i < this.order.length; i += 1) {\n const materialized = this.frozen[i]\n if (materialized !== null) {\n out.push(materialized)\n continue\n }\n const builder = this.builders.get(this.order[i])\n if (builder === undefined) continue\n out.push(this.materializeTurn(builder, i))\n }\n // Trailing user messages that never found a turn.\n let counter = this.order.length\n for (const pending of this.pendingUser) {\n const builder = newBuilder(null)\n builder.input = pending.text\n builder.inputSeq = pending.seq\n appendItem(builder, { kind: 'input', seq: pending.seq, text: pending.text, preview: compact(pending.text) })\n out.push(finishBuilder(builder, counter, pending.seq))\n counter += 1\n }\n return out\n }\n\n /** Incrementally maintained step statistics (total / failed / by-tool). */\n stats(): TrailStats {\n const tools = [...this.byTool.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([name, count]) => ({ name, count }))\n return { total: this.total, failed: this.failed, tools }\n }\n\n // \u2500\u2500 turn-mode application (mirrors foldTurns' switch verbatim) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private builderOf(turn: number): Builder {\n let builder = this.builders.get(turn)\n if (builder === undefined) {\n builder = newBuilder(turn)\n builder.position = this.order.length\n this.builders.set(turn, builder)\n this.order.push(turn)\n this.frozen.push(null)\n }\n return builder\n }\n\n private markDirty(builder: Builder): void {\n if (this.mode === 'user') {\n if (builder === this.userActive) this.userActiveTurn = null\n return\n }\n const position = builder.position\n if (position !== undefined && position >= 0 && position < this.frozen.length) {\n this.frozen[position] = null\n }\n }\n\n private addStep(builder: Builder, item: TurnItem & { kind: 'tool' }): void {\n appendItem(builder, item)\n this.total += 1\n if (item.step.kind === 'error') this.failed += 1\n else this.byTool.set(item.step.name, (this.byTool.get(item.step.name) ?? 0) + 1)\n this.markDirty(builder)\n }\n\n private materializeTurn(builder: Builder, position: number): TrailTurn {\n const boundarySeq = builder.startSeq !== null ? builder.startSeq : (builder.inputSeq ?? firstItemSeq(builder.items))\n const materialized = finishBuilder(builder, position, boundarySeq)\n this.frozen[position] = materialized\n return materialized\n }\n\n private applyTurnEvent(type: string, data: Record<string, unknown>, seq: number, time?: number): void {\n switch (type) {\n case 'turn/start': {\n const raw = data.turn\n if (typeof raw !== 'number') break\n const builder = this.builderOf(raw)\n if (builder.startSeq === null) builder.startSeq = seq\n if (builder.startedAt === undefined) builder.startedAt = time\n for (const pending of this.pendingUser.splice(0)) {\n builder.input = pending.text\n builder.inputSeq = pending.seq\n builder.items.unshift({ kind: 'input', seq: pending.seq, text: pending.text, preview: compact(pending.text) })\n }\n this.current = builder\n this.markDirty(builder)\n break\n }\n case 'turn/end': {\n const raw = data.turn\n const target = typeof raw === 'number' ? this.builderOf(raw) : this.current\n if (target !== null) {\n target.endSeq = seq\n target.endedAt = time\n this.current = null\n this.markDirty(target)\n }\n break\n }\n case 'user/message': {\n const preview = contentPreview(data.content)\n if (preview.length === 0) break\n const kind = sourceKindOf({ type, seq, data }, data)\n const text = typeof data.content === 'string'\n ? data.content\n : (typeof data.message === 'object' && data.message !== null\n && typeof (data.message as { content?: unknown }).content === 'string'\n ? (data.message as { content: string }).content\n : preview)\n if (kind === 'user') {\n if (this.current === null) {\n this.pendingUser.push({ seq, text })\n } else {\n this.current.input = text\n this.current.inputSeq = seq\n appendItem(this.current, { kind: 'input', seq, text, preview })\n this.markDirty(this.current)\n }\n } else if (this.current !== null) {\n this.current.contexts.push({ seq, text, preview })\n this.markDirty(this.current)\n }\n break\n }\n case 'assistant/message': {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const content = typeof (message as { content?: unknown }).content !== 'undefined'\n ? (message as { content: unknown }).content\n : data.content\n const text = lastTextBlock(content)\n if (text.length === 0) break\n if (this.current !== null) {\n this.current.output = text\n this.current.outputSeq = seq\n appendItem(this.current, { kind: 'output', seq, text, preview: compact(text) })\n this.markDirty(this.current)\n }\n break\n }\n case 'tool/call': {\n if (this.current === null) break\n const name = typeof data.name === 'string' && data.name.length > 0 ? data.name : 'tool'\n const args = typeof data.arguments === 'string' && data.arguments.length > 0 ? data.arguments : ''\n const step: TrailStep = {\n kind: 'tool',\n seq,\n name,\n argsPreview: compact(args),\n callId: toolCallIdOf(data),\n error: '',\n }\n this.addStep(this.current, { kind: 'tool', step })\n break\n }\n case 'tool/result': {\n const error = (data.error !== null && typeof data.error === 'object' ? data.error : null) as {\n name?: unknown\n code?: unknown\n } | null\n if (error === null) break\n if (this.current === null) break\n const name = typeof error.name === 'string' && error.name.length > 0 ? error.name : 'tool'\n const detail = typeof error.code === 'string' && error.code.length > 0 ? ` \u00B7 ${error.code}` : ''\n const step: TrailStep = {\n kind: 'error',\n seq,\n name,\n argsPreview: '',\n callId: toolCallIdOf(data),\n error: compact(`${name}${detail}`),\n }\n this.addStep(this.current, { kind: 'tool', step })\n break\n }\n case 'compaction/summary': {\n const preview = contentPreview(data.summary)\n if (preview.length === 0) break\n if (this.current !== null) {\n appendItem(this.current, { kind: 'compaction', seq, preview })\n this.markDirty(this.current)\n }\n break\n }\n default:\n // command/run and anything else: ignored (mirrors the trajectory view).\n break\n }\n }\n\n // \u2500\u2500 user-mode application (mirrors foldByUserBoundaries verbatim) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private closeUserTurn(): void {\n if (this.userActive === null) return\n this.userClosed.push(finishUserTurn(this.userActive, this.userCounter))\n this.userCounter += 1\n this.userActive = null\n this.userActiveTurn = null\n }\n\n private applyUserEvent(type: string, data: Record<string, unknown>, seq: number, time?: number): void {\n if (type === 'user/message') {\n const kind = sourceKindOf({ type, seq, data }, data)\n if (kind !== 'user') return\n this.closeUserTurn()\n const text = typeof data.content === 'string' ? data.content : contentPreview(data.content)\n if (text.length === 0) return\n const builder = newBuilder(null)\n builder.startSeq = seq\n builder.startedAt = time\n builder.input = text\n builder.inputSeq = seq\n appendItem(builder, { kind: 'input', seq, text, preview: compact(text) })\n this.userActive = builder\n this.userActiveTurn = null\n return\n }\n if (this.userActive === null) return\n if (type === 'assistant/message') {\n const message = data.message !== null && typeof data.message === 'object' ? data.message : {}\n const text = lastTextBlock((message as { content?: unknown }).content ?? data.content)\n if (text.length === 0) return\n this.userActive.output = text\n this.userActive.outputSeq = seq\n appendItem(this.userActive, { kind: 'output', seq, text, preview: compact(text) })\n this.userActiveTurn = null\n return\n }\n if (type === 'tool/call') {\n const name = typeof data.name === 'string' && data.name.length > 0 ? data.name : 'tool'\n const args = typeof data.arguments === 'string' && data.arguments.length > 0 ? data.arguments : ''\n const step: TrailStep = {\n kind: 'tool',\n seq,\n name,\n argsPreview: compact(args),\n callId: toolCallIdOf(data),\n error: '',\n }\n this.addStep(this.userActive, { kind: 'tool', step })\n return\n }\n if (type === 'tool/result') {\n const error = (data.error !== null && typeof data.error === 'object' ? data.error : null) as {\n name?: unknown\n code?: unknown\n } | null\n if (error === null) return\n const name = typeof error.name === 'string' && error.name.length > 0 ? error.name : 'tool'\n const detail = typeof error.code === 'string' && error.code.length > 0 ? ` \u00B7 ${error.code}` : ''\n const step: TrailStep = {\n kind: 'error',\n seq,\n name,\n argsPreview: '',\n callId: toolCallIdOf(data),\n error: compact(`${name}${detail}`),\n }\n this.addStep(this.userActive, { kind: 'tool', step })\n return\n }\n // compaction & anything else: foldByUserBoundaries ignores them.\n }\n\n /**\n * A turn/start arrived after user-boundary turns were folded. Reconstruct\n * what a from-scratch turn-mode fold of the consumed prefix would hold:\n * every user input becomes a pending input (in arrival order), and the\n * pre-turn outputs / tool steps are dropped \u2014 turn-mode replay attaches\n * nothing while no turn is open. Statistics reset accordingly.\n */\n private flipToTurnMode(): void {\n const pending: Array<{ seq: number; text: string }> = []\n const take = (turn: TrailTurn): void => {\n for (const item of turn.items) {\n if (item.kind === 'input') {\n pending.push({ seq: item.seq, text: item.text })\n return\n }\n }\n }\n for (const turn of this.userClosed) take(turn)\n // Read the active builder directly: it may never have been snapshot()'d.\n if (this.userActive !== null && this.userActive.inputSeq !== null) {\n pending.push({ seq: this.userActive.inputSeq, text: this.userActive.input })\n }\n this.userClosed.length = 0\n this.userActive = null\n this.userActiveTurn = null\n this.userCounter = 0\n this.pendingUser.length = 0\n this.pendingUser.push(...pending)\n this.total = 0\n this.failed = 0\n this.byTool.clear()\n this.mode = 'turn'\n }\n}\n", "/**\n * locales.ts \u2014 zh/en dictionaries for the trail panel, following the\n * browser language (matching conversation-outline's i18n approach).\n */\n\nexport type Lang = 'zh' | 'en'\n\nexport function detectLang(): Lang {\n if (typeof navigator === 'undefined') return 'en'\n const raw = navigator.language ?? ''\n return raw.toLowerCase().startsWith('zh') ? 'zh' : 'en'\n}\n\nexport interface Dict {\n tabTitle: string\n turnsCount: (n: number) => string\n followLatest: string\n followPaused: string\n newUpdates: string\n filterAll: string\n filterFailed: string\n filterMore: string\n clearFilter: string\n emptyNoTurns: string\n emptyFiltered: string\n loadFailed: string\n loading: string\n retry: string\n stepsCount: (n: number) => string\n toolCalls: (n: number) => string\n input: string\n output: string\n context: string\n contextCount: (n: number) => string\n compaction: string\n /** Short compaction label for the row tag column. */\n compactionTag: string\n jumpProgress: (n: number) => string\n jumpDone: string\n jumpFailed: string\n jumpTrajectory: string\n previewExpand: string\n previewClose: string\n previewTitle: (label: string) => string\n copyTurn: string\n copied: string\n running: string\n error: string\n turnPrefix: (n: number) => string\n expandAll: string\n collapseAll: string\n locate: string\n stepsLabel: string\n filterCollapse: string\n showEarlierSteps: (n: number) => string\n}\n\nconst zh: Dict = {\n tabTitle: '\u8F6E\u6B21\u8F68\u8FF9',\n turnsCount: (n) => `${n} \u8F6E`,\n followLatest: '\u8DDF\u968F\u6700\u65B0',\n followPaused: '\u5DF2\u6682\u505C\u8DDF\u968F',\n newUpdates: '\u6709\u65B0\u66F4\u65B0 \u2193',\n filterAll: '\u5168\u90E8',\n filterFailed: '\u5931\u8D25',\n filterMore: '\u66F4\u591A',\n clearFilter: '\u6E05\u7A7A\u7B5B\u9009',\n emptyNoTurns: '\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u8F6E\u6B21',\n emptyFiltered: '\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u7684\u6B65\u9AA4',\n loadFailed: '\u4F1A\u8BDD\u6570\u636E\u52A0\u8F7D\u5931\u8D25',\n loading: '\u6B63\u5728\u52A0\u8F7D\u4F1A\u8BDD\u6570\u636E\u2026',\n retry: '\u91CD\u8BD5',\n stepsCount: (n) => `${n} \u6B65\u9AA4`,\n toolCalls: (n) => `${n} \u6B21\u8C03\u7528`,\n input: '\u8F93\u5165',\n output: '\u8F93\u51FA',\n context: '\u4E0A\u4E0B\u6587',\n contextCount: (n) => `${n} \u6761`,\n compaction: '\u538B\u7F29\u68C0\u67E5\u70B9',\n compactionTag: '\u538B\u7F29',\n jumpProgress: (n) => `\u6B63\u5728\u5B9A\u4F4D\u66F4\u65E9\u7684\u5BF9\u8BDD\uFF08\u5DF2\u52A0\u8F7D ${n} \u9875\uFF09\u2026`,\n jumpDone: '\u5DF2\u5B9A\u4F4D',\n jumpFailed: '\u65E0\u6CD5\u5B9A\u4F4D\u8BE5\u6D88\u606F',\n jumpTrajectory: '\u5DF2\u5728\u8F68\u8FF9\u89C6\u56FE\u5B9A\u4F4D',\n previewExpand: '\u5C55\u5F00',\n previewClose: '\u5173\u95ED',\n previewTitle: (label) => label,\n copyTurn: '\u590D\u5236\u672C\u8F6E',\n copied: '\u5DF2\u590D\u5236',\n running: '\u8FDB\u884C\u4E2D',\n error: '\u9519\u8BEF',\n turnPrefix: (n) => `\u8F6E\u6B21 ${n}`,\n expandAll: '\u5C55\u5F00\u5168\u90E8',\n collapseAll: '\u5168\u90E8\u6298\u53E0',\n locate: '\u5B9A\u4F4D',\n stepsLabel: '\u5DE5\u5177\u6B65\u9AA4\u6570',\n filterCollapse: '\u6536\u8D77',\n showEarlierSteps: (n) => `\u663E\u793A\u66F4\u65E9 ${n} \u6761`,\n}\n\nconst en: Dict = {\n tabTitle: 'Turn Outline',\n turnsCount: (n) => `${n} turns`,\n followLatest: 'Follow latest',\n followPaused: 'Follow paused',\n newUpdates: 'New updates \u2193',\n filterAll: 'All',\n filterFailed: 'Failed',\n filterMore: 'More',\n clearFilter: 'Clear filter',\n emptyNoTurns: 'No turns in this session yet',\n emptyFiltered: 'No steps match the filter',\n loadFailed: 'Failed to load session data',\n loading: 'Loading session data\u2026',\n retry: 'Retry',\n stepsCount: (n) => `${n} steps`,\n toolCalls: (n) => `${n} calls`,\n input: 'Input',\n output: 'Output',\n context: 'Context',\n contextCount: (n) => `${n} items`,\n compaction: 'Compaction checkpoint',\n compactionTag: 'Fold',\n jumpProgress: (n) => `Locating earlier conversation (loaded ${n} page${n === 1 ? '' : 's'})\u2026`,\n jumpDone: 'Located',\n jumpFailed: 'Could not locate that message',\n jumpTrajectory: 'Located in the trajectory view',\n previewExpand: 'Expand',\n previewClose: 'Close',\n previewTitle: (label) => label,\n copyTurn: 'Copy turn',\n copied: 'Copied',\n running: 'running',\n error: 'error',\n turnPrefix: (n) => `Turn ${n}`,\n expandAll: 'Expand all',\n collapseAll: 'Collapse all',\n locate: 'Locate',\n stepsLabel: 'tool steps',\n filterCollapse: 'Collapse',\n showEarlierSteps: (n) => `Show ${n} earlier`,\n}\n\nconst dicts: Record<Lang, Dict> = { zh, en }\n\nexport function t(lang: Lang, key: keyof Dict): string | number | ((...args: never[]) => unknown) {\n return dicts[lang][key]\n}\n", "/**\n * useTrail.ts \u2014 subscription-driven session-event hook for the trail panel.\n *\n * Refreshing strategy (v0.2+): no timer polling. Pull once on activation,\n * then subscribe to the client `sessions` binding snapshot; any snapshot\n * change (running flip, new steps, older pages) re-pulls the delta. The tab's\n * `visible` gate pauses the subscription entirely.\n *\n * Incremental folding (v0.2.1): incoming events are pushed ONCE into a\n * TrailFolder instead of re-folding the whole accumulated array on every\n * pull. Closed turns keep their object identity across snapshots, so\n * memoized row components skip re-rendering; each throttled pull costs\n * O(delta) folding + O(page) appends instead of a full O(n) re-fold.\n * Duplicate / re-sent pages are dropped by the folder's seq watermark.\n *\n * One pull drains at most MAX_PULL_PAGES pages; if pages remain pending\n * (first open of a huge cold session), the throttled path schedules a\n * continuation pull so the trail is never silently truncated. The chain is\n * guarded: it stops when the tab hides, the session/generation changes, or a\n * pull makes zero seq progress (stalled / looping transport).\n */\nimport * as React from 'react'\nimport type { SessionEventLike } from '../trail-model'\nimport { TrailFolder, type TrailStats, type TrailTurn } from '../trail-model'\n\nexport interface FetchEventsResult {\n events: SessionEventLike[]\n /** True when the server held more events back (page); re-pull to drain. */\n hasMore?: boolean\n}\n\nexport type FetchEvents = (sessionId: string, afterSeq: number) => Promise<FetchEventsResult>\n\nexport interface SessionsServiceLike {\n binding(sessionId: string): {\n session?: SessionFaceLike\n } | undefined\n}\n\nexport interface SessionFaceLike {\n subscribe(listener: () => void): () => void\n getSnapshot(): { running?: boolean } | null\n}\n\nexport type TrailStatus = 'idle' | 'loading' | 'ready' | 'error'\n\nexport interface TrailState {\n turns: TrailTurn[]\n stats: TrailStats\n status: TrailStatus\n error: string | null\n refresh: () => void\n}\n\n/** Cap on drained pages per pull (each page \u2264 PAGE_SIZE events). */\nexport const MAX_PULL_PAGES = 64\n\n/** Default transport: POST the /turn-outline/api/events route. */\nexport function fetchTrailEvents(sessionId: string, afterSeq: number, base?: string): Promise<FetchEventsResult> {\n const url = new URL('/turn-outline/api/events', base ?? window.location.origin)\n return fetch(url.toString(), {\n method: 'POST',\n headers: { 'content-type': 'application/json', accept: 'application/json' },\n body: JSON.stringify({ sessionId, afterSeq }),\n }).then(async (response) => {\n const data = (await response.json()) as { events?: SessionEventLike[]; hasMore?: boolean; error?: string }\n if (!response.ok || data === null || typeof data !== 'object' || !Array.isArray(data.events)) {\n throw new Error(data !== null && typeof data === 'object' && typeof data.error === 'string'\n ? data.error\n : `HTTP ${response.status}`)\n }\n return { events: data.events, hasMore: data.hasMore === true }\n })\n}\n\nconst EMPTY: TrailTurn[] = []\nconst EMPTY_STATS: TrailStats = { total: 0, failed: 0, tools: [] }\n\nexport function useTrail(\n sessionId: string,\n visible: boolean,\n fetchEvents: FetchEvents,\n sessionsService?: SessionsServiceLike,\n): TrailState {\n const [turns, setTurns] = React.useState<TrailTurn[]>(EMPTY)\n const [stats, setStats] = React.useState<TrailStats>(EMPTY_STATS)\n const [status, setStatus] = React.useState<TrailStatus>('idle')\n const [error, setError] = React.useState<string | null>(null)\n const folderRef = React.useRef<TrailFolder | null>(null)\n const seqRef = React.useRef(0)\n const sessionIdRef = React.useRef(sessionId)\n const fetchRef = React.useRef(fetchEvents)\n const requestGen = React.useRef(0)\n // Throttle the subscription-driven refresh. During live generation DSH emits\n // many events; a network round-trip + fold + re-render on EVERY event\n // hammers the (shared) main thread. Coalesce a burst into one update.\n const lastPullAt = React.useRef(0)\n const throttleTimer = React.useRef<number | null>(null)\n // Mirrors for async callbacks: pull() must know the current visibility and\n // reach the throttled scheduler (defined below it) without re-creating.\n const visibleRef = React.useRef(visible)\n const schedulePullRef = React.useRef<(() => void) | null>(null)\n\n React.useEffect(() => { fetchRef.current = fetchEvents }, [fetchEvents])\n React.useEffect(() => { visibleRef.current = visible }, [visible])\n\n // Cancel any pending throttled pull on teardown.\n React.useEffect(() => () => {\n if (throttleTimer.current !== null) window.clearTimeout(throttleTimer.current)\n schedulePullRef.current = null\n }, [])\n\n const pull = React.useCallback(async (): Promise<void> => {\n const generation = ++requestGen.current\n const sid = sessionIdRef.current\n const seqBefore = seqRef.current\n // True when the drain loop hit MAX_PULL_PAGES with pages still pending.\n let drainCapped = false\n try {\n // Drain possibly-paginated pages until the server log is current.\n for (let page = 0; page < MAX_PULL_PAGES; page += 1) {\n const { events, hasMore } = await fetchRef.current(sid, seqRef.current)\n if (generation !== requestGen.current || sid !== sessionIdRef.current) return\n if (events.length > 0) {\n let folder = folderRef.current\n if (folder === null) folder = folderRef.current = new TrailFolder()\n let anyPushed = false\n for (const event of events) {\n const before = folder.latestSeq\n folder.push(event)\n if (folder.latestSeq !== before) anyPushed = true\n }\n if (anyPushed) {\n const last = events[events.length - 1]\n if (typeof last.seq === 'number' && last.seq > seqRef.current) seqRef.current = last.seq\n setTurns(folder.snapshot())\n setStats(folder.stats())\n }\n }\n if (hasMore !== true || events.length === 0) {\n drainCapped = false\n break\n }\n drainCapped = page === MAX_PULL_PAGES - 1\n }\n setError(null)\n setStatus('ready')\n // A capped drain with pages pending (first open of a huge cold session)\n // must not truncate silently: keep draining through the throttled path.\n // Guards stop a misbehaving transport from looping forever: the tab is\n // still visible, the pull is still current, and THIS pull made real seq\n // progress (a stalled transport yields none, so scheduling stops).\n if (\n drainCapped\n && seqRef.current !== seqBefore\n && generation === requestGen.current\n && sid === sessionIdRef.current\n && visibleRef.current\n && schedulePullRef.current !== null\n ) {\n schedulePullRef.current()\n }\n } catch (reason) {\n if (generation !== requestGen.current) return\n setError(reason instanceof Error ? reason.message : String(reason))\n setStatus((current) => (current === 'ready' ? current : 'error'))\n }\n }, [])\n\n const refresh = React.useCallback(() => { void pull() }, [pull])\n\n /**\n * Throttled pull for the subscription path: at most one run per MIN_GAP ms,\n * trailing catch-up so the last event of a burst is never dropped.\n */\n const schedulePull = React.useCallback((): void => {\n const MIN_GAP = 120\n const now = Date.now()\n const since = now - lastPullAt.current\n if (since >= MIN_GAP) {\n lastPullAt.current = now\n void pull()\n return\n }\n if (throttleTimer.current !== null) return\n throttleTimer.current = window.setTimeout(() => {\n throttleTimer.current = null\n lastPullAt.current = Date.now()\n void pull()\n }, MIN_GAP - since)\n }, [pull])\n // Expose the scheduler to pull()'s cap-continuation (declared above it).\n React.useEffect(() => {\n schedulePullRef.current = schedulePull\n }, [schedulePull])\n\n // Session switch: reset the accumulator.\n React.useEffect(() => {\n sessionIdRef.current = sessionId\n folderRef.current = null\n seqRef.current = 0\n setTurns(EMPTY)\n setStats(EMPTY_STATS)\n setError(null)\n setStatus(sessionId.length > 0 ? 'loading' : 'idle')\n }, [sessionId])\n\n // Activation + subscription-driven refresh (paused while invisible).\n React.useEffect(() => {\n if (sessionId.length === 0 || !visible) return undefined\n void pull()\n const binding = sessionsService?.binding(sessionId)\n const session = binding !== undefined && binding.session !== undefined ? binding.session : undefined\n if (session === undefined || typeof session.subscribe !== 'function') return undefined\n return session.subscribe(() => { schedulePull() })\n }, [sessionId, visible, pull, schedulePull, sessionsService])\n\n return { turns, stats, status, error, refresh }\n}\n", "/**\n * trail-jump.ts \u2014 structured jump-back into the main conversation, via the\n * client `sessions` service (no DOM text matching, no \"load older\" button\n * hacking).\n *\n * chat view: session.loadOlder() loop \u2192 snapshot.chat.nodes (Map,\n * keyed by node key, each with anchorSeq) \u2192 find the node\n * nearest to the target seq \u2192 `[data-chat-anchor-key]`\n * row \u2192 scrollIntoView + flash.\n * trajectory view: snapshot.views.get('trajectory') \u2192 { eventNodes,\n * requests } mapped to `[data-trajectory-row-key]` rows;\n * collapsed summaries are expanded first.\n *\n * All dependencies (session face, document) are injected so the module stays\n * unit-testable.\n */\n\n/** A fold item the user clicked \u2014 anything with a seq. */\nexport interface JumpTarget {\n seq: number\n kind: 'user' | 'assistant' | 'tool' | 'error' | 'compaction' | 'context' | 'input' | 'output'\n callId?: string\n turn?: number\n step?: number\n}\n\nexport interface ChatNodeLike {\n key: string\n anchorSeq: number\n visibility?: string\n kind?: string\n turn?: number\n step?: number\n callId?: string\n}\n\nexport interface SessionSnapshotLike {\n hasMore?: boolean\n chat?: {\n nodes?: {\n values(): Iterable<ChatNodeLike>\n }\n }\n views?: {\n get(kind: string): unknown\n }\n}\n\nexport interface SessionFace {\n loadOlder(): Promise<unknown>\n getSnapshot(): SessionSnapshotLike\n}\n\nexport interface DocumentLike {\n querySelectorAll(selector: string): ArrayLike<{ dataset: Record<string, string>; click?(): void; scrollIntoView(opts?: unknown): void }>\n querySelector(selector: string): { scrollIntoView(opts?: unknown): void; dataset?: Record<string, string>; classList?: { add(name: string): void } } | null\n}\n\nexport interface JumpOptions {\n document: DocumentLike\n guard?: number\n onProgress?: (pages: number) => void\n}\n\nexport type JumpOutcome =\n | { ok: true; mode: 'chat' | 'trajectory' }\n | { ok: false; error: string }\n\n/** Whether the chat view lists a seq inside its current render window. */\nexport function hasSeqInWindow(snapshot: SessionSnapshotLike, seq: number): boolean {\n const store = snapshot.chat?.nodes\n if (store === undefined || typeof store.values !== 'function') return false\n let min = Number.POSITIVE_INFINITY\n let max = Number.NEGATIVE_INFINITY\n for (const node of store.values()) {\n if (node === null || typeof node !== 'object' || typeof node.anchorSeq !== 'number') continue\n if (node.anchorSeq < min) min = node.anchorSeq\n if (node.anchorSeq > max) max = node.anchorSeq\n }\n return min <= seq && seq <= max\n}\n\n/**\n * Find the visible node nearest to a seq (exact \u2192 next \u2192 prev). Hidden\n * nodes (e.g. an injected system-reminder) are skipped, so a hidden input\n * falls back to the next visible row naturally.\n */\nexport function findNodeForSeq(snapshot: SessionSnapshotLike, seq: number): ChatNodeLike | null {\n const store = snapshot.chat?.nodes\n if (store === undefined || typeof store.values !== 'function') return null\n let exact: ChatNodeLike | null = null\n let next: ChatNodeLike | null = null\n let prev: ChatNodeLike | null = null\n for (const node of store.values()) {\n if (node === null || typeof node !== 'object' || node.visibility === 'hidden') continue\n const anchor = node.anchorSeq\n if (typeof anchor !== 'number') continue\n if (Math.abs(anchor - seq) < 0.5) {\n if (exact === null) exact = node\n continue\n }\n if (anchor >= seq && (next === null || anchor < next.anchorSeq)) next = node\n if (anchor <= seq && (prev === null || anchor > prev.anchorSeq)) prev = node\n }\n return exact !== null ? exact : next !== null ? next : prev\n}\n\nfunction isTrajectoryViewActive(document: DocumentLike): boolean {\n return document.querySelector('[data-trajectory-scroll]') !== null\n}\n\n/** trajectory view \u2192 a stable `data-trajectory-row-key` value (or null). */\nexport function trajectoryRecordIdFor(item: JumpTarget, snapshot: SessionSnapshotLike): string | null {\n const views = snapshot.views\n if (views === undefined || typeof views.get !== 'function') return null\n const trajectory = views.get('trajectory')\n if (trajectory === null || typeof trajectory !== 'object') return null\n const view = trajectory as { eventNodes?: unknown[]; requests?: unknown[] }\n const nodes = Array.isArray(view.eventNodes) ? view.eventNodes : []\n const requests = Array.isArray(view.requests) ? view.requests : []\n const nodeWithKind = (kinds: string[], seq: number): { seq?: number; kind?: string; turn?: number; step?: number; callId?: string } | undefined => {\n const matches = nodes.filter((node): node is Record<string, unknown> => (\n node !== null && typeof node === 'object'\n && kinds.includes(String((node as { kind?: unknown }).kind))\n && typeof (node as { seq?: unknown }).seq === 'number'\n ))\n return (matches.find((node) => node.seq === seq)\n ?? matches.filter((node) => (node.seq as number) >= seq).sort((a, b) => (a.seq as number) - (b.seq as number))[0]) as\n { seq?: number; kind?: string; turn?: number; step?: number; callId?: string } | undefined\n }\n\n if (item.kind === 'user' || item.kind === 'context' || item.kind === 'input') {\n const target = nodeWithKind(['user', 'steering', 'context'], item.seq)\n if (target === undefined || typeof target.seq !== 'number') return null\n const rowKind = target.kind === 'steering' ? 'user' : target.kind\n return `${rowKind}\\u0000seq\\u0000${target.seq}`\n }\n if (item.kind === 'assistant' || item.kind === 'output') {\n const target = nodeWithKind(['assistant'], item.seq)\n if (target === undefined || typeof target.turn !== 'number' || typeof target.step !== 'number') return null\n return `assistant\\u0000${target.turn}\\u0000${target.step}`\n }\n if (item.kind === 'tool' || item.kind === 'error') {\n if (typeof item.callId === 'string' && item.callId.length > 0) return `tool\\u0000call\\u0000${item.callId}`\n const target = nodeWithKind(['tool-result'], item.seq)\n if (target === undefined || typeof target.callId !== 'string') return null\n return `tool\\u0000call\\u0000${target.callId}`\n }\n if (item.kind === 'compaction') {\n const compactions: Array<{ purpose?: unknown; startSeq?: number; resultSeq?: number }> = []\n for (const request of requests) {\n if (request === null || typeof request !== 'object') continue\n const record = request as { purpose?: unknown; startSeq?: unknown; resultSeq?: unknown }\n if (record.purpose === 'compaction' && typeof record.startSeq === 'number') {\n compactions.push({ purpose: record.purpose, startSeq: record.startSeq, resultSeq: typeof record.resultSeq === 'number' ? record.resultSeq : undefined })\n }\n }\n const target = compactions.find((request) => request.resultSeq === item.seq)\n ?? compactions.filter((request) => (request.resultSeq ?? -1) >= item.seq)\n .sort((a, b) => (a.resultSeq ?? -1) - (b.resultSeq ?? -1))[0]\n if (target === undefined) return null\n return `compacted\\u0000seq\\u0000${target.startSeq}`\n }\n return null\n}\n\nfunction findTrajectoryRow(document: DocumentLike, key: string): { dataset: Record<string, string>; click?(): void; scrollIntoView(opts?: unknown): void } | null {\n const encoded = encodeURIComponent(key)\n const rows = document.querySelectorAll('tr[data-trajectory-row-key]')\n for (let index = 0; index < rows.length; index += 1) {\n const row = rows[index]\n const value = row.dataset !== undefined ? row.dataset.trajectoryRowKey : undefined\n if (value === encoded || value === key) return row\n }\n return null\n}\n\nfunction findTrajectoryRowBySeq(document: DocumentLike, item: JumpTarget): { dataset: Record<string, string>; click?(): void; scrollIntoView(opts?: unknown): void } | null {\n const rows = document.querySelectorAll('tr[data-trajectory-row-key]')\n for (let index = 0; index < rows.length; index += 1) {\n const row = rows[index]\n const raw = row.dataset.trajectoryRowKey ?? ''\n let id = ''\n try {\n id = decodeURIComponent(raw)\n } catch {\n continue\n }\n if (id.includes(`\\u0000seq\\u0000${item.seq}`)) return row\n if (typeof item.callId === 'string' && id.includes(`\\u0000call\\u0000${item.callId}`)) return row\n }\n return null\n}\n\nfunction expandCollapsedSummaries(document: DocumentLike): void {\n const rows = document.querySelectorAll('tr[data-collapsed-summary]')\n for (let index = 0; index < rows.length; index += 1) {\n const row = rows[index]\n if (typeof row.click === 'function') row.click()\n }\n}\n\nfunction waitForNextFrame(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(() => resolve())\n } else {\n resolve()\n }\n })\n}\n\n/**\n * Jump to the nearest rendered row for a fold item. Returns the outcome and\n * reports paging progress through onProgress.\n */\nexport async function jumpToItem(\n session: SessionFace,\n item: JumpTarget,\n options: JumpOptions,\n): Promise<JumpOutcome> {\n const guard = Number.isFinite(Number(options.guard)) && (options.guard ?? 0) > 0\n ? Math.min(500, Math.max(1, Math.floor(options.guard ?? 200)))\n : 200\n const document = options.document\n\n try {\n let snapshot = session.getSnapshot()\n const trajectoryActive = isTrajectoryViewActive(document)\n\n if (trajectoryActive) {\n let key = trajectoryRecordIdFor(item, snapshot)\n let row = key === null ? null : findTrajectoryRow(document, key)\n let pages = 0\n if (row === null && snapshot.hasMore === true) {\n while (snapshot.hasMore === true && pages < guard) {\n await session.loadOlder()\n snapshot = session.getSnapshot()\n pages += 1\n options.onProgress?.(pages)\n }\n key = trajectoryRecordIdFor(item, snapshot)\n row = key === null ? null : findTrajectoryRow(document, key)\n }\n if (row === null) {\n expandCollapsedSummaries(document)\n await waitForNextFrame()\n row = key === null ? null : findTrajectoryRow(document, key)\n }\n if (row === null) {\n row = findTrajectoryRowBySeq(document, item)\n }\n if (row === null) return { ok: false, error: 'trajectory-row-not-rendered' }\n row.scrollIntoView({ behavior: 'smooth', block: 'center' })\n return { ok: true, mode: 'trajectory' }\n }\n\n // Chat view: load older pages until the target is in the window.\n let pages = 0\n while (snapshot.hasMore === true && !hasSeqInWindow(snapshot, item.seq) && pages < guard) {\n await session.loadOlder()\n snapshot = session.getSnapshot()\n pages += 1\n options.onProgress?.(pages)\n }\n const node = findNodeForSeq(snapshot, item.seq)\n if (node === null) return { ok: false, error: 'seq-not-rendered' }\n // After loadOlder() the session data updates immediately, but the chat\n // DOM commits a tick later \u2014 give the freshly loaded row a few frames to\n // mount before declaring failure. (Without this the FIRST attempt used to\n // report \"cannot locate\" and the user had to click a second time.)\n const selector = `[data-chat-anchor-key=\"${cssEscape(node.key)}\"]`\n let row = document.querySelector(selector)\n let retries = 0\n while (row === null && retries < 6) {\n await waitForNextFrame()\n row = document.querySelector(selector)\n retries += 1\n }\n if (row === null) return { ok: false, error: 'row-not-in-chat-view' }\n row.scrollIntoView({ behavior: 'smooth', block: 'center' })\n if (row.classList !== undefined && typeof row.classList.add === 'function') {\n row.classList.add('ttl-flash')\n }\n return { ok: true, mode: 'chat' }\n } catch (reason) {\n return { ok: false, error: reason instanceof Error ? reason.message : String(reason) }\n }\n}\n\nfunction cssEscape(value: string): string {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') return CSS.escape(value)\n return value.replace(/[\"\\\\]/g, '\\\\$&')\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,IAAAA,SAAuB;;;ACQhB,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGxB,IAAAC,SAAuB;;;ACoDhB,IAAM,cAAc;AAGpB,SAAS,QAAQ,MAA8B;AACpD,QAAM,MAAmB,CAAC;AAC1B,aAAW,QAAQ,KAAK,OAAO;AAC7B,QAAI,KAAK,SAAS,OAAQ,KAAI,KAAK,KAAK,IAAI;AAAA,EAC9C;AACA,SAAO;AACT;AAEO,SAAS,QAAQ,OAAe,MAAM,aAAqB;AAChE,QAAM,UAAU,MAAM,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAChD,MAAI,QAAQ,UAAU,IAAK,QAAO;AAClC,SAAO,GAAG,QAAQ,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,CAAC;AAC/C;AAGO,SAAS,eAAe,QAAyB;AACtD,MAAI,OAAO,WAAW,SAAU,QAAO,QAAQ,MAAM;AACrD,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,aAAW,SAAS,QAAQ;AAC1B,QACE,UAAU,QAAQ,OAAO,UAAU,YAC/B,MAA6B,SAAS,UACvC,OAAQ,MAA6B,SAAS,YAC7C,MAA2B,KAAK,KAAK,EAAE,SAAS,GACpD;AACA,aAAO,QAAS,MAA2B,IAAI;AAAA,IACjD;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,cAAc,QAAyB;AACrD,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,MAAI,OAAO;AACX,aAAW,SAAS,QAAQ;AAC1B,QACE,UAAU,QAAQ,OAAO,UAAU,YAC/B,MAA6B,SAAS,UACvC,OAAQ,MAA6B,SAAS,YAC7C,MAA2B,KAAK,KAAK,EAAE,SAAS,GACpD;AACA,aAAQ,MAA2B;AAAA,IACrC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,OAAkD;AACpE,SAAO,MAAM,SAAS,QAAQ,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,CAAC;AAC/E;AAGA,SAAS,aAAa,OAAyB,MAAmD;AAChG,QAAM,UAAU,KAAK,YAAY,QAAQ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,CAAC;AAC5F,QAAM,SAAS,YAAY,QAAQ,OAAQ,QAAiC,WAAW,WAClF,QAA2C,SAC3C,KAAK,WAAW,QAAQ,OAAO,KAAK,WAAW,WAC7C,KAAK,SACN;AACN,MAAI,WAAW,QAAQ,OAAO,OAAO,SAAS,SAAU,QAAO,OAAO;AACtE,SAAO;AACT;AAEO,SAAS,aAAa,MAA8C;AACzE,MAAI,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,SAAS,EAAG,QAAO,KAAK;AAC3E,QAAM,UAAU,KAAK,YAAY,QAAQ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,CAAC;AAC5F,QAAM,SAAS,YAAY,QAAQ,OAAQ,QAAiC,WAAW,WAClF,QAA6C,SAC9C;AACJ,MAAI,WAAW,QAAQ,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,GAAG;AACpF,WAAO,OAAO;AAAA,EAChB;AACA,SAAO;AACT;AAoBA,SAAS,WAAW,MAA8B;AAChD,SAAO;AAAA,IACL;AAAA,IACA,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,OAAO,CAAC;AAAA,IACR,OAAO,CAAC;AAAA,IACR,UAAU,CAAC;AAAA,IACX,UAAU;AAAA,EACZ;AACF;AAEA,SAAS,WAAW,SAAkB,MAAsB;AAC1D,UAAQ,MAAM,KAAK,IAAI;AACvB,MAAI,KAAK,SAAS,QAAQ;AACxB,YAAQ,MAAM,KAAK,KAAK,IAAI;AAC5B,QAAI,KAAK,KAAK,SAAS,QAAS,SAAQ,WAAW;AAAA,EACrD;AACF;AAEA,SAAS,cAAc,SAAkB,OAAe,aAAgC;AACtF,QAAM,YAAY,QAAQ,aAAa,OAAO,QAAQ,WAAW;AACjE,QAAM,SAA8B,QAAQ,WACxC,UACA,QAAQ,WAAW,OAChB,QAAQ,MAAM,SAAS,IAAI,YAAY,YACxC;AACN,SAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ;AAAA,IACd;AAAA,IACA,cAAc,QAAQ,QAAQ,KAAK;AAAA,IACnC,eAAe,QAAQ,QAAQ,MAAM;AAAA,IACrC;AAAA,IACA,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,IAC1E,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,IACpE,OAAO,QAAQ;AAAA,IACf,YAAY,QAAQ,MAAM;AAAA,IAC1B,cAAc,QAAQ,SAAS;AAAA,IAC/B,UAAU,QAAQ;AAAA,EACpB;AACF;AAGA,SAAS,aAAa,OAA2B;AAC/C,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,OAAQ,QAAO,KAAK,KAAK;AAC3C,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AAgQA,SAAS,eAAe,SAAkB,OAA0B;AAClE,QAAM,cAAc,QAAQ,aAAa,OAAO,QAAQ,WAAW,QAAQ,YAAY;AACvF,SAAO,cAAc,SAAS,OAAO,WAAW;AAClD;AA0BO,IAAM,cAAN,MAAkB;AAAA,EAAlB;AACL,SAAQ,OAAoC;AAG5C;AAAA,SAAiB,WAAW,oBAAI,IAAqB;AACrD,SAAiB,QAAkB,CAAC;AACpC,SAAiB,SAAkC,CAAC;AACpD,SAAiB,cAAoD,CAAC;AACtE,SAAQ,UAA0B;AAGlC;AAAA,SAAiB,aAA0B,CAAC;AAC5C,SAAQ,aAA6B;AACrC,SAAQ,iBAAmC;AAC3C,SAAQ,cAAc;AAEtB,SAAQ,UAAU;AAClB,SAAQ,QAAQ;AAChB,SAAQ,SAAS;AACjB,SAAiB,SAAS,oBAAI,IAAoB;AAAA;AAAA;AAAA,EAGlD,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,OAA+B;AAClC,QAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,MAAM,SAAS,SAAU;AACnF,UAAM,MAAM,OAAO,MAAM,QAAQ,WAAW,MAAM,MAAM,OAAO;AAC/D,QAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,KAAK,QAAS;AAClD,UAAM,OAAO,WAAW,KAAK;AAC7B,UAAM,OAAO,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAC3D,SAAK,UAAU;AAEf,QAAI,KAAK,SAAS,QAAQ;AACxB,WAAK,eAAe,MAAM,MAAM,MAAM,KAAK,IAAI;AAC/C;AAAA,IACF;AACA,QAAI,KAAK,SAAS,QAAQ;AACxB,UAAI,MAAM,SAAS,gBAAgB,OAAO,KAAK,SAAS,UAAU;AAChE,aAAK,eAAe;AACpB,aAAK,eAAe,MAAM,MAAM,MAAM,KAAK,IAAI;AAAA,MACjD,OAAO;AACL,aAAK,eAAe,MAAM,MAAM,MAAM,KAAK,IAAI;AAAA,MACjD;AACA;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,gBAAgB,OAAO,KAAK,SAAS,UAAU;AAChE,WAAK,OAAO;AACZ,WAAK,eAAe,MAAM,MAAM,MAAM,KAAK,IAAI;AAC/C;AAAA,IACF;AACA,QAAI,MAAM,SAAS,kBAAkB,aAAa,OAAO,IAAI,MAAM,QAAQ;AACzE,WAAK,OAAO;AACZ,WAAK,eAAe,MAAM,MAAM,MAAM,KAAK,IAAI;AAC/C;AAAA,IACF;AAAA,EAEF;AAAA;AAAA,EAGA,WAAwB;AACtB,QAAI,KAAK,SAAS,UAAW,QAAO,CAAC;AACrC,QAAI,KAAK,SAAS,QAAQ;AACxB,YAAMC,OAAM,KAAK,WAAW,MAAM;AAClC,UAAI,KAAK,eAAe,MAAM;AAC5B,YAAI,KAAK,mBAAmB,MAAM;AAChC,eAAK,iBAAiB,eAAe,KAAK,YAAY,KAAK,WAAW,MAAM;AAAA,QAC9E;AACA,QAAAA,KAAI,KAAK,KAAK,cAAc;AAAA,MAC9B;AACA,aAAOA;AAAA,IACT;AACA,UAAM,MAAmB,CAAC;AAC1B,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK,GAAG;AAC7C,YAAM,eAAe,KAAK,OAAO,CAAC;AAClC,UAAI,iBAAiB,MAAM;AACzB,YAAI,KAAK,YAAY;AACrB;AAAA,MACF;AACA,YAAM,UAAU,KAAK,SAAS,IAAI,KAAK,MAAM,CAAC,CAAC;AAC/C,UAAI,YAAY,OAAW;AAC3B,UAAI,KAAK,KAAK,gBAAgB,SAAS,CAAC,CAAC;AAAA,IAC3C;AAEA,QAAI,UAAU,KAAK,MAAM;AACzB,eAAW,WAAW,KAAK,aAAa;AACtC,YAAM,UAAU,WAAW,IAAI;AAC/B,cAAQ,QAAQ,QAAQ;AACxB,cAAQ,WAAW,QAAQ;AAC3B,iBAAW,SAAS,EAAE,MAAM,SAAS,KAAK,QAAQ,KAAK,MAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ,IAAI,EAAE,CAAC;AAC3G,UAAI,KAAK,cAAc,SAAS,SAAS,QAAQ,GAAG,CAAC;AACrD,iBAAW;AAAA,IACb;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,QAAoB;AAClB,UAAM,QAAQ,CAAC,GAAG,KAAK,OAAO,QAAQ,CAAC,EACpC,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAC1B,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,EAAE;AAC3C,WAAO,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,MAAM;AAAA,EACzD;AAAA;AAAA,EAIQ,UAAU,MAAuB;AACvC,QAAI,UAAU,KAAK,SAAS,IAAI,IAAI;AACpC,QAAI,YAAY,QAAW;AACzB,gBAAU,WAAW,IAAI;AACzB,cAAQ,WAAW,KAAK,MAAM;AAC9B,WAAK,SAAS,IAAI,MAAM,OAAO;AAC/B,WAAK,MAAM,KAAK,IAAI;AACpB,WAAK,OAAO,KAAK,IAAI;AAAA,IACvB;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,UAAU,SAAwB;AACxC,QAAI,KAAK,SAAS,QAAQ;AACxB,UAAI,YAAY,KAAK,WAAY,MAAK,iBAAiB;AACvD;AAAA,IACF;AACA,UAAM,WAAW,QAAQ;AACzB,QAAI,aAAa,UAAa,YAAY,KAAK,WAAW,KAAK,OAAO,QAAQ;AAC5E,WAAK,OAAO,QAAQ,IAAI;AAAA,IAC1B;AAAA,EACF;AAAA,EAEQ,QAAQ,SAAkB,MAAyC;AACzE,eAAW,SAAS,IAAI;AACxB,SAAK,SAAS;AACd,QAAI,KAAK,KAAK,SAAS,QAAS,MAAK,UAAU;AAAA,QAC1C,MAAK,OAAO,IAAI,KAAK,KAAK,OAAO,KAAK,OAAO,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC/E,SAAK,UAAU,OAAO;AAAA,EACxB;AAAA,EAEQ,gBAAgB,SAAkB,UAA6B;AACrE,UAAM,cAAc,QAAQ,aAAa,OAAO,QAAQ,WAAY,QAAQ,YAAY,aAAa,QAAQ,KAAK;AAClH,UAAM,eAAe,cAAc,SAAS,UAAU,WAAW;AACjE,SAAK,OAAO,QAAQ,IAAI;AACxB,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,MAAc,MAA+B,KAAa,MAAqB;AACpG,YAAQ,MAAM;AAAA,MACZ,KAAK,cAAc;AACjB,cAAM,MAAM,KAAK;AACjB,YAAI,OAAO,QAAQ,SAAU;AAC7B,cAAM,UAAU,KAAK,UAAU,GAAG;AAClC,YAAI,QAAQ,aAAa,KAAM,SAAQ,WAAW;AAClD,YAAI,QAAQ,cAAc,OAAW,SAAQ,YAAY;AACzD,mBAAW,WAAW,KAAK,YAAY,OAAO,CAAC,GAAG;AAChD,kBAAQ,QAAQ,QAAQ;AACxB,kBAAQ,WAAW,QAAQ;AAC3B,kBAAQ,MAAM,QAAQ,EAAE,MAAM,SAAS,KAAK,QAAQ,KAAK,MAAM,QAAQ,MAAM,SAAS,QAAQ,QAAQ,IAAI,EAAE,CAAC;AAAA,QAC/G;AACA,aAAK,UAAU;AACf,aAAK,UAAU,OAAO;AACtB;AAAA,MACF;AAAA,MACA,KAAK,YAAY;AACf,cAAM,MAAM,KAAK;AACjB,cAAM,SAAS,OAAO,QAAQ,WAAW,KAAK,UAAU,GAAG,IAAI,KAAK;AACpE,YAAI,WAAW,MAAM;AACnB,iBAAO,SAAS;AAChB,iBAAO,UAAU;AACjB,eAAK,UAAU;AACf,eAAK,UAAU,MAAM;AAAA,QACvB;AACA;AAAA,MACF;AAAA,MACA,KAAK,gBAAgB;AACnB,cAAM,UAAU,eAAe,KAAK,OAAO;AAC3C,YAAI,QAAQ,WAAW,EAAG;AAC1B,cAAM,OAAO,aAAa,EAAE,MAAM,KAAK,KAAK,GAAG,IAAI;AACnD,cAAM,OAAO,OAAO,KAAK,YAAY,WACjC,KAAK,UACJ,OAAO,KAAK,YAAY,YAAY,KAAK,YAAY,QACnD,OAAQ,KAAK,QAAkC,YAAY,WAC3D,KAAK,QAAgC,UACtC;AACN,YAAI,SAAS,QAAQ;AACnB,cAAI,KAAK,YAAY,MAAM;AACzB,iBAAK,YAAY,KAAK,EAAE,KAAK,KAAK,CAAC;AAAA,UACrC,OAAO;AACL,iBAAK,QAAQ,QAAQ;AACrB,iBAAK,QAAQ,WAAW;AACxB,uBAAW,KAAK,SAAS,EAAE,MAAM,SAAS,KAAK,MAAM,QAAQ,CAAC;AAC9D,iBAAK,UAAU,KAAK,OAAO;AAAA,UAC7B;AAAA,QACF,WAAW,KAAK,YAAY,MAAM;AAChC,eAAK,QAAQ,SAAS,KAAK,EAAE,KAAK,MAAM,QAAQ,CAAC;AACjD,eAAK,UAAU,KAAK,OAAO;AAAA,QAC7B;AACA;AAAA,MACF;AAAA,MACA,KAAK,qBAAqB;AACxB,cAAM,UAAU,KAAK,YAAY,QAAQ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,CAAC;AAC5F,cAAM,UAAU,OAAQ,QAAkC,YAAY,cACjE,QAAiC,UAClC,KAAK;AACT,cAAM,OAAO,cAAc,OAAO;AAClC,YAAI,KAAK,WAAW,EAAG;AACvB,YAAI,KAAK,YAAY,MAAM;AACzB,eAAK,QAAQ,SAAS;AACtB,eAAK,QAAQ,YAAY;AACzB,qBAAW,KAAK,SAAS,EAAE,MAAM,UAAU,KAAK,MAAM,SAAS,QAAQ,IAAI,EAAE,CAAC;AAC9E,eAAK,UAAU,KAAK,OAAO;AAAA,QAC7B;AACA;AAAA,MACF;AAAA,MACA,KAAK,aAAa;AAChB,YAAI,KAAK,YAAY,KAAM;AAC3B,cAAM,OAAO,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;AACjF,cAAM,OAAO,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,SAAS,IAAI,KAAK,YAAY;AAChG,cAAM,OAAkB;AAAA,UACtB,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,aAAa,QAAQ,IAAI;AAAA,UACzB,QAAQ,aAAa,IAAI;AAAA,UACzB,OAAO;AAAA,QACT;AACA,aAAK,QAAQ,KAAK,SAAS,EAAE,MAAM,QAAQ,KAAK,CAAC;AACjD;AAAA,MACF;AAAA,MACA,KAAK,eAAe;AAClB,cAAM,QAAS,KAAK,UAAU,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAIpF,YAAI,UAAU,KAAM;AACpB,YAAI,KAAK,YAAY,KAAM;AAC3B,cAAM,OAAO,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,SAAS,IAAI,MAAM,OAAO;AACpF,cAAM,SAAS,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,SAAS,IAAI,SAAM,MAAM,IAAI,KAAK;AAC9F,cAAM,OAAkB;AAAA,UACtB,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb,QAAQ,aAAa,IAAI;AAAA,UACzB,OAAO,QAAQ,GAAG,IAAI,GAAG,MAAM,EAAE;AAAA,QACnC;AACA,aAAK,QAAQ,KAAK,SAAS,EAAE,MAAM,QAAQ,KAAK,CAAC;AACjD;AAAA,MACF;AAAA,MACA,KAAK,sBAAsB;AACzB,cAAM,UAAU,eAAe,KAAK,OAAO;AAC3C,YAAI,QAAQ,WAAW,EAAG;AAC1B,YAAI,KAAK,YAAY,MAAM;AACzB,qBAAW,KAAK,SAAS,EAAE,MAAM,cAAc,KAAK,QAAQ,CAAC;AAC7D,eAAK,UAAU,KAAK,OAAO;AAAA,QAC7B;AACA;AAAA,MACF;AAAA,MACA;AAEE;AAAA,IACJ;AAAA,EACF;AAAA;AAAA,EAIQ,gBAAsB;AAC5B,QAAI,KAAK,eAAe,KAAM;AAC9B,SAAK,WAAW,KAAK,eAAe,KAAK,YAAY,KAAK,WAAW,CAAC;AACtE,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEQ,eAAe,MAAc,MAA+B,KAAa,MAAqB;AACpG,QAAI,SAAS,gBAAgB;AAC3B,YAAM,OAAO,aAAa,EAAE,MAAM,KAAK,KAAK,GAAG,IAAI;AACnD,UAAI,SAAS,OAAQ;AACrB,WAAK,cAAc;AACnB,YAAM,OAAO,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,eAAe,KAAK,OAAO;AAC1F,UAAI,KAAK,WAAW,EAAG;AACvB,YAAM,UAAU,WAAW,IAAI;AAC/B,cAAQ,WAAW;AACnB,cAAQ,YAAY;AACpB,cAAQ,QAAQ;AAChB,cAAQ,WAAW;AACnB,iBAAW,SAAS,EAAE,MAAM,SAAS,KAAK,MAAM,SAAS,QAAQ,IAAI,EAAE,CAAC;AACxE,WAAK,aAAa;AAClB,WAAK,iBAAiB;AACtB;AAAA,IACF;AACA,QAAI,KAAK,eAAe,KAAM;AAC9B,QAAI,SAAS,qBAAqB;AAChC,YAAM,UAAU,KAAK,YAAY,QAAQ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,CAAC;AAC5F,YAAM,OAAO,cAAe,QAAkC,WAAW,KAAK,OAAO;AACrF,UAAI,KAAK,WAAW,EAAG;AACvB,WAAK,WAAW,SAAS;AACzB,WAAK,WAAW,YAAY;AAC5B,iBAAW,KAAK,YAAY,EAAE,MAAM,UAAU,KAAK,MAAM,SAAS,QAAQ,IAAI,EAAE,CAAC;AACjF,WAAK,iBAAiB;AACtB;AAAA,IACF;AACA,QAAI,SAAS,aAAa;AACxB,YAAM,OAAO,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;AACjF,YAAM,OAAO,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,SAAS,IAAI,KAAK,YAAY;AAChG,YAAM,OAAkB;AAAA,QACtB,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,aAAa,QAAQ,IAAI;AAAA,QACzB,QAAQ,aAAa,IAAI;AAAA,QACzB,OAAO;AAAA,MACT;AACA,WAAK,QAAQ,KAAK,YAAY,EAAE,MAAM,QAAQ,KAAK,CAAC;AACpD;AAAA,IACF;AACA,QAAI,SAAS,eAAe;AAC1B,YAAM,QAAS,KAAK,UAAU,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAIpF,UAAI,UAAU,KAAM;AACpB,YAAM,OAAO,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,SAAS,IAAI,MAAM,OAAO;AACpF,YAAM,SAAS,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,SAAS,IAAI,SAAM,MAAM,IAAI,KAAK;AAC9F,YAAM,OAAkB;AAAA,QACtB,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,aAAa;AAAA,QACb,QAAQ,aAAa,IAAI;AAAA,QACzB,OAAO,QAAQ,GAAG,IAAI,GAAG,MAAM,EAAE;AAAA,MACnC;AACA,WAAK,QAAQ,KAAK,YAAY,EAAE,MAAM,QAAQ,KAAK,CAAC;AACpD;AAAA,IACF;AAAA,EAEF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAuB;AAC7B,UAAM,UAAgD,CAAC;AACvD,UAAM,OAAO,CAAC,SAA0B;AACtC,iBAAW,QAAQ,KAAK,OAAO;AAC7B,YAAI,KAAK,SAAS,SAAS;AACzB,kBAAQ,KAAK,EAAE,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,CAAC;AAC/C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,eAAW,QAAQ,KAAK,WAAY,MAAK,IAAI;AAE7C,QAAI,KAAK,eAAe,QAAQ,KAAK,WAAW,aAAa,MAAM;AACjE,cAAQ,KAAK,EAAE,KAAK,KAAK,WAAW,UAAU,MAAM,KAAK,WAAW,MAAM,CAAC;AAAA,IAC7E;AACA,SAAK,WAAW,SAAS;AACzB,SAAK,aAAa;AAClB,SAAK,iBAAiB;AACtB,SAAK,cAAc;AACnB,SAAK,YAAY,SAAS;AAC1B,SAAK,YAAY,KAAK,GAAG,OAAO;AAChC,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,SAAK,OAAO,MAAM;AAClB,SAAK,OAAO;AAAA,EACd;AACF;;;AC12BO,SAAS,aAAmB;AACjC,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,QAAM,MAAM,UAAU,YAAY;AAClC,SAAO,IAAI,YAAY,EAAE,WAAW,IAAI,IAAI,OAAO;AACrD;AA8CA,IAAM,KAAW;AAAA,EACf,UAAU;AAAA,EACV,YAAY,CAAC,MAAM,GAAG,CAAC;AAAA,EACvB,cAAc;AAAA,EACd,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,OAAO;AAAA,EACP,YAAY,CAAC,MAAM,GAAG,CAAC;AAAA,EACvB,WAAW,CAAC,MAAM,GAAG,CAAC;AAAA,EACtB,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,cAAc,CAAC,MAAM,GAAG,CAAC;AAAA,EACzB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,cAAc,CAAC,MAAM,kFAAiB,CAAC;AAAA,EACvC,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc,CAAC,UAAU;AAAA,EACzB,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,YAAY,CAAC,MAAM,gBAAM,CAAC;AAAA,EAC1B,WAAW;AAAA,EACX,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,kBAAkB,CAAC,MAAM,4BAAQ,CAAC;AACpC;AAEA,IAAM,KAAW;AAAA,EACf,UAAU;AAAA,EACV,YAAY,CAAC,MAAM,GAAG,CAAC;AAAA,EACvB,cAAc;AAAA,EACd,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,OAAO;AAAA,EACP,YAAY,CAAC,MAAM,GAAG,CAAC;AAAA,EACvB,WAAW,CAAC,MAAM,GAAG,CAAC;AAAA,EACtB,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,cAAc,CAAC,MAAM,GAAG,CAAC;AAAA,EACzB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,cAAc,CAAC,MAAM,yCAAyC,CAAC,QAAQ,MAAM,IAAI,KAAK,GAAG;AAAA,EACzF,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc,CAAC,UAAU;AAAA,EACzB,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,YAAY,CAAC,MAAM,QAAQ,CAAC;AAAA,EAC5B,WAAW;AAAA,EACX,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,kBAAkB,CAAC,MAAM,QAAQ,CAAC;AACpC;AAEA,IAAM,QAA4B,EAAE,IAAI,GAAG;AAEpC,SAAS,EAAE,MAAY,KAAoE;AAChG,SAAO,MAAM,IAAI,EAAE,GAAG;AACxB;;;AC9HA,YAAuB;AAkChB,IAAM,iBAAiB;AAGvB,SAAS,iBAAiB,WAAmB,UAAkB,MAA2C;AAC/G,QAAM,MAAM,IAAI,IAAI,4BAA4B,QAAQ,OAAO,SAAS,MAAM;AAC9E,SAAO,MAAM,IAAI,SAAS,GAAG;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oBAAoB,QAAQ,mBAAmB;AAAA,IAC1E,MAAM,KAAK,UAAU,EAAE,WAAW,SAAS,CAAC;AAAA,EAC9C,CAAC,EAAE,KAAK,OAAO,aAAa;AAC1B,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,QAAI,CAAC,SAAS,MAAM,SAAS,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,MAAM,GAAG;AAC5F,YAAM,IAAI,MAAM,SAAS,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,UAAU,WAC/E,KAAK,QACL,QAAQ,SAAS,MAAM,EAAE;AAAA,IAC/B;AACA,WAAO,EAAE,QAAQ,KAAK,QAAQ,SAAS,KAAK,YAAY,KAAK;AAAA,EAC/D,CAAC;AACH;AAEA,IAAM,QAAqB,CAAC;AAC5B,IAAM,cAA0B,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,EAAE;AAE1D,SAAS,SACd,WACA,SACA,aACA,iBACY;AACZ,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAsB,KAAK;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAqB,WAAW;AAChE,QAAM,CAAC,QAAQ,SAAS,IAAU,eAAsB,MAAM;AAC9D,QAAM,CAAC,OAAO,QAAQ,IAAU,eAAwB,IAAI;AAC5D,QAAM,YAAkB,aAA2B,IAAI;AACvD,QAAM,SAAe,aAAO,CAAC;AAC7B,QAAM,eAAqB,aAAO,SAAS;AAC3C,QAAM,WAAiB,aAAO,WAAW;AACzC,QAAM,aAAmB,aAAO,CAAC;AAIjC,QAAM,aAAmB,aAAO,CAAC;AACjC,QAAM,gBAAsB,aAAsB,IAAI;AAGtD,QAAM,aAAmB,aAAO,OAAO;AACvC,QAAM,kBAAwB,aAA4B,IAAI;AAE9D,EAAM,gBAAU,MAAM;AAAE,aAAS,UAAU;AAAA,EAAY,GAAG,CAAC,WAAW,CAAC;AACvE,EAAM,gBAAU,MAAM;AAAE,eAAW,UAAU;AAAA,EAAQ,GAAG,CAAC,OAAO,CAAC;AAGjE,EAAM,gBAAU,MAAM,MAAM;AAC1B,QAAI,cAAc,YAAY,KAAM,QAAO,aAAa,cAAc,OAAO;AAC7E,oBAAgB,UAAU;AAAA,EAC5B,GAAG,CAAC,CAAC;AAEL,QAAM,OAAa,kBAAY,YAA2B;AACxD,UAAM,aAAa,EAAE,WAAW;AAChC,UAAM,MAAM,aAAa;AACzB,UAAM,YAAY,OAAO;AAEzB,QAAI,cAAc;AAClB,QAAI;AAEF,eAAS,OAAO,GAAG,OAAO,gBAAgB,QAAQ,GAAG;AACnD,cAAM,EAAE,QAAQ,QAAQ,IAAI,MAAM,SAAS,QAAQ,KAAK,OAAO,OAAO;AACtE,YAAI,eAAe,WAAW,WAAW,QAAQ,aAAa,QAAS;AACvE,YAAI,OAAO,SAAS,GAAG;AACrB,cAAI,SAAS,UAAU;AACvB,cAAI,WAAW,KAAM,UAAS,UAAU,UAAU,IAAI,YAAY;AAClE,cAAI,YAAY;AAChB,qBAAW,SAAS,QAAQ;AAC1B,kBAAM,SAAS,OAAO;AACtB,mBAAO,KAAK,KAAK;AACjB,gBAAI,OAAO,cAAc,OAAQ,aAAY;AAAA,UAC/C;AACA,cAAI,WAAW;AACb,kBAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,gBAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,MAAM,OAAO,QAAS,QAAO,UAAU,KAAK;AACrF,qBAAS,OAAO,SAAS,CAAC;AAC1B,qBAAS,OAAO,MAAM,CAAC;AAAA,UACzB;AAAA,QACF;AACA,YAAI,YAAY,QAAQ,OAAO,WAAW,GAAG;AAC3C,wBAAc;AACd;AAAA,QACF;AACA,sBAAc,SAAS,iBAAiB;AAAA,MAC1C;AACA,eAAS,IAAI;AACb,gBAAU,OAAO;AAMjB,UACE,eACG,OAAO,YAAY,aACnB,eAAe,WAAW,WAC1B,QAAQ,aAAa,WACrB,WAAW,WACX,gBAAgB,YAAY,MAC/B;AACA,wBAAgB,QAAQ;AAAA,MAC1B;AAAA,IACF,SAAS,QAAQ;AACf,UAAI,eAAe,WAAW,QAAS;AACvC,eAAS,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAClE,gBAAU,CAAC,YAAa,YAAY,UAAU,UAAU,OAAQ;AAAA,IAClE;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,UAAgB,kBAAY,MAAM;AAAE,SAAK,KAAK;AAAA,EAAE,GAAG,CAAC,IAAI,CAAC;AAM/D,QAAM,eAAqB,kBAAY,MAAY;AACjD,UAAM,UAAU;AAChB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,QAAQ,MAAM,WAAW;AAC/B,QAAI,SAAS,SAAS;AACpB,iBAAW,UAAU;AACrB,WAAK,KAAK;AACV;AAAA,IACF;AACA,QAAI,cAAc,YAAY,KAAM;AACpC,kBAAc,UAAU,OAAO,WAAW,MAAM;AAC9C,oBAAc,UAAU;AACxB,iBAAW,UAAU,KAAK,IAAI;AAC9B,WAAK,KAAK;AAAA,IACZ,GAAG,UAAU,KAAK;AAAA,EACpB,GAAG,CAAC,IAAI,CAAC;AAET,EAAM,gBAAU,MAAM;AACpB,oBAAgB,UAAU;AAAA,EAC5B,GAAG,CAAC,YAAY,CAAC;AAGjB,EAAM,gBAAU,MAAM;AACpB,iBAAa,UAAU;AACvB,cAAU,UAAU;AACpB,WAAO,UAAU;AACjB,aAAS,KAAK;AACd,aAAS,WAAW;AACpB,aAAS,IAAI;AACb,cAAU,UAAU,SAAS,IAAI,YAAY,MAAM;AAAA,EACrD,GAAG,CAAC,SAAS,CAAC;AAGd,EAAM,gBAAU,MAAM;AACpB,QAAI,UAAU,WAAW,KAAK,CAAC,QAAS,QAAO;AAC/C,SAAK,KAAK;AACV,UAAM,UAAU,iBAAiB,QAAQ,SAAS;AAClD,UAAM,UAAU,YAAY,UAAa,QAAQ,YAAY,SAAY,QAAQ,UAAU;AAC3F,QAAI,YAAY,UAAa,OAAO,QAAQ,cAAc,WAAY,QAAO;AAC7E,WAAO,QAAQ,UAAU,MAAM;AAAE,mBAAa;AAAA,IAAE,CAAC;AAAA,EACnD,GAAG,CAAC,WAAW,SAAS,MAAM,cAAc,eAAe,CAAC;AAE5D,SAAO,EAAE,OAAO,OAAO,QAAQ,OAAO,QAAQ;AAChD;;;ACrJO,SAAS,eAAe,UAA+B,KAAsB;AAClF,QAAM,QAAQ,SAAS,MAAM;AAC7B,MAAI,UAAU,UAAa,OAAO,MAAM,WAAW,WAAY,QAAO;AACtE,MAAI,MAAM,OAAO;AACjB,MAAI,MAAM,OAAO;AACjB,aAAW,QAAQ,MAAM,OAAO,GAAG;AACjC,QAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,cAAc,SAAU;AACrF,QAAI,KAAK,YAAY,IAAK,OAAM,KAAK;AACrC,QAAI,KAAK,YAAY,IAAK,OAAM,KAAK;AAAA,EACvC;AACA,SAAO,OAAO,OAAO,OAAO;AAC9B;AAOO,SAAS,eAAe,UAA+B,KAAkC;AAC9F,QAAM,QAAQ,SAAS,MAAM;AAC7B,MAAI,UAAU,UAAa,OAAO,MAAM,WAAW,WAAY,QAAO;AACtE,MAAI,QAA6B;AACjC,MAAI,OAA4B;AAChC,MAAI,OAA4B;AAChC,aAAW,QAAQ,MAAM,OAAO,GAAG;AACjC,QAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,KAAK,eAAe,SAAU;AAC/E,UAAM,SAAS,KAAK;AACpB,QAAI,OAAO,WAAW,SAAU;AAChC,QAAI,KAAK,IAAI,SAAS,GAAG,IAAI,KAAK;AAChC,UAAI,UAAU,KAAM,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,UAAU,QAAQ,SAAS,QAAQ,SAAS,KAAK,WAAY,QAAO;AACxE,QAAI,UAAU,QAAQ,SAAS,QAAQ,SAAS,KAAK,WAAY,QAAO;AAAA,EAC1E;AACA,SAAO,UAAU,OAAO,QAAQ,SAAS,OAAO,OAAO;AACzD;AAEA,SAAS,uBAAuBC,WAAiC;AAC/D,SAAOA,UAAS,cAAc,0BAA0B,MAAM;AAChE;AAGO,SAAS,sBAAsB,MAAkB,UAA8C;AACpG,QAAM,QAAQ,SAAS;AACvB,MAAI,UAAU,UAAa,OAAO,MAAM,QAAQ,WAAY,QAAO;AACnE,QAAM,aAAa,MAAM,IAAI,YAAY;AACzC,MAAI,eAAe,QAAQ,OAAO,eAAe,SAAU,QAAO;AAClE,QAAM,OAAO;AACb,QAAM,QAAQ,MAAM,QAAQ,KAAK,UAAU,IAAI,KAAK,aAAa,CAAC;AAClE,QAAM,WAAW,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC;AACjE,QAAM,eAAe,CAAC,OAAiB,QAA4G;AACjJ,UAAM,UAAU,MAAM,OAAO,CAAC,SAC5B,SAAS,QAAQ,OAAO,SAAS,YAC9B,MAAM,SAAS,OAAQ,KAA4B,IAAI,CAAC,KACxD,OAAQ,KAA2B,QAAQ,QAC/C;AACD,WAAQ,QAAQ,KAAK,CAAC,SAAS,KAAK,QAAQ,GAAG,KAC1C,QAAQ,OAAO,CAAC,SAAU,KAAK,OAAkB,GAAG,EAAE,KAAK,CAAC,GAAG,MAAO,EAAE,MAAkB,EAAE,GAAc,EAAE,CAAC;AAAA,EAEpH;AAEA,MAAI,KAAK,SAAS,UAAU,KAAK,SAAS,aAAa,KAAK,SAAS,SAAS;AAC5E,UAAM,SAAS,aAAa,CAAC,QAAQ,YAAY,SAAS,GAAG,KAAK,GAAG;AACrE,QAAI,WAAW,UAAa,OAAO,OAAO,QAAQ,SAAU,QAAO;AACnE,UAAM,UAAU,OAAO,SAAS,aAAa,SAAS,OAAO;AAC7D,WAAO,GAAG,OAAO,UAAkB,OAAO,GAAG;AAAA,EAC/C;AACA,MAAI,KAAK,SAAS,eAAe,KAAK,SAAS,UAAU;AACvD,UAAM,SAAS,aAAa,CAAC,WAAW,GAAG,KAAK,GAAG;AACnD,QAAI,WAAW,UAAa,OAAO,OAAO,SAAS,YAAY,OAAO,OAAO,SAAS,SAAU,QAAO;AACvG,WAAO,cAAkB,OAAO,IAAI,KAAS,OAAO,IAAI;AAAA,EAC1D;AACA,MAAI,KAAK,SAAS,UAAU,KAAK,SAAS,SAAS;AACjD,QAAI,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,SAAS,EAAG,QAAO,eAAuB,KAAK,MAAM;AACxG,UAAM,SAAS,aAAa,CAAC,aAAa,GAAG,KAAK,GAAG;AACrD,QAAI,WAAW,UAAa,OAAO,OAAO,WAAW,SAAU,QAAO;AACtE,WAAO,eAAuB,OAAO,MAAM;AAAA,EAC7C;AACA,MAAI,KAAK,SAAS,cAAc;AAC9B,UAAM,cAAmF,CAAC;AAC1F,eAAW,WAAW,UAAU;AAC9B,UAAI,YAAY,QAAQ,OAAO,YAAY,SAAU;AACrD,YAAM,SAAS;AACf,UAAI,OAAO,YAAY,gBAAgB,OAAO,OAAO,aAAa,UAAU;AAC1E,oBAAY,KAAK,EAAE,SAAS,OAAO,SAAS,UAAU,OAAO,UAAU,WAAW,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY,OAAU,CAAC;AAAA,MACzJ;AAAA,IACF;AACA,UAAM,SAAS,YAAY,KAAK,CAAC,YAAY,QAAQ,cAAc,KAAK,GAAG,KACtE,YAAY,OAAO,CAAC,aAAa,QAAQ,aAAa,OAAO,KAAK,GAAG,EACrE,KAAK,CAAC,GAAG,OAAO,EAAE,aAAa,OAAO,EAAE,aAAa,GAAG,EAAE,CAAC;AAChE,QAAI,WAAW,OAAW,QAAO;AACjC,WAAO,mBAA2B,OAAO,QAAQ;AAAA,EACnD;AACA,SAAO;AACT;AAEA,SAAS,kBAAkBA,WAAwB,KAA+G;AAChK,QAAM,UAAU,mBAAmB,GAAG;AACtC,QAAM,OAAOA,UAAS,iBAAiB,6BAA6B;AACpE,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,MAAM,KAAK,KAAK;AACtB,UAAM,QAAQ,IAAI,YAAY,SAAY,IAAI,QAAQ,mBAAmB;AACzE,QAAI,UAAU,WAAW,UAAU,IAAK,QAAO;AAAA,EACjD;AACA,SAAO;AACT;AAEA,SAAS,uBAAuBA,WAAwB,MAAoH;AAC1K,QAAM,OAAOA,UAAS,iBAAiB,6BAA6B;AACpE,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,MAAM,KAAK,KAAK;AACtB,UAAM,MAAM,IAAI,QAAQ,oBAAoB;AAC5C,QAAI,KAAK;AACT,QAAI;AACF,WAAK,mBAAmB,GAAG;AAAA,IAC7B,QAAQ;AACN;AAAA,IACF;AACA,QAAI,GAAG,SAAS,UAAkB,KAAK,GAAG,EAAE,EAAG,QAAO;AACtD,QAAI,OAAO,KAAK,WAAW,YAAY,GAAG,SAAS,WAAmB,KAAK,MAAM,EAAE,EAAG,QAAO;AAAA,EAC/F;AACA,SAAO;AACT;AAEA,SAAS,yBAAyBA,WAA8B;AAC9D,QAAM,OAAOA,UAAS,iBAAiB,4BAA4B;AACnE,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,MAAM,KAAK,KAAK;AACtB,QAAI,OAAO,IAAI,UAAU,WAAY,KAAI,MAAM;AAAA,EACjD;AACF;AAEA,SAAS,mBAAkC;AACzC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,QAAI,OAAO,0BAA0B,YAAY;AAC/C,4BAAsB,MAAM,QAAQ,CAAC;AAAA,IACvC,OAAO;AACL,cAAQ;AAAA,IACV;AAAA,EACF,CAAC;AACH;AAMA,eAAsB,WACpB,SACA,MACA,SACsB;AACtB,QAAM,QAAQ,OAAO,SAAS,OAAO,QAAQ,KAAK,CAAC,MAAM,QAAQ,SAAS,KAAK,IAC3E,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,SAAS,GAAG,CAAC,CAAC,IAC3D;AACJ,QAAMA,YAAW,QAAQ;AAEzB,MAAI;AACF,QAAI,WAAW,QAAQ,YAAY;AACnC,UAAM,mBAAmB,uBAAuBA,SAAQ;AAExD,QAAI,kBAAkB;AACpB,UAAI,MAAM,sBAAsB,MAAM,QAAQ;AAC9C,UAAIC,OAAM,QAAQ,OAAO,OAAO,kBAAkBD,WAAU,GAAG;AAC/D,UAAIE,SAAQ;AACZ,UAAID,SAAQ,QAAQ,SAAS,YAAY,MAAM;AAC7C,eAAO,SAAS,YAAY,QAAQC,SAAQ,OAAO;AACjD,gBAAM,QAAQ,UAAU;AACxB,qBAAW,QAAQ,YAAY;AAC/B,UAAAA,UAAS;AACT,kBAAQ,aAAaA,MAAK;AAAA,QAC5B;AACA,cAAM,sBAAsB,MAAM,QAAQ;AAC1C,QAAAD,OAAM,QAAQ,OAAO,OAAO,kBAAkBD,WAAU,GAAG;AAAA,MAC7D;AACA,UAAIC,SAAQ,MAAM;AAChB,iCAAyBD,SAAQ;AACjC,cAAM,iBAAiB;AACvB,QAAAC,OAAM,QAAQ,OAAO,OAAO,kBAAkBD,WAAU,GAAG;AAAA,MAC7D;AACA,UAAIC,SAAQ,MAAM;AAChB,QAAAA,OAAM,uBAAuBD,WAAU,IAAI;AAAA,MAC7C;AACA,UAAIC,SAAQ,KAAM,QAAO,EAAE,IAAI,OAAO,OAAO,8BAA8B;AAC3E,MAAAA,KAAI,eAAe,EAAE,UAAU,UAAU,OAAO,SAAS,CAAC;AAC1D,aAAO,EAAE,IAAI,MAAM,MAAM,aAAa;AAAA,IACxC;AAGA,QAAI,QAAQ;AACZ,WAAO,SAAS,YAAY,QAAQ,CAAC,eAAe,UAAU,KAAK,GAAG,KAAK,QAAQ,OAAO;AACxF,YAAM,QAAQ,UAAU;AACxB,iBAAW,QAAQ,YAAY;AAC/B,eAAS;AACT,cAAQ,aAAa,KAAK;AAAA,IAC5B;AACA,UAAM,OAAO,eAAe,UAAU,KAAK,GAAG;AAC9C,QAAI,SAAS,KAAM,QAAO,EAAE,IAAI,OAAO,OAAO,mBAAmB;AAKjE,UAAM,WAAW,0BAA0B,UAAU,KAAK,GAAG,CAAC;AAC9D,QAAI,MAAMD,UAAS,cAAc,QAAQ;AACzC,QAAI,UAAU;AACd,WAAO,QAAQ,QAAQ,UAAU,GAAG;AAClC,YAAM,iBAAiB;AACvB,YAAMA,UAAS,cAAc,QAAQ;AACrC,iBAAW;AAAA,IACb;AACA,QAAI,QAAQ,KAAM,QAAO,EAAE,IAAI,OAAO,OAAO,uBAAuB;AACpE,QAAI,eAAe,EAAE,UAAU,UAAU,OAAO,SAAS,CAAC;AAC1D,QAAI,IAAI,cAAc,UAAa,OAAO,IAAI,UAAU,QAAQ,YAAY;AAC1E,UAAI,UAAU,IAAI,WAAW;AAAA,IAC/B;AACA,WAAO,EAAE,IAAI,MAAM,MAAM,OAAO;AAAA,EAClC,SAAS,QAAQ;AACf,WAAO,EAAE,IAAI,OAAO,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,EAAE;AAAA,EACvF;AACF;AAEA,SAAS,UAAU,OAAuB;AACxC,MAAI,OAAO,QAAQ,eAAe,OAAO,IAAI,WAAW,WAAY,QAAO,IAAI,OAAO,KAAK;AAC3F,SAAO,MAAM,QAAQ,UAAU,MAAM;AACvC;;;AJ7PA,SAAS,WAAW,KAA4D;AAC9E,MAAI,QAAQ,UAAa,OAAO,IAAI,QAAQ,WAAY,QAAO;AAC/D,QAAM,UAAU,IAAI,IAAI,UAAU;AAClC,SAAO,YAAY,UAAa,YAAY,QAAQ,OAAQ,QAAkC,YAAY,aACtG,UACA;AACN;AAEA,SAAS,eAAe,MAAyB;AAC/C,SAAO,KAAK,SAAS,OAAO,QAAQ,KAAK,IAAI,KAAK,SAAS,KAAK,SAAS;AAC3E;AAGA,SAAS,YAAY,GAAmB;AACtC,MAAI,IAAI,IAAM,QAAO,OAAO,CAAC;AAC7B,QAAM,IAAI,IAAI;AACd,SAAO,GAAG,KAAK,KAAK,OAAO,KAAK,MAAM,CAAC,CAAC,IAAI,OAAO,KAAK,MAAM,IAAI,EAAE,IAAI,EAAE,CAAC;AAC7E;AAGA,IAAM,WAAW;AAGjB,IAAM,YAAY;AAElB,SAAS,UAAU,OAAuB;AACxC,SAAO,MAAM,SAAS,YAAY,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC,WAAM;AACtE;AAGA,SAAS,aAAa,MAA8C;AAClE,QAAM,IAAI,KAAK,YAAY;AAC3B,MAAI,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,OAAO,KAAK,EAAE,SAAS,OAAO,EAAG,QAAO;AAC7E,MAAI,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,SAAS,KAAK,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,KAAK,EAAG,QAAO;AACnG,SAAO;AACT;AAGA,SAAS,UAAU,MAAwB;AACzC,SAAO,OAAO,KAAK,SAAS,SAAS,KAAK,KAAK,MAAM,KAAK,GAAG;AAC/D;AAGA,SAAS,UAAU,MAA4B;AAC7C,MAAI,KAAK,SAAS,QAAQ;AACxB,WAAO;AAAA,MACL,KAAK,KAAK,KAAK;AAAA,MACf,MAAM,KAAK,KAAK,SAAS,UAAU,UAAU;AAAA,MAC7C,GAAI,KAAK,KAAK,WAAW,OAAO,EAAE,QAAQ,KAAK,KAAK,OAAO,IAAI,CAAC;AAAA,IAClE;AAAA,EACF;AACA,SAAO,EAAE,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK;AAC1C;AAiBA,IAAM,UAAgB,YAAK,SAASG,SAAQ,OAAyC;AACnF,QAAM,EAAE,MAAM,OAAO,UAAU,QAAQ,UAAU,QAAQ,cAAc,MAAM,UAAU,eAAe,OAAO,IAAI;AACjH,QAAM,KAAK,eAAe,IAAI;AAE9B,QAAM,cAAc,CAAC,SAA6B;AAChD,QAAI,WAAW,MAAO,QAAO;AAC7B,QAAI,WAAW,SAAU,QAAO,KAAK,SAAS;AAC9C,WAAO,KAAK,SAAS,UAAU,KAAK,SAAS;AAAA,EAC/C;AAEA,QAAM,QAAQ,KAAK,MAAM,OAAO,CAAC,SAAS;AACxC,QAAI,KAAK,SAAS,OAAQ,QAAO,YAAY,KAAK,IAAI;AACtD,QAAI,KAAK,SAAS,QAAS,QAAO;AAClC,WAAO,CAAC;AAAA,EACV,CAAC;AACD,QAAM,UAAW,EAAE,MAAM,YAAY,EAA4B,KAAK,QAAQ,QAAQ,CAAC;AACvF,QAAM,UAAU,KAAK;AACrB,QAAM,WAAW,KAAK;AAEtB,QAAM,YAAY,KAAK,MAAM,KAAK,CAAC,SAAuD,KAAK,SAAS,OAAO;AAG/G,QAAM,MAAM,CAAC,SACX,qCAAC,UAAK,WAAW,sBAAsB,IAAI,IAAI,eAAY,QAAO;AAEpE,QAAM,OAAO,CAAC,OAAe,SAC3B,qCAAC,UAAK,WAAW,cAAc,OAAO,qBAAqB,EAAE,MAAK,KAAM;AAI1E,QAAM,iBAAiB,CAAC,SACtB;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAU;AAAA,MACV,eAAY;AAAA,MACZ,OAAO,UAAU,KAAK,IAAI;AAAA,MAC1B,SAAS,MAAM;AAAE,eAAO,EAAE,KAAK,KAAK,KAAK,MAAM,QAAQ,GAAG,IAAI;AAAA,MAAE;AAAA;AAAA,IAE/D,IAAI,MAAM;AAAA,IACV,KAAK,EAAE,MAAM,OAAO,GAAa,KAAK;AAAA,IACvC,qCAAC,UAAK,WAAU,iBAAe,KAAK,IAAK;AAAA,EAC3C;AAGF,QAAM,aAAa,CAAC,MAAiB,QAAoC;AACvE,UAAM,UAAU,KAAK,SAAS;AAC9B,UAAM,OAAO,UAAU,KAAK,QAAQ,KAAK;AACzC,UAAM,OAAO,UAAU,QAAQ,aAAa,KAAK,IAAI;AACrD,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV;AAAA,QACA,eAAY;AAAA,QACZ,kBAAgB,KAAK;AAAA,QACrB,OAAO;AAAA,QACP,SAAS,MAAM;AAAE,iBAAO,UAAU,EAAE,MAAM,QAAQ,KAAK,CAAC,GAAG,IAAI;AAAA,QAAE;AAAA;AAAA,MAEhE,IAAI,IAAI;AAAA,MACR,KAAK,KAAK,MAAM,IAAI;AAAA,MACrB,qCAAC,UAAK,WAAU,iBAAe,IAAK;AAAA,IACtC;AAAA,EAEJ;AAEA,QAAM,aAAa,CAAC,MAAgB,QAA2C;AAC7E,QAAI,KAAK,SAAS,OAAQ,QAAO,WAAW,KAAK,MAAM,GAAG;AAC1D,QAAI,KAAK,SAAS,QAAS,QAAO;AAClC,QAAI,KAAK,SAAS,UAAU;AAC1B,aACE;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL;AAAA,UACA,WAAU;AAAA,UACV,eAAY;AAAA,UACZ,OAAO,UAAU,KAAK,IAAI;AAAA,UAC1B,SAAS,MAAM;AAAE,mBAAO,UAAU,IAAI,GAAG,IAAI;AAAA,UAAE;AAAA;AAAA,QAE9C,IAAI,QAAQ;AAAA,QACZ,KAAK,EAAE,MAAM,QAAQ,GAAa,KAAK;AAAA,QACxC,qCAAC,UAAK,WAAU,8BAA4B,KAAK,IAAK;AAAA,MACxD;AAAA,IAEJ;AACA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL;AAAA,QACA,WAAU;AAAA,QACV,eAAY;AAAA,QACZ,OAAO,UAAU,KAAK,OAAO;AAAA,QAC7B,SAAS,MAAM;AAAE,iBAAO,UAAU,IAAI,GAAG,IAAI;AAAA,QAAE;AAAA;AAAA,MAE9C,IAAI,SAAS;AAAA,MACb,KAAK,EAAE,MAAM,eAAe,GAAa,KAAK;AAAA,MAC/C,qCAAC,UAAK,WAAU,+CAA6C,KAAK,OAAQ;AAAA,IAC5E;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,eAAY;AAAA,MACZ,kBAAgB,WAAW,UAAU;AAAA,MACrC,eAAa,SAAS,SAAS;AAAA,MAC/B,cAAY,WAAW,SAAS;AAAA;AAAA,IAEhC,qCAAC,SAAI,WAAU,kBACb;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,iBAAe;AAAA,QACf,SAAS,MAAM;AAAE,mBAAS,EAAE;AAAA,QAAE;AAAA;AAAA,MAE9B,qCAAC,UAAK,WAAW,cAAc,WAAW,qBAAqB,EAAE,MAAK,YAAY,CAAE;AAAA,MACpF,qCAAC,UAAK,WAAU,eAAc,eAAY,kBAAgB,OAAQ;AAAA,MACjE,QAAQ,SAAS,IACd,qCAAC,UAAK,WAAU,mBAAkB,OAAO,UAAU,OAAO,KAAI,OAAQ,IACtE;AAAA,IACN,GACA,qCAAC,UAAK,WAAU,kBACb,WAAW,qCAAC,UAAK,WAAU,gBAAe,eAAY,iBAAgB,OAAO,EAAE,MAAM,OAAO,GAAa,IAAK,MAC/G;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,eAAY;AAAA,QACZ,OAAO,EAAE,MAAM,YAAY;AAAA;AAAA,MAC3B,YAAY,KAAK,UAAU;AAAA,IAAE,GAC/B;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,eAAY;AAAA,QACZ,OAAO,EAAE,MAAM,QAAQ;AAAA,QACvB,SAAS,MAAM;AAAE,iBAAO,EAAE,KAAK,KAAK,WAAW,MAAM,QAAQ,GAAG,IAAI;AAAA,QAAE;AAAA;AAAA,MACtE,WAAW;AAAA,IAAE,CACjB,CACF;AAAA,IACC,YACI,MAAM;AAML,YAAM,YAAY,MAAM,SAAS,YAAY,CAAC;AAC9C,YAAM,aAAa,YAAY,MAAM,MAAM,MAAM,SAAS,QAAQ,IAAI;AACtE,YAAM,cAAc,MAAM,SAAS,WAAW;AAC9C,aACE,qCAAC,SAAI,WAAU,kBACZ,WAAW,SAAS,cAAc,SAAY,eAAe,SAAS,IAAI,MAC1E,cAAc,IACX;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,eAAY;AAAA,UACZ,SAAS,MAAM;AAAE,0BAAc,EAAE;AAAA,UAAE;AAAA;AAAA,QAClC,EAAE,MAAM,kBAAkB,EAA4B,WAAW;AAAA,MAAE,IACtE,MACH,MAAM,WAAW,IACd,qCAAC,SAAI,WAAU,qBAAmB,EAAE,MAAM,eAAe,CAAY,IACrE,WAAW,IAAI,CAAC,SAAS,WAAW,MAAM,UAAU,IAAI,CAAC,CAAC,CAChE;AAAA,IAEJ,GAAG,IACH;AAAA,EACN;AAEJ,CAAC;AAEM,SAAS,SAAS,OAAiD;AACxE,QAAM,OAAa,WAAW;AAC9B,QAAM,YAAY,MAAM,OAAO,aAAa;AAC5C,QAAM,UAAU,MAAM,YAAY;AAClC,QAAM,kBAAwB,eAAQ,MAAM,WAAW,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC;AAC9E,QAAM,cAAoB;AAAA,IACxB,MAAM,MAAM,wBAAwB,SAChC,MAAM,sBACN,CAAC,KAAa,aAAqB,iBAAiB,KAAK,QAAQ;AAAA,IACrE,CAAC,MAAM,mBAAmB;AAAA,EAC5B;AAEA,QAAM,EAAE,OAAO,OAAO,QAAQ,OAAO,QAAQ,IAAI,SAAS,WAAW,SAAS,aAAa,eAAe;AAE1G,QAAM,CAAC,QAAQ,SAAS,IAAU,gBAAqB,KAAK;AAC5D,QAAM,CAAC,eAAe,gBAAgB,IAAU,gBAA6B,IAAI;AACjF,QAAM,CAAC,SAAS,UAAU,IAAU,gBAAwB,IAAI;AAChE,QAAM,CAAC,WAAW,YAAY,IAAU,gBAAS,CAAC;AAClD,QAAM,CAAC,aAAa,cAAc,IAAU,gBAAwB,IAAI;AACxE,QAAM,CAAC,cAAc,eAAe,IAAU,gBAAwB,IAAI;AAC1E,QAAM,CAAC,cAAc,eAAe,IAAU,gBAAS,IAAI;AAC3D,QAAM,CAAC,cAAc,eAAe,IAAU,gBAAS,KAAK;AAC5D,QAAM,CAAC,eAAe,gBAAgB,IAAU,gBAAS,KAAK;AAC9D,QAAM,CAAC,cAAc,eAAe,IAAU,gBAAsB,oBAAI,IAAI,CAAC;AAC7E,QAAM,UAAgB,cAA8B,IAAI;AACxD,QAAM,aAAmB,cAAO,KAAK;AAGrC,QAAM,WAAiB,mBAAY,MAAY;AAC7C,UAAM,KAAK,QAAQ;AACnB,QAAI,OAAO,KAAM;AACjB,UAAM,aAAa,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe;AACtE,oBAAgB,CAAC,UAAU;AAAA,EAC7B,GAAG,CAAC,CAAC;AAEL,EAAM,iBAAU,MAAM;AACpB,QAAI,CAAC,gBAAgB,aAAc,QAAO;AAC1C,UAAM,KAAK,QAAQ;AACnB,QAAI,OAAO,KAAM,QAAO;AACxB,QAAI,OAAO,OAAO,0BAA0B,YAAY;AACtD,YAAM,MAAM,OAAO,sBAAsB,MAAM;AAAE,WAAG,YAAY,GAAG;AAAA,MAAa,CAAC;AACjF,aAAO,MAAM,OAAO,qBAAqB,GAAG;AAAA,IAC9C;AACA,UAAM,UAAU,OAAO,WAAW,MAAM;AAAE,SAAG,YAAY,GAAG;AAAA,IAAa,GAAG,CAAC;AAC7E,WAAO,MAAM,OAAO,aAAa,OAAO;AAAA,EAC1C,GAAG,CAAC,OAAO,cAAc,YAAY,CAAC;AAEtC,QAAM,iBAAuB,mBAAY,MAAY;AACnD,UAAM,KAAK,QAAQ;AACnB,QAAI,OAAO,KAAM;AACjB,OAAG,YAAY,GAAG;AAClB,oBAAgB,KAAK;AAAA,EACvB,GAAG,CAAC,CAAC;AAGL,QAAM,cAAoB,cAAO,EAAE;AACnC,EAAM,iBAAU,MAAM;AACpB,gBAAY,UAAU,MAAM,SAAS,IAAI,eAAe,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;AAAA,EACrF,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,iBAAuB,mBAAY,CAAC,IAAY,UAA2B;AAC/E,QAAI,kBAAkB,KAAM,QAAO,UAAU,MAAM,SAAS;AAC5D,WAAO,cAAc,IAAI,EAAE;AAAA,EAC7B,GAAG,CAAC,eAAe,MAAM,MAAM,CAAC;AAEhC,QAAM,aAAmB,mBAAY,CAAC,OAAqB;AACzD,qBAAiB,CAAC,YAAY;AAC5B,UAAI,YAAY,MAAM;AAIpB,cAAM,WAAW,YAAY;AAC7B,cAAMC,QAAO,oBAAI,IAAY;AAC7B,YAAI,SAAS,SAAS,EAAG,CAAAA,MAAK,IAAI,QAAQ;AAC1C,YAAIA,MAAK,IAAI,EAAE,EAAG,CAAAA,MAAK,OAAO,EAAE;AAAA,YAC3B,CAAAA,MAAK,IAAI,EAAE;AAChB,eAAOA;AAAA,MACT;AACA,YAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,UAAI,KAAK,IAAI,EAAE,EAAG,MAAK,OAAO,EAAE;AAAA,UAC3B,MAAK,IAAI,EAAE;AAChB,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,iBAAuB,mBAAY,CAAC,OAAqB;AAC7D,oBAAgB,CAAC,YAAY;AAC3B,YAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,UAAI,KAAK,IAAI,EAAE,EAAG,MAAK,OAAO,EAAE;AAAA,UAC3B,MAAK,IAAI,EAAE;AAChB,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAGL,QAAM,iBAAuB,eAA4B,MAAM;AAC7D,QAAI,oBAAoB,OAAW,QAAO;AAC1C,UAAM,UAAU,gBAAgB,QAAQ,SAAS;AACjD,UAAM,UAAU,YAAY,UAAa,QAAQ,YAAY,SAAY,QAAQ,UAAU;AAC3F,QAAI,YAAY,OAAW,QAAO;AAClC,UAAM,OAAO;AACb,QAAI,OAAO,KAAK,cAAc,cAAc,OAAO,KAAK,gBAAgB,WAAY,QAAO;AAC3F,WAAO;AAAA,EACT,GAAG,CAAC,iBAAiB,SAAS,CAAC;AAE/B,QAAM,SAAe,mBAAY,OAAO,QAAoB,SAAmC;AAC7F,QAAI,WAAW,QAAS;AACxB,oBAAgB,eAAe,IAAI,CAAC;AACpC,QAAI,mBAAmB,MAAM;AAC3B,qBAAe,EAAE,MAAM,YAAY,CAAW;AAC9C;AAAA,IACF;AACA,eAAW,UAAU;AACrB,eAAW,OAAO,GAAG;AACrB,iBAAa,CAAC;AACd,mBAAe,IAAI;AACnB,UAAM,UAAU,MAAM,WAAW,gBAAgB,QAAQ;AAAA,MACvD,UAAU,OAAO;AAAA,MACjB,YAAY,CAAC,UAAU,aAAa,KAAK;AAAA,IAC3C,CAAC;AACD,eAAW,UAAU;AACrB,eAAW,IAAI;AACf,QAAI,QAAQ,IAAI;AACd,qBAAe,EAAE,MAAM,QAAQ,SAAS,eAAe,mBAAmB,UAAU,CAAW;AAAA,IACjG,OAAO;AACL,qBAAe,EAAE,MAAM,YAAY,CAAW;AAAA,IAChD;AAAA,EACF,GAAG,CAAC,gBAAgB,IAAI,CAAC;AAGzB,QAAM,cAAc,CAAC,SAA6B;AAChD,QAAI,WAAW,MAAO,QAAO;AAC7B,QAAI,WAAW,SAAU,QAAO,KAAK,SAAS;AAC9C,WAAO,KAAK,SAAS,UAAU,KAAK,SAAS;AAAA,EAC/C;AAEA,QAAM,eAAqB;AAAA,IACzB,MAAO,WAAW,SAAS,WAAW,WAAW,QAAQ,MAAM,OAAO,CAAC,SAAS,QAAQ,IAAI,EAAE,KAAK,CAAC,SAAS,YAAY,IAAI,CAAC,CAAC;AAAA;AAAA,IAE/H,CAAC,OAAO,MAAM;AAAA,EAChB;AAEA,QAAM,YAAY,MAAM,MAAM,MAAM,GAAG,CAAC;AACxC,QAAM,eAAe,MAAM,MAAM,SAAS;AAC1C,QAAM,eAAe,WAAW,SAAS,WAAW;AAEpD,MAAI,CAAC,QAAS,QAAO;AAGrB,QAAM,aAAa,CAAC,OAAmB,OAAe,OAAe,QACnE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,KAAK;AAAA,MACL,WAAU;AAAA,MACV,gBAAc,WAAW,QAAQ,SAAS;AAAA,MAC1C,eAAa,YAAY,KAAK;AAAA,MAC9B,SAAS,MAAM;AAAE,kBAAU,CAAC,YAAa,YAAY,QAAQ,QAAQ,KAAM;AAAA,MAAE;AAAA;AAAA,IAE5E,MAAM,qCAAC,UAAK,WAAW,uBAAuB,GAAG,IAAI,eAAY,QAAO,IAAK;AAAA,IAC9E,qCAAC,cAAM,KAAM;AAAA,IACZ,QAAQ,IAAI,qCAAC,UAAK,WAAU,iBAAe,OAAO,KAAK,CAAE,IAAU;AAAA,EACtE;AAGF,QAAM,SACJ,qCAAC,SAAI,WAAU,gBACb,qCAAC,QAAG,WAAU,eAAa,EAAE,MAAM,UAAU,CAAY,GACzD,qCAAC,UAAK,WAAU,aAAY,eAAY,eAAc,EAAE,MAAM,YAAY,EAA4B,MAAM,MAAM,CAAE,GACpH,qCAAC,UAAK,WAAU,oBAAmB,GACnC;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAU;AAAA,MACV,gBAAc,eAAe,SAAS;AAAA,MACtC,eAAY;AAAA,MACZ,SAAS,MAAM;AAAE,wBAAgB,CAAC,YAAY,CAAC,OAAO;AAAA,MAAE;AAAA;AAAA,IAExD,qCAAC,UAAK,WAAU,iBAAgB,eAAY,QAAO;AAAA,IAClD,EAAE,MAAM,cAAc;AAAA,EACzB,CACF;AAGF,QAAM,QACJ,4DACE,qCAAC,SAAI,WAAU,iBAAgB,MAAK,SAAQ,eAAY,eACrD,WAAW,OAAO,EAAE,MAAM,WAAW,GAAa,MAAM,KAAK,GAC7D,MAAM,SAAS,IAAI,WAAW,UAAU,EAAE,MAAM,cAAc,GAAa,MAAM,MAAM,IAAI,MAC3F,UAAU,IAAI,CAAC,SAAS,WAAW,KAAK,MAAM,KAAK,MAAM,KAAK,OAAO,aAAa,KAAK,IAAI,CAAC,CAAC,GAC7F,eACG;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,KAAI;AAAA,MACJ,WAAU;AAAA,MACV,iBAAe,gBAAgB,SAAS;AAAA,MACxC,eAAY;AAAA,MACZ,SAAS,MAAM;AAAE,yBAAiB,CAAC,YAAY,CAAC,OAAO;AAAA,MAAE;AAAA;AAAA,IAExD,EAAE,MAAM,gBAAgB,mBAAmB,YAAY;AAAA,IACxD,qCAAC,UAAK,WAAW,gBAAgB,gBAAgB,uBAAuB,EAAE,MAAK,YAAY,CAAE;AAAA,EAC/F,IACA,IACN,GACC,iBAAiB,eACd,qCAAC,SAAI,WAAU,oBAAmB,eAAY,uBAC3C,MAAM,MAAM,IAAI,CAAC,SAAS,WAAW,KAAK,MAAM,KAAK,MAAM,KAAK,OAAO,aAAa,KAAK,IAAI,CAAC,CAAC,CAClG,IACA,IACN;AAGF,MAAI;AACJ,MAAI,WAAW,WAAW;AACxB,WAAO,qCAAC,SAAI,WAAU,cAAa,eAAY,gBAAc,EAAE,MAAM,SAAS,CAAY;AAAA,EAC5F,WAAW,WAAW,WAAW,MAAM,WAAW,GAAG;AACnD,WACE,qCAAC,aACC,qCAAC,SAAI,WAAU,aAAY,eAAY,eAAa,SAAS,EAAE,MAAM,YAAY,CAAY,GAC7F,qCAAC,SAAI,WAAU,mBACb,qCAAC,YAAO,MAAK,UAAS,WAAU,YAAW,SAAS,SAAS,eAAY,eAAa,EAAE,MAAM,OAAO,CAAY,CACnH,CACF;AAAA,EAEJ,WAAW,MAAM,WAAW,GAAG;AAC7B,WAAO,qCAAC,SAAI,WAAU,cAAa,eAAY,eAAa,EAAE,MAAM,cAAc,CAAY;AAAA,EAChG,WAAW,aAAa,WAAW,GAAG;AACpC,WACE,qCAAC,SAAI,WAAU,cAAa,eAAY,wBACrC,EAAE,MAAM,eAAe,GACxB,qCAAC,YAAO,MAAK,UAAS,WAAU,YAAW,OAAO,EAAE,YAAY,EAAE,GAAG,SAAS,MAAM;AAAE,gBAAU,KAAK;AAAA,IAAE,KAAI,EAAE,MAAM,aAAa,CAAY,CAC9I;AAAA,EAEJ,OAAO;AACL,WACE,qCAAC,SAAI,WAAU,YAAW,KAAK,SAAS,UAAoB,eAAY,cACrE,aAAa,IAAI,CAAC,MAAM,UACvB;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,eAAe,IAAI;AAAA,QACxB;AAAA,QACA;AAAA,QACA,UAAU,eAAe,eAAe,IAAI,GAAG,KAAK;AAAA,QACpD,QAAQ,iBAAiB,eAAe,IAAI;AAAA,QAC5C,UAAU,aAAa,IAAI,eAAe,IAAI,CAAC;AAAA,QAC/C;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,eAAe;AAAA,QACf,QAAQ;AAAA;AAAA,IACV,CACD,CACH;AAAA,EAEJ;AAEA,QAAM,YAAY,YAAY,QAAQ,gBAAgB,OAEhD,qCAAC,SAAI,WAAU,iBAAgB,eAAY,mBACxC,YAAY,OAAO,qCAAC,UAAK,WAAU,cAAY,YAAY,CAAE,IAAU,MACxE,qCAAC,cACE,YAAY,OAAO,GAAI,EAAE,MAAM,cAAc,EAA4B,SAAS,CAAC,MAAM,IACzF,eAAe,EAClB,CACF,IAEF;AAEJ,SACE,qCAAC,SAAI,WAAU,YAAW,MAAK,iBAAgB,cAAY,EAAE,MAAM,UAAU,GAAa,eAAY,cACnG,QACA,OACA,MACA,WACA,YAAY,OACT,qCAAC,SAAI,WAAU,kBAAiB,eAAY,mBAAkB,MAAK,YACjE,qCAAC,UAAK,WAAU,cAAY,YAAY,CAAE,GACxC,EAAE,MAAM,cAAc,EAA4B,SAAS,CAC/D,IACA,MACH,gBAAgB,MAAM,SAAS,IAC5B,qCAAC,YAAO,MAAK,UAAS,WAAU,gBAAe,eAAY,iBAAgB,SAAS,kBAAiB,EAAE,MAAM,YAAY,CAAY,IACrI,IACN;AAEJ;AAEA,SAAS,cAAkC;AACzC,SACE,qCAAC,SAAI,SAAQ,aAAY,OAAM,MAAK,QAAO,MAAK,eAAY,UAC1D,qCAAC,UAAK,GAAE,gBAAe,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ,gBAAe,SAAQ,CAC1H;AAEJ;AAEA,SAAS,cAAkC;AACzC,SACE,qCAAC,SAAI,SAAQ,aAAY,OAAM,MAAK,QAAO,MAAK,eAAY,UAC1D,qCAAC,UAAK,GAAE,+CAA8C,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,eAAc,SAAQ,CAClI;AAEJ;AAEA,SAAS,aAAiC;AACxC,SACE,qCAAC,SAAI,SAAQ,aAAY,OAAM,MAAK,QAAO,MAAK,eAAY,UAC1D,qCAAC,YAAO,IAAG,KAAI,IAAG,KAAI,GAAE,KAAI,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,GAChF,qCAAC,YAAO,IAAG,KAAI,IAAG,KAAI,GAAE,OAAM,MAAK,gBAAe,CACpD;AAEJ;;;AFnjBA,SAAS,UAAU,MAA+B;AAChD,QAAM,WAAW;AAAA,IACT,qBAAc,UAAU,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC;AAAA,IACzD,qBAAc,QAAQ,EAAE,KAAK,KAAK,GAAG,uDAAuD,CAAC;AAAA,IAC7F,qBAAc,UAAU,EAAE,KAAK,KAAK,IAAI,IAAI,IAAI,GAAG,GAAG,EAAE,CAAC;AAAA,EACjE;AACA,SAAa;AAAA,IACX;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,eAAe;AAAA,IACjB;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,SAAS,CAAC,YAAY,eAAe;AAE3C,SAAS,MAAM,KAGb;AACP,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,QAAQ,SAAS;AACvB,QAAM,cAAc;AACpB,WAAS,KAAK,YAAY,KAAK;AAM/B,MAAI,OAAO,MAAM,MAAM,MAAM,OAAO,CAAC;AAErC,QAAM,gBAAgB,IAAI,IAAI,eAAe;AAC7C,MACE,kBAAkB,UAAa,kBAAkB,QAC9C,OAAQ,cAA4C,gBAAgB,YACvE;AAEA;AAAA,EACF;AAEA,MAAI,OAAO,MAAO,cAAmE,YAAY;AAAA,IAC/F,IAAI;AAAA,IACJ,OAAO,MAAO,OAAO,cAAc,eAAe,UAAU,SAAS,YAAY,EAAE,WAAW,IAAI,IAC9F,6BACA;AAAA,IACJ,MAAM,CAAC,SAAiB,UAAU,IAAI;AAAA,IACtC,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,UAMA,qBAAc,UAAU;AAAA,MAClC,KAAK,MAAM;AAAA,MACX,OAAO,MAAM;AAAA,MACb,SAAS,MAAM,YAAY;AAAA,MAC3B,KAAK,MAAM;AAAA,IACb,CAAC;AAAA,EACH,CAAC,CAAC;AACJ;",
6
6
  "names": ["React", "React", "out", "document", "row", "pages", "TurnRow", "next"]
7
7
  }
package/lib/index.js CHANGED
@@ -241,7 +241,7 @@ function cacheStore(sessionId, lastSeq, events, bytes, probedAt) {
241
241
  function apply(ctx) {
242
242
  ctx.effect(() => ctx.webServer.register({
243
243
  kind: "exact",
244
- path: "/turntrail/api/events",
244
+ path: "/turn-outline/api/events",
245
245
  handler: async (rawReq, rawRes) => {
246
246
  const req = rawReq;
247
247
  const res = rawRes;
package/lib/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/wire.ts", "../src/index.ts"],
4
- "sourcesContent": ["/**\n * wire.ts \u2014 host-side response shaping for POST /turntrail/api/events.\n *\n * The client fold needs only a small, typed subset of each event. Every\n * response event is projected to that subset BEFORE serialization, so the\n * wire (and the client's retained state) never carries full tool results,\n * reasoning blocks, or unbounded message text.\n *\n * Projection is idempotent and lossless for the fold's needs:\n * - `seq` / `type` / `time` are always preserved (the seq watermark and\n * turn identity ride on them).\n * - text fields are clipped to TEXT_CAP; tool arguments to ARGS_CAP.\n * - content block arrays keep ONLY non-empty text blocks, in order (the\n * fold reads exactly the first / last text block).\n * - `tool/result` message content (the bulky part of real logs) is dropped\n * entirely \u2014 the fold consumes only `error.name` / `error.code`.\n */\nimport type { SessionEventLike } from './context-types'\n\n/** Max characters carried per text field (input / output / summary). */\nexport const TEXT_CAP = 2048\n\n/** Max characters of tool-call arguments (the fold only previews 120). */\nexport const ARGS_CAP = 512\n\n/** Max events per response page; the client loops while `hasMore` is true. */\nexport const PAGE_SIZE = 2000\n\n/** Hard body cap for the route's request payload. */\nexport const MAX_BODY_BYTES = 64 * 1024\n\nfunction clipText(value: string, cap: number): string {\n return value.length > cap ? `${value.slice(0, cap)}\u2026` : value\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return value !== null && typeof value === 'object' ? (value as Record<string, unknown>) : undefined\n}\n\n/**\n * Project one content value (bare string or block array) onto the fold's\n * needs: clipped text, or a text-blocks-only array. Returns undefined when\n * nothing foldable survives (the fold then treats the field as absent,\n * which matches its behavior for empty/non-text content).\n */\nfunction projectContent(content: unknown, cap: number): string | Array<{ type: 'text'; text: string }> | undefined {\n if (typeof content === 'string') return content.length > 0 ? clipText(content, cap) : undefined\n if (!Array.isArray(content)) return undefined\n const out: Array<{ type: 'text'; text: string }> = []\n for (const block of content) {\n const b = record(block)\n if (b === undefined || b.type !== 'text' || typeof b.text !== 'string' || b.text.length === 0) continue\n out.push({ type: 'text', text: clipText(b.text, cap) })\n }\n return out.length > 0 ? out : undefined\n}\n\nfunction sourceOf(data: Record<string, unknown>): Record<string, unknown> | undefined {\n const message = record(data.message)\n if (message !== undefined) {\n const source = record(message.source)\n if (source !== undefined) return source\n }\n return record(data.source)\n}\n\n/** Copy only the source fields the fold reads (kind / callId). */\nfunction projectSourceFields(source: Record<string, unknown>): Record<string, unknown> | undefined {\n const out: Record<string, unknown> = {}\n if (typeof source.kind === 'string') out.kind = source.kind\n if (typeof source.callId === 'string' && source.callId.length > 0) out.callId = source.callId\n return Object.keys(out).length > 0 ? out : undefined\n}\n\n/**\n * Project one session event onto the fold-relevant wire subset.\n * See the module header for the field policy.\n */\nexport function projectEvent(event: SessionEventLike): SessionEventLike {\n const base: SessionEventLike = {\n type: event.type,\n seq: event.seq,\n ...(typeof event.time === 'number' ? { time: event.time } : {}),\n }\n const data = record(event.data)\n if (data === undefined) return base\n\n switch (event.type) {\n case 'turn/start':\n case 'turn/end':\n return typeof data.turn === 'number' ? { ...base, data: { turn: data.turn } } : base\n\n case 'user/message': {\n const out: Record<string, unknown> = {}\n const content = projectContent(data.content, TEXT_CAP)\n if (content !== undefined) out.content = content\n const message = record(data.message)\n if (message !== undefined) {\n const m: Record<string, unknown> = {}\n if (typeof message.content === 'string' && message.content.length > 0) {\n m.content = clipText(message.content, TEXT_CAP)\n }\n const source = record(message.source)\n if (source !== undefined) {\n const s = projectSourceFields(source)\n if (s !== undefined) m.source = s\n }\n if (Object.keys(m).length > 0) out.message = m\n }\n const source = record(data.source)\n if (source !== undefined) {\n const s = projectSourceFields(source)\n if (s !== undefined) out.source = s\n }\n return Object.keys(out).length > 0 ? { ...base, data: out } : base\n }\n\n case 'assistant/message': {\n const out: Record<string, unknown> = {}\n const message = record(data.message)\n if (message !== undefined) {\n const content = projectContent(message.content, TEXT_CAP)\n if (content !== undefined) out.message = { content }\n }\n // The fold falls back to data.content when message.content is absent.\n const fallback = projectContent(data.content, TEXT_CAP)\n if (fallback !== undefined) out.content = fallback\n return Object.keys(out).length > 0 ? { ...base, data: out } : base\n }\n\n case 'tool/call': {\n const out: Record<string, unknown> = {}\n if (typeof data.name === 'string' && data.name.length > 0) out.name = data.name\n if (typeof data.arguments === 'string' && data.arguments.length > 0) {\n out.arguments = clipText(data.arguments, ARGS_CAP)\n }\n if (typeof data.callId === 'string' && data.callId.length > 0) out.callId = data.callId\n const source = sourceOf(data)\n if (source !== undefined) {\n const s = projectSourceFields(source)\n if (s !== undefined && typeof s.callId === 'string') out.message = { source: s }\n }\n return Object.keys(out).length > 0 ? { ...base, data: out } : base\n }\n\n case 'tool/result': {\n // The fold reads only error.name / error.code (plus call ids);\n // message.content \u2014 the bulky payload \u2014 is intentionally dropped.\n const out: Record<string, unknown> = {}\n const error = record(data.error)\n if (error !== undefined) {\n const e: Record<string, unknown> = {}\n if (typeof error.name === 'string' && error.name.length > 0) e.name = error.name\n if (typeof error.code === 'string' && error.code.length > 0) e.code = error.code\n if (Object.keys(e).length > 0) out.error = e\n }\n if (typeof data.callId === 'string' && data.callId.length > 0) out.callId = data.callId\n const source = sourceOf(data)\n if (source !== undefined) {\n const s = projectSourceFields(source)\n if (s !== undefined && typeof s.callId === 'string') out.message = { source: s }\n }\n return Object.keys(out).length > 0 ? { ...base, data: out } : base\n }\n\n case 'compaction/summary': {\n const summary = projectContent(data.summary, TEXT_CAP)\n return summary !== undefined ? { ...base, data: { summary } } : base\n }\n\n default:\n // command/run and unknown types: the fold ignores their data.\n return base\n }\n}\n", "/**\n * Host half of dsh-turn-outline: one incremental, read-only route that serves\n * session events for the trail panel, behind a browser-trust fence.\n *\n * POST /turntrail/api/events { sessionId, afterSeq }\n * \u2192 200 { events, hasMore } (one PAGE_SIZE page, ascending seq)\n * \u2192 400 bad request / missing sessionId\n * \u2192 403 fence rejection\n * \u2192 404 session-not-found\n * \u2192 405 non-POST\n * \u2192 413 oversized request body\n *\n * Two read paths, hottest first:\n *\n * LIVE sessions (the trail's hot case \u2014 the session is generating):\n * borrow the live store's frozen `events` snapshot. The probe is O(1)\n * (`events.at(-1).seq`), the tail slice is O(delta), and the whole path\n * is synchronous \u2014 no await between borrowing the snapshot and writing\n * the response, so no interleaved mutation. Zero deep clones: the store\n * already deep-freezes its events, and `JSON.stringify` reads frozen\n * objects fine. (The sessionQuery equivalents each `structuredClone`\n * EVERY event \u2014 3\u00D7 per request through listEvents + readSession \u2014 which\n * hammered the very process running the agent.)\n *\n * COLD / persisted sessions (idle, pulled only on activation):\n * sessionQuery with a per-session lastSeq cache, exactly as before. The\n * `listEvents` probe is itself a whole-log read + per-event structuredClone\n * inside sessionQuery, so the probe result is cached behind a short TTL\n * (PROBE_TTL_MS): one probe per multi-page drain instead of one per page.\n *\n * inject = ['webServer', 'sessionQuery', 'sessions'] \u2014 all host services;\n * `sessions` is core DSH (sessionQuery itself depends on it), and the code\n * feature-detects it so foreign contexts degrade to the persisted path.\n */\nimport type {\n HostCtx,\n HttpHeadersLike,\n HttpRequestLike,\n HttpResponseLike,\n LiveSessionLike,\n SessionEventLike,\n TrailEventsRequest,\n} from './context-types'\nimport { MAX_BODY_BYTES, PAGE_SIZE, projectEvent } from './wire'\n\nexport const inject = ['webServer', 'sessionQuery', 'sessions']\n\n/** Per-session cache: { lastSeq, events } refreshed only on growth (LRU). */\ninterface CacheEntry {\n lastSeq: number\n /** Time of the last authoritative listEvents probe for this entry. */\n probedAt: number\n events: SessionEventLike[]\n bytes: number\n}\nconst trailCache = new Map<string, CacheEntry>()\nconst CACHE_MAX_ENTRIES = 64\nconst CACHE_MAX_BYTES = 32 * 1024 * 1024\nlet cacheBytes = 0\n\n/**\n * Freshness window for the cached lastSeq probe. A persisted (non-live) log\n * cannot grow in this process \u2014 a session that starts generating turns live\n * and is served by the zero-copy fast path above \u2014 so reusing a recent probe\n * across a multi-page drain is safe; the TTL only bounds the staleness window.\n */\nexport const PROBE_TTL_MS = 2000\n\n// \u2500\u2500 Browser-trust fence (DNS-rebinding / cross-site defense, not auth) \u2500\u2500\u2500\u2500\n\nfunction header(headers: HttpHeadersLike, name: string): string | undefined {\n const value = (headers as Record<string, unknown>)[name]\n return typeof value === 'string' ? value : undefined\n}\n\nfunction parseAuthority(authority: string): URL | undefined {\n try {\n return new URL(`http://${authority}`)\n } catch {\n return undefined\n }\n}\n\nexport function isLoopbackHostname(hostname: string): boolean {\n if (hostname === 'localhost' || hostname === '[::1]') return true\n const parts = hostname.split('.')\n return parts.length === 4\n && parts[0] === '127'\n && parts.every((part) => /^\\d{1,3}$/.test(part) && Number(part) <= 255)\n}\n\n/**\n * Decide whether one request may reach the trail routes. Behaviorally\n * identical to DSH's /api gateway fence (api-request-trust + loopback-hostname):\n * Host must resolve to loopback; cross-site browser markers refuse; an Origin\n * (when present) must name the same hostname.\n */\nexport function isTrustedApiRequest(request: { headers: HttpHeadersLike }): boolean {\n const host = header(request.headers, 'host')\n if (host === undefined) return false\n const hostUrl = parseAuthority(host)\n if (hostUrl === undefined || !isLoopbackHostname(hostUrl.hostname)) return false\n if (header(request.headers, 'sec-fetch-site') === 'cross-site') return false\n const origin = header(request.headers, 'origin')\n if (origin === undefined) return true\n try {\n return new URL(origin).hostname === hostUrl.hostname\n } catch {\n return false\n }\n}\n\n// \u2500\u2500 Route helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction sendJson(res: HttpResponseLike, status: number, body: unknown): void {\n const text = JSON.stringify(body)\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'content-length': String(Buffer.byteLength(text)),\n })\n res.end(text)\n}\n\n/** Sentinel for an oversized request body (mapped to 413). */\nconst BODY_TOO_LARGE = Symbol('trail-body-too-large')\n\nasync function readJsonBody(req: HttpRequestLike): Promise<TrailEventsRequest | typeof BODY_TOO_LARGE> {\n const declared = header(req.headers, 'content-length')\n if (declared !== undefined) {\n const declaredBytes = Number(declared)\n if (Number.isFinite(declaredBytes) && declaredBytes > MAX_BODY_BYTES) return BODY_TOO_LARGE\n }\n const chunks: Buffer[] = []\n let total = 0\n for await (const chunk of req) {\n const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), 'utf8')\n total += buf.length\n if (total > MAX_BODY_BYTES) return BODY_TOO_LARGE\n chunks.push(buf)\n }\n const text = Buffer.concat(chunks).toString('utf8')\n try {\n const parsed = JSON.parse(text.length > 0 ? text : '{}')\n return parsed !== null && typeof parsed === 'object' ? parsed : {}\n } catch {\n return {}\n }\n}\n\nfunction afterSeqOf(value: unknown): number {\n const n = Number(value)\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0\n}\n\n/** First index whose event seq is strictly greater than afterSeq (ascending seqs). */\nfunction upperBound(events: ReadonlyArray<{ seq?: number }>, afterSeq: number): number {\n let lo = 0\n let hi = events.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n const seq = events[mid].seq\n if (typeof seq === 'number' && seq <= afterSeq) lo = mid + 1\n else hi = mid\n }\n return lo\n}\n\n/** Borrow the live store's frozen snapshot \u2014 O(1) amortized, zero copies. */\nfunction liveSessionOf(ctx: HostCtx, sessionId: string): LiveSessionLike | undefined {\n const sessions = ctx.sessions\n if (sessions === undefined || typeof sessions.get !== 'function') return undefined\n try {\n const session = sessions.get(sessionId)\n if (session === null || session === undefined || typeof session !== 'object') return undefined\n return Array.isArray((session as { events?: unknown }).events)\n ? (session as LiveSessionLike)\n : undefined\n } catch {\n return undefined\n }\n}\n\n/** Slice one ascending page after afterSeq; `project` trims the wire fields. */\nfunction respondPage(\n res: HttpResponseLike,\n events: ReadonlyArray<SessionEventLike>,\n afterSeq: number,\n project: boolean,\n): void {\n const lo = upperBound(events, afterSeq)\n const end = Math.min(events.length, lo + PAGE_SIZE)\n const tail: SessionEventLike[] = []\n for (let i = lo; i < end; i += 1) tail.push(project ? projectEvent(events[i]) : events[i])\n sendJson(res, 200, { events: tail, hasMore: end < events.length })\n}\n\n/** Insert/refresh one cache entry, then evict past the entry/byte budgets. */\nfunction cacheStore(\n sessionId: string,\n lastSeq: number,\n events: SessionEventLike[],\n bytes: number,\n probedAt: number,\n): void {\n const existing = trailCache.get(sessionId)\n if (existing !== undefined) {\n trailCache.delete(sessionId)\n cacheBytes -= existing.bytes\n }\n trailCache.set(sessionId, { lastSeq, probedAt, events, bytes })\n cacheBytes += bytes\n while (trailCache.size > CACHE_MAX_ENTRIES || (cacheBytes > CACHE_MAX_BYTES && trailCache.size > 1)) {\n const oldest = trailCache.keys().next().value\n if (oldest === undefined) break\n const entry = trailCache.get(oldest)\n trailCache.delete(oldest)\n cacheBytes -= entry?.bytes ?? 0\n }\n}\n\nexport function apply(ctx: HostCtx): void {\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: '/turntrail/api/events',\n handler: async (rawReq, rawRes) => {\n const req = rawReq as HttpRequestLike\n const res = rawRes as HttpResponseLike\n\n if (!isTrustedApiRequest({ headers: req.headers })) {\n sendJson(res, 403, { error: 'forbidden' })\n return\n }\n if (req.method !== 'POST') {\n sendJson(res, 405, { error: 'method not allowed' })\n return\n }\n const body = await readJsonBody(req)\n if (body === BODY_TOO_LARGE) {\n sendJson(res, 413, { error: 'payload too large' })\n return\n }\n const sessionId = typeof body.sessionId === 'string' ? body.sessionId : ''\n if (sessionId.length === 0) {\n sendJson(res, 400, { error: 'sessionId is required' })\n return\n }\n const afterSeq = afterSeqOf(body.afterSeq)\n\n try {\n // Live fast path: synchronous, O(1) probe, O(delta) projection.\n const live = liveSessionOf(ctx, sessionId)\n if (live !== undefined) {\n respondPage(res, live.events, afterSeq, true)\n return\n }\n\n // Persisted path. The listEvents probe costs a whole-log read +\n // per-event structuredClone inside sessionQuery, so its result is\n // cached behind PROBE_TTL_MS: a multi-page drain (each page = one\n // request) probes once instead of once per page.\n const cached = trailCache.get(sessionId)\n let lastSeq = -1\n let probedAt = 0\n if (cached !== undefined && Date.now() - cached.probedAt < PROBE_TTL_MS) {\n // Fresh probe reuse: trust the cached lastSeq, skip the round-trip.\n lastSeq = cached.lastSeq\n probedAt = cached.probedAt\n } else {\n try {\n const records = await ctx.sessionQuery.listEvents(sessionId)\n const last = records[records.length - 1]\n lastSeq = last !== undefined && typeof last.seq === 'number' ? last.seq : -1\n probedAt = Date.now()\n } catch {\n lastSeq = -1\n probedAt = Date.now()\n }\n }\n let base: SessionEventLike[]\n if (cached !== undefined && lastSeq >= 0 && cached.lastSeq === lastSeq) {\n base = cached.events\n if (probedAt > cached.probedAt) cached.probedAt = probedAt\n // LRU touch: re-insert at the fresh end.\n trailCache.delete(sessionId)\n trailCache.set(sessionId, cached)\n } else {\n const snapshot = await ctx.sessionQuery.readSession(sessionId)\n const raw = Array.isArray(snapshot.events) ? snapshot.events : []\n // Project once, accumulating wire bytes per event in the same pass \u2014\n // no separate whole-log JSON.stringify just for the byte budget.\n let bytes = 0\n base = raw.map((event) => {\n const projected = projectEvent(event)\n bytes += Buffer.byteLength(JSON.stringify(projected))\n return projected\n })\n cacheStore(sessionId, lastSeq, base, bytes, probedAt)\n }\n // base is already projected (idempotently); serve without re-projecting.\n respondPage(res, base, afterSeq, false)\n } catch {\n sendJson(res, 404, { error: 'session-not-found' })\n }\n },\n }))\n}\n"],
4
+ "sourcesContent": ["/**\n * wire.ts \u2014 host-side response shaping for POST /turn-outline/api/events.\n *\n * The client fold needs only a small, typed subset of each event. Every\n * response event is projected to that subset BEFORE serialization, so the\n * wire (and the client's retained state) never carries full tool results,\n * reasoning blocks, or unbounded message text.\n *\n * Projection is idempotent and lossless for the fold's needs:\n * - `seq` / `type` / `time` are always preserved (the seq watermark and\n * turn identity ride on them).\n * - text fields are clipped to TEXT_CAP; tool arguments to ARGS_CAP.\n * - content block arrays keep ONLY non-empty text blocks, in order (the\n * fold reads exactly the first / last text block).\n * - `tool/result` message content (the bulky part of real logs) is dropped\n * entirely \u2014 the fold consumes only `error.name` / `error.code`.\n */\nimport type { SessionEventLike } from './context-types'\n\n/** Max characters carried per text field (input / output / summary). */\nexport const TEXT_CAP = 2048\n\n/** Max characters of tool-call arguments (the fold only previews 120). */\nexport const ARGS_CAP = 512\n\n/** Max events per response page; the client loops while `hasMore` is true. */\nexport const PAGE_SIZE = 2000\n\n/** Hard body cap for the route's request payload. */\nexport const MAX_BODY_BYTES = 64 * 1024\n\nfunction clipText(value: string, cap: number): string {\n return value.length > cap ? `${value.slice(0, cap)}\u2026` : value\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return value !== null && typeof value === 'object' ? (value as Record<string, unknown>) : undefined\n}\n\n/**\n * Project one content value (bare string or block array) onto the fold's\n * needs: clipped text, or a text-blocks-only array. Returns undefined when\n * nothing foldable survives (the fold then treats the field as absent,\n * which matches its behavior for empty/non-text content).\n */\nfunction projectContent(content: unknown, cap: number): string | Array<{ type: 'text'; text: string }> | undefined {\n if (typeof content === 'string') return content.length > 0 ? clipText(content, cap) : undefined\n if (!Array.isArray(content)) return undefined\n const out: Array<{ type: 'text'; text: string }> = []\n for (const block of content) {\n const b = record(block)\n if (b === undefined || b.type !== 'text' || typeof b.text !== 'string' || b.text.length === 0) continue\n out.push({ type: 'text', text: clipText(b.text, cap) })\n }\n return out.length > 0 ? out : undefined\n}\n\nfunction sourceOf(data: Record<string, unknown>): Record<string, unknown> | undefined {\n const message = record(data.message)\n if (message !== undefined) {\n const source = record(message.source)\n if (source !== undefined) return source\n }\n return record(data.source)\n}\n\n/** Copy only the source fields the fold reads (kind / callId). */\nfunction projectSourceFields(source: Record<string, unknown>): Record<string, unknown> | undefined {\n const out: Record<string, unknown> = {}\n if (typeof source.kind === 'string') out.kind = source.kind\n if (typeof source.callId === 'string' && source.callId.length > 0) out.callId = source.callId\n return Object.keys(out).length > 0 ? out : undefined\n}\n\n/**\n * Project one session event onto the fold-relevant wire subset.\n * See the module header for the field policy.\n */\nexport function projectEvent(event: SessionEventLike): SessionEventLike {\n const base: SessionEventLike = {\n type: event.type,\n seq: event.seq,\n ...(typeof event.time === 'number' ? { time: event.time } : {}),\n }\n const data = record(event.data)\n if (data === undefined) return base\n\n switch (event.type) {\n case 'turn/start':\n case 'turn/end':\n return typeof data.turn === 'number' ? { ...base, data: { turn: data.turn } } : base\n\n case 'user/message': {\n const out: Record<string, unknown> = {}\n const content = projectContent(data.content, TEXT_CAP)\n if (content !== undefined) out.content = content\n const message = record(data.message)\n if (message !== undefined) {\n const m: Record<string, unknown> = {}\n if (typeof message.content === 'string' && message.content.length > 0) {\n m.content = clipText(message.content, TEXT_CAP)\n }\n const source = record(message.source)\n if (source !== undefined) {\n const s = projectSourceFields(source)\n if (s !== undefined) m.source = s\n }\n if (Object.keys(m).length > 0) out.message = m\n }\n const source = record(data.source)\n if (source !== undefined) {\n const s = projectSourceFields(source)\n if (s !== undefined) out.source = s\n }\n return Object.keys(out).length > 0 ? { ...base, data: out } : base\n }\n\n case 'assistant/message': {\n const out: Record<string, unknown> = {}\n const message = record(data.message)\n if (message !== undefined) {\n const content = projectContent(message.content, TEXT_CAP)\n if (content !== undefined) out.message = { content }\n }\n // The fold falls back to data.content when message.content is absent.\n const fallback = projectContent(data.content, TEXT_CAP)\n if (fallback !== undefined) out.content = fallback\n return Object.keys(out).length > 0 ? { ...base, data: out } : base\n }\n\n case 'tool/call': {\n const out: Record<string, unknown> = {}\n if (typeof data.name === 'string' && data.name.length > 0) out.name = data.name\n if (typeof data.arguments === 'string' && data.arguments.length > 0) {\n out.arguments = clipText(data.arguments, ARGS_CAP)\n }\n if (typeof data.callId === 'string' && data.callId.length > 0) out.callId = data.callId\n const source = sourceOf(data)\n if (source !== undefined) {\n const s = projectSourceFields(source)\n if (s !== undefined && typeof s.callId === 'string') out.message = { source: s }\n }\n return Object.keys(out).length > 0 ? { ...base, data: out } : base\n }\n\n case 'tool/result': {\n // The fold reads only error.name / error.code (plus call ids);\n // message.content \u2014 the bulky payload \u2014 is intentionally dropped.\n const out: Record<string, unknown> = {}\n const error = record(data.error)\n if (error !== undefined) {\n const e: Record<string, unknown> = {}\n if (typeof error.name === 'string' && error.name.length > 0) e.name = error.name\n if (typeof error.code === 'string' && error.code.length > 0) e.code = error.code\n if (Object.keys(e).length > 0) out.error = e\n }\n if (typeof data.callId === 'string' && data.callId.length > 0) out.callId = data.callId\n const source = sourceOf(data)\n if (source !== undefined) {\n const s = projectSourceFields(source)\n if (s !== undefined && typeof s.callId === 'string') out.message = { source: s }\n }\n return Object.keys(out).length > 0 ? { ...base, data: out } : base\n }\n\n case 'compaction/summary': {\n const summary = projectContent(data.summary, TEXT_CAP)\n return summary !== undefined ? { ...base, data: { summary } } : base\n }\n\n default:\n // command/run and unknown types: the fold ignores their data.\n return base\n }\n}\n", "/**\n * Host half of dsh-turn-outline: one incremental, read-only route that serves\n * session events for the trail panel, behind a browser-trust fence.\n *\n * POST /turn-outline/api/events { sessionId, afterSeq }\n * \u2192 200 { events, hasMore } (one PAGE_SIZE page, ascending seq)\n * \u2192 400 bad request / missing sessionId\n * \u2192 403 fence rejection\n * \u2192 404 session-not-found\n * \u2192 405 non-POST\n * \u2192 413 oversized request body\n *\n * Two read paths, hottest first:\n *\n * LIVE sessions (the trail's hot case \u2014 the session is generating):\n * borrow the live store's frozen `events` snapshot. The probe is O(1)\n * (`events.at(-1).seq`), the tail slice is O(delta), and the whole path\n * is synchronous \u2014 no await between borrowing the snapshot and writing\n * the response, so no interleaved mutation. Zero deep clones: the store\n * already deep-freezes its events, and `JSON.stringify` reads frozen\n * objects fine. (The sessionQuery equivalents each `structuredClone`\n * EVERY event \u2014 3\u00D7 per request through listEvents + readSession \u2014 which\n * hammered the very process running the agent.)\n *\n * COLD / persisted sessions (idle, pulled only on activation):\n * sessionQuery with a per-session lastSeq cache, exactly as before. The\n * `listEvents` probe is itself a whole-log read + per-event structuredClone\n * inside sessionQuery, so the probe result is cached behind a short TTL\n * (PROBE_TTL_MS): one probe per multi-page drain instead of one per page.\n *\n * inject = ['webServer', 'sessionQuery', 'sessions'] \u2014 all host services;\n * `sessions` is core DSH (sessionQuery itself depends on it), and the code\n * feature-detects it so foreign contexts degrade to the persisted path.\n */\nimport type {\n HostCtx,\n HttpHeadersLike,\n HttpRequestLike,\n HttpResponseLike,\n LiveSessionLike,\n SessionEventLike,\n TrailEventsRequest,\n} from './context-types'\nimport { MAX_BODY_BYTES, PAGE_SIZE, projectEvent } from './wire'\n\nexport const inject = ['webServer', 'sessionQuery', 'sessions']\n\n/** Per-session cache: { lastSeq, events } refreshed only on growth (LRU). */\ninterface CacheEntry {\n lastSeq: number\n /** Time of the last authoritative listEvents probe for this entry. */\n probedAt: number\n events: SessionEventLike[]\n bytes: number\n}\nconst trailCache = new Map<string, CacheEntry>()\nconst CACHE_MAX_ENTRIES = 64\nconst CACHE_MAX_BYTES = 32 * 1024 * 1024\nlet cacheBytes = 0\n\n/**\n * Freshness window for the cached lastSeq probe. A persisted (non-live) log\n * cannot grow in this process \u2014 a session that starts generating turns live\n * and is served by the zero-copy fast path above \u2014 so reusing a recent probe\n * across a multi-page drain is safe; the TTL only bounds the staleness window.\n */\nexport const PROBE_TTL_MS = 2000\n\n// \u2500\u2500 Browser-trust fence (DNS-rebinding / cross-site defense, not auth) \u2500\u2500\u2500\u2500\n\nfunction header(headers: HttpHeadersLike, name: string): string | undefined {\n const value = (headers as Record<string, unknown>)[name]\n return typeof value === 'string' ? value : undefined\n}\n\nfunction parseAuthority(authority: string): URL | undefined {\n try {\n return new URL(`http://${authority}`)\n } catch {\n return undefined\n }\n}\n\nexport function isLoopbackHostname(hostname: string): boolean {\n if (hostname === 'localhost' || hostname === '[::1]') return true\n const parts = hostname.split('.')\n return parts.length === 4\n && parts[0] === '127'\n && parts.every((part) => /^\\d{1,3}$/.test(part) && Number(part) <= 255)\n}\n\n/**\n * Decide whether one request may reach the trail routes. Behaviorally\n * identical to DSH's /api gateway fence (api-request-trust + loopback-hostname):\n * Host must resolve to loopback; cross-site browser markers refuse; an Origin\n * (when present) must name the same hostname.\n */\nexport function isTrustedApiRequest(request: { headers: HttpHeadersLike }): boolean {\n const host = header(request.headers, 'host')\n if (host === undefined) return false\n const hostUrl = parseAuthority(host)\n if (hostUrl === undefined || !isLoopbackHostname(hostUrl.hostname)) return false\n if (header(request.headers, 'sec-fetch-site') === 'cross-site') return false\n const origin = header(request.headers, 'origin')\n if (origin === undefined) return true\n try {\n return new URL(origin).hostname === hostUrl.hostname\n } catch {\n return false\n }\n}\n\n// \u2500\u2500 Route helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction sendJson(res: HttpResponseLike, status: number, body: unknown): void {\n const text = JSON.stringify(body)\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'content-length': String(Buffer.byteLength(text)),\n })\n res.end(text)\n}\n\n/** Sentinel for an oversized request body (mapped to 413). */\nconst BODY_TOO_LARGE = Symbol('trail-body-too-large')\n\nasync function readJsonBody(req: HttpRequestLike): Promise<TrailEventsRequest | typeof BODY_TOO_LARGE> {\n const declared = header(req.headers, 'content-length')\n if (declared !== undefined) {\n const declaredBytes = Number(declared)\n if (Number.isFinite(declaredBytes) && declaredBytes > MAX_BODY_BYTES) return BODY_TOO_LARGE\n }\n const chunks: Buffer[] = []\n let total = 0\n for await (const chunk of req) {\n const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk), 'utf8')\n total += buf.length\n if (total > MAX_BODY_BYTES) return BODY_TOO_LARGE\n chunks.push(buf)\n }\n const text = Buffer.concat(chunks).toString('utf8')\n try {\n const parsed = JSON.parse(text.length > 0 ? text : '{}')\n return parsed !== null && typeof parsed === 'object' ? parsed : {}\n } catch {\n return {}\n }\n}\n\nfunction afterSeqOf(value: unknown): number {\n const n = Number(value)\n return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0\n}\n\n/** First index whose event seq is strictly greater than afterSeq (ascending seqs). */\nfunction upperBound(events: ReadonlyArray<{ seq?: number }>, afterSeq: number): number {\n let lo = 0\n let hi = events.length\n while (lo < hi) {\n const mid = (lo + hi) >> 1\n const seq = events[mid].seq\n if (typeof seq === 'number' && seq <= afterSeq) lo = mid + 1\n else hi = mid\n }\n return lo\n}\n\n/** Borrow the live store's frozen snapshot \u2014 O(1) amortized, zero copies. */\nfunction liveSessionOf(ctx: HostCtx, sessionId: string): LiveSessionLike | undefined {\n const sessions = ctx.sessions\n if (sessions === undefined || typeof sessions.get !== 'function') return undefined\n try {\n const session = sessions.get(sessionId)\n if (session === null || session === undefined || typeof session !== 'object') return undefined\n return Array.isArray((session as { events?: unknown }).events)\n ? (session as LiveSessionLike)\n : undefined\n } catch {\n return undefined\n }\n}\n\n/** Slice one ascending page after afterSeq; `project` trims the wire fields. */\nfunction respondPage(\n res: HttpResponseLike,\n events: ReadonlyArray<SessionEventLike>,\n afterSeq: number,\n project: boolean,\n): void {\n const lo = upperBound(events, afterSeq)\n const end = Math.min(events.length, lo + PAGE_SIZE)\n const tail: SessionEventLike[] = []\n for (let i = lo; i < end; i += 1) tail.push(project ? projectEvent(events[i]) : events[i])\n sendJson(res, 200, { events: tail, hasMore: end < events.length })\n}\n\n/** Insert/refresh one cache entry, then evict past the entry/byte budgets. */\nfunction cacheStore(\n sessionId: string,\n lastSeq: number,\n events: SessionEventLike[],\n bytes: number,\n probedAt: number,\n): void {\n const existing = trailCache.get(sessionId)\n if (existing !== undefined) {\n trailCache.delete(sessionId)\n cacheBytes -= existing.bytes\n }\n trailCache.set(sessionId, { lastSeq, probedAt, events, bytes })\n cacheBytes += bytes\n while (trailCache.size > CACHE_MAX_ENTRIES || (cacheBytes > CACHE_MAX_BYTES && trailCache.size > 1)) {\n const oldest = trailCache.keys().next().value\n if (oldest === undefined) break\n const entry = trailCache.get(oldest)\n trailCache.delete(oldest)\n cacheBytes -= entry?.bytes ?? 0\n }\n}\n\nexport function apply(ctx: HostCtx): void {\n ctx.effect(() => ctx.webServer.register({\n kind: 'exact',\n path: '/turn-outline/api/events',\n handler: async (rawReq, rawRes) => {\n const req = rawReq as HttpRequestLike\n const res = rawRes as HttpResponseLike\n\n if (!isTrustedApiRequest({ headers: req.headers })) {\n sendJson(res, 403, { error: 'forbidden' })\n return\n }\n if (req.method !== 'POST') {\n sendJson(res, 405, { error: 'method not allowed' })\n return\n }\n const body = await readJsonBody(req)\n if (body === BODY_TOO_LARGE) {\n sendJson(res, 413, { error: 'payload too large' })\n return\n }\n const sessionId = typeof body.sessionId === 'string' ? body.sessionId : ''\n if (sessionId.length === 0) {\n sendJson(res, 400, { error: 'sessionId is required' })\n return\n }\n const afterSeq = afterSeqOf(body.afterSeq)\n\n try {\n // Live fast path: synchronous, O(1) probe, O(delta) projection.\n const live = liveSessionOf(ctx, sessionId)\n if (live !== undefined) {\n respondPage(res, live.events, afterSeq, true)\n return\n }\n\n // Persisted path. The listEvents probe costs a whole-log read +\n // per-event structuredClone inside sessionQuery, so its result is\n // cached behind PROBE_TTL_MS: a multi-page drain (each page = one\n // request) probes once instead of once per page.\n const cached = trailCache.get(sessionId)\n let lastSeq = -1\n let probedAt = 0\n if (cached !== undefined && Date.now() - cached.probedAt < PROBE_TTL_MS) {\n // Fresh probe reuse: trust the cached lastSeq, skip the round-trip.\n lastSeq = cached.lastSeq\n probedAt = cached.probedAt\n } else {\n try {\n const records = await ctx.sessionQuery.listEvents(sessionId)\n const last = records[records.length - 1]\n lastSeq = last !== undefined && typeof last.seq === 'number' ? last.seq : -1\n probedAt = Date.now()\n } catch {\n lastSeq = -1\n probedAt = Date.now()\n }\n }\n let base: SessionEventLike[]\n if (cached !== undefined && lastSeq >= 0 && cached.lastSeq === lastSeq) {\n base = cached.events\n if (probedAt > cached.probedAt) cached.probedAt = probedAt\n // LRU touch: re-insert at the fresh end.\n trailCache.delete(sessionId)\n trailCache.set(sessionId, cached)\n } else {\n const snapshot = await ctx.sessionQuery.readSession(sessionId)\n const raw = Array.isArray(snapshot.events) ? snapshot.events : []\n // Project once, accumulating wire bytes per event in the same pass \u2014\n // no separate whole-log JSON.stringify just for the byte budget.\n let bytes = 0\n base = raw.map((event) => {\n const projected = projectEvent(event)\n bytes += Buffer.byteLength(JSON.stringify(projected))\n return projected\n })\n cacheStore(sessionId, lastSeq, base, bytes, probedAt)\n }\n // base is already projected (idempotently); serve without re-projecting.\n respondPage(res, base, afterSeq, false)\n } catch {\n sendJson(res, 404, { error: 'session-not-found' })\n }\n },\n }))\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["source"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-turn-outline",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Turn-outline sidebar tab for dsh-better-sidebar: fold a session into user turns (input + tool steps + output) with structured jump-back into the conversation | DSH 轮次轨迹侧边栏:按用户轮次折叠会话(输入+工具步骤+输出),一键定位回对话原位",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -27,7 +27,7 @@
27
27
  }
28
28
  },
29
29
  "peerDependencies": {
30
- "dsh-better-sidebar": "^0.17.0",
30
+ "dsh-better-sidebar": "^0.17.0 || ^0.18.0",
31
31
  "react": "^18.2.0"
32
32
  },
33
33
  "peerDependenciesMeta": {