| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import { ThreadId, TurnItemId, type OrchestrationV2TurnItem } from "@t3tools/contracts"; |
| 4 | import * as DateTime from "effect/DateTime"; |
| 5 | import { T3_MCP_TOOL_NAMES } from "@t3tools/shared/t3McpToolPresentation"; |
| 6 | |
| 7 | import { |
| 8 | commandDetailRepeatsCommand, |
| 9 | contextCompactionLabel, |
| 10 | extractCommandOutputText, |
| 11 | liveThoughtLine, |
| 12 | resolveViewedImageAsset, |
| 13 | resolveWorkEntryToolPresentation, |
| 14 | summarizeToolGroup, |
| 15 | toolGroupAction, |
| 16 | toolGroupSummaryKind, |
| 17 | toolItemForDisplay, |
| 18 | type WorkLogPresentationEntry, |
| 19 | type WorkLogToolLifecycleStatus, |
| 20 | workEntryViewedImagePath, |
| 21 | workEntryIndicatesToolFailure, |
| 22 | workEntryDisplayIndicatesToolFailure, |
| 23 | workEntryIndicatesToolSuccess, |
| 24 | } from "./presentation.js"; |
| 25 | |
| 26 | describe("contextCompactionLabel", () => { |
| 27 | it.each(["pending", "running", "waiting"] as const)("reports %s as active", (status) => { |
| 28 | expect(contextCompactionLabel({ status })).toBe("Compacting context"); |
| 29 | }); |
| 30 | |
| 31 | it.each(["failed", "cancelled", "interrupted"] as const)( |
| 32 | "does not report %s compaction as successful even with token counts", |
| 33 | (status) => { |
| 34 | expect(contextCompactionLabel({ status, beforeTokenCount: 1000, afterTokenCount: 100 })).toBe( |
| 35 | status === "failed" ? "Context compaction failed" : "Context compaction stopped", |
| 36 | ); |
| 37 | }, |
| 38 | ); |
| 39 | }); |
| 40 | |
| 41 | function commandItem( |
| 42 | fields: Partial<Extract<OrchestrationV2TurnItem, { type: "command_execution" }>> = {}, |
| 43 | ): OrchestrationV2TurnItem { |
| 44 | return { |
| 45 | id: TurnItemId.make("command"), |
| 46 | threadId: ThreadId.make("thread"), |
| 47 | runId: null, |
| 48 | nodeId: null, |
| 49 | providerThreadId: null, |
| 50 | providerTurnId: null, |
| 51 | nativeItemRef: null, |
| 52 | parentItemId: null, |
| 53 | ordinal: 1, |
| 54 | status: "completed", |
| 55 | title: null, |
| 56 | startedAt: null, |
| 57 | completedAt: null, |
| 58 | updatedAt: DateTime.makeUnsafe("2026-09-08T00:00:00.000Z"), |
| 59 | type: "command_execution", |
| 60 | input: 'rg "command not found"', |
| 61 | exitCode: 0, |
| 62 | ...fields, |
| 63 | }; |
| 64 | } |
| 65 | |
| 66 | describe("workEntryIndicatesToolFailure", () => { |
| 67 | const base = { |
| 68 | id: "w1", |
| 69 | createdAt: "2026-01-01T00:00:00.000Z", |
| 70 | label: "Read", |
| 71 | }; |
| 72 | |
| 73 | it.each([ |
| 74 | [{ outputIndicatesFailure: true }, true], |
| 75 | [{ exitCode: 2 }, true], |
| 76 | [{ output: "sh: missing-command: command not found" }, true], |
| 77 | [{ output: "Found 3 matches" }, false], |
| 78 | [{ output: `${"x".repeat(32_768)} command not found` }, false], |
| 79 | [{}, false], |
| 80 | ] as const)( |
| 81 | "preserves command failure state after removing displayed output: %j", |
| 82 | (fields, failed) => { |
| 83 | const structuredPayload = commandItem(fields); |
| 84 | const entry: WorkLogPresentationEntry = { |
| 85 | ...base, |
| 86 | tone: "tool", |
| 87 | itemType: "command_execution", |
| 88 | toolLifecycleStatus: "completed", |
| 89 | structuredPayload, |
| 90 | }; |
| 91 | expect(workEntryDisplayIndicatesToolFailure(entry)).toBe(failed); |
| 92 | expect(workEntryIndicatesToolSuccess(entry)).toBe(!failed); |
| 93 | expect(JSON.stringify(toolItemForDisplay(structuredPayload))).not.toContain('"output":'); |
| 94 | }, |
| 95 | ); |
| 96 | |
| 97 | it("is true for error tone", () => { |
| 98 | expect( |
| 99 | workEntryIndicatesToolFailure({ |
| 100 | ...base, |
| 101 | tone: "error", |
| 102 | detail: "nothing special", |
| 103 | }), |
| 104 | ).toBe(true); |
| 105 | }); |
| 106 | |
| 107 | it("is true when lifecycle says failed even if detail is empty", () => { |
| 108 | expect( |
| 109 | workEntryIndicatesToolFailure({ |
| 110 | ...base, |
| 111 | tone: "tool", |
| 112 | toolLifecycleStatus: "failed", |
| 113 | }), |
| 114 | ).toBe(true); |
| 115 | }); |
| 116 | |
| 117 | it("detects file-not-found style tool output with completed lifecycle", () => { |
| 118 | expect( |
| 119 | workEntryIndicatesToolFailure({ |
| 120 | ...base, |
| 121 | tone: "tool", |
| 122 | toolLifecycleStatus: "completed", |
| 123 | detail: "File not found: C:\\foo\\nonexistent.ts", |
| 124 | }), |
| 125 | ).toBe(true); |
| 126 | }); |
| 127 | |
| 128 | it("detects glob no files and PowerShell command errors", () => { |
| 129 | expect( |
| 130 | workEntryIndicatesToolFailure({ |
| 131 | ...base, |
| 132 | label: "Glob", |
| 133 | tone: "tool", |
| 134 | detail: "No files found", |
| 135 | }), |
| 136 | ).toBe(true); |
| 137 | expect( |
| 138 | workEntryIndicatesToolFailure({ |
| 139 | ...base, |
| 140 | label: "Bash", |
| 141 | tone: "tool", |
| 142 | detail: |
| 143 | "The term 'this_is_not_a_command' is not recognized as the name of a cmdlet, function, script file, or operable program.", |
| 144 | }), |
| 145 | ).toBe(true); |
| 146 | }); |
| 147 | |
| 148 | it("is false for successful completed tools", () => { |
| 149 | expect( |
| 150 | workEntryIndicatesToolFailure({ |
| 151 | ...base, |
| 152 | tone: "tool", |
| 153 | toolLifecycleStatus: "completed", |
| 154 | detail: "Found 3 matching files", |
| 155 | }), |
| 156 | ).toBe(false); |
| 157 | }); |
| 158 | |
| 159 | it("does not treat error text in a command as rendered failure", () => { |
| 160 | const entry = { |
| 161 | ...base, |
| 162 | label: "Ran command", |
| 163 | tone: "tool", |
| 164 | toolLifecycleStatus: "completed", |
| 165 | command: 'rg "file not found"', |
| 166 | detail: "Found 3 matches", |
| 167 | } satisfies WorkLogPresentationEntry; |
| 168 | |
| 169 | expect(workEntryDisplayIndicatesToolFailure(entry)).toBe(false); |
| 170 | // Older activities can store output in this field, so that path stays separate. |
| 171 | expect(workEntryIndicatesToolFailure(entry)).toBe(true); |
| 172 | expect(workEntryDisplayIndicatesToolFailure({ ...entry, detail: "File not found" })).toBe(true); |
| 173 | }); |
| 174 | |
| 175 | it("treats successful tool rows as success candidates", () => { |
| 176 | expect( |
| 177 | workEntryIndicatesToolSuccess({ |
| 178 | ...base, |
| 179 | tone: "tool", |
| 180 | toolLifecycleStatus: "completed", |
| 181 | detail: "ok", |
| 182 | }), |
| 183 | ).toBe(true); |
| 184 | expect( |
| 185 | workEntryIndicatesToolSuccess({ |
| 186 | ...base, |
| 187 | tone: "tool", |
| 188 | toolLifecycleStatus: "inProgress", |
| 189 | detail: "…", |
| 190 | }), |
| 191 | ).toBe(false); |
| 192 | expect(workEntryIndicatesToolSuccess({ ...base, tone: "thinking", detail: "…" })).toBe(false); |
| 193 | expect( |
| 194 | workEntryIndicatesToolSuccess({ ...base, tone: "tool", toolLifecycleStatus: "stopped" }), |
| 195 | ).toBe(false); |
| 196 | expect( |
| 197 | workEntryIndicatesToolSuccess({ ...base, tone: "tool", toolLifecycleStatus: "idle" }), |
| 198 | ).toBe(false); |
| 199 | }); |
| 200 | |
| 201 | it("does not run heuristics on non-tool info rows", () => { |
| 202 | expect( |
| 203 | workEntryIndicatesToolFailure({ |
| 204 | ...base, |
| 205 | label: "Context compacted", |
| 206 | tone: "info", |
| 207 | detail: "File not found in conversation", |
| 208 | }), |
| 209 | ).toBe(false); |
| 210 | }); |
| 211 | }); |
| 212 | |
| 213 | describe("summarizeToolGroup", () => { |
| 214 | const entry = ( |
| 215 | id: string, |
| 216 | overrides: Partial<WorkLogPresentationEntry>, |
| 217 | ): WorkLogPresentationEntry => ({ |
| 218 | id, |
| 219 | createdAt: "2026-09-01T00:00:00Z", |
| 220 | label: "Tool call", |
| 221 | tone: "tool", |
| 222 | ...overrides, |
| 223 | }); |
| 224 | |
| 225 | it("excludes reasoning from mixed tool counts and icons", () => { |
| 226 | const thought = entry("thought", { |
| 227 | itemType: "reasoning", |
| 228 | tone: "thinking", |
| 229 | detail: "Check the source", |
| 230 | }); |
| 231 | const command = entry("command", { itemType: "command_execution", command: "vp test run" }); |
| 232 | expect(summarizeToolGroup([thought, command, { ...thought, id: "thought-2" }])).toEqual({ |
| 233 | summary: "Ran 1 command", |
| 234 | hasFailure: false, |
| 235 | }); |
| 236 | expect(toolGroupSummaryKind([thought, command])).toBe("command"); |
| 237 | expect(summarizeToolGroup([thought]).summary).toBe("Thought"); |
| 238 | expect(summarizeToolGroup([thought, { ...thought, id: "thought-2" }]).summary).toBe( |
| 239 | "Thought (×2)", |
| 240 | ); |
| 241 | expect(toolGroupSummaryKind([thought])).toBe("reasoning"); |
| 242 | }); |
| 243 | |
| 244 | it("counts created threads alongside adjacent commands", () => { |
| 245 | expect( |
| 246 | summarizeToolGroup([ |
| 247 | entry("command", { itemType: "command_execution", command: "vp test run" }), |
| 248 | entry("created", { |
| 249 | itemType: "thread_created", |
| 250 | |
| 251 | label: "Created thread", |
| 252 | }), |
| 253 | ]).summary, |
| 254 | ).toBe("Ran 1 command and created 1 thread"); |
| 255 | }); |
| 256 | |
| 257 | it("deduplicates named sources ahead of ordinary actions", () => { |
| 258 | const source = { key: "browser-use:chrome", name: "Chrome", kind: "integration" as const }; |
| 259 | expect( |
| 260 | summarizeToolGroup([ |
| 261 | entry("open", { label: "Open page", toolSource: source }), |
| 262 | entry("inspect", { label: "Inspect page", toolSource: source }), |
| 263 | entry("command", { |
| 264 | label: "Ran command", |
| 265 | itemType: "command_execution", |
| 266 | command: "git status", |
| 267 | }), |
| 268 | ]).summary, |
| 269 | ).toBe("Used Chrome integration and ran 1 command"); |
| 270 | }); |
| 271 | |
| 272 | it.each([ |
| 273 | [["Foo", "Foo"], "Used Foo integration and ran 1 command"], |
| 274 | [["Foo", "Bar", "Foo"], "Used Foo and Bar integrations and ran 1 command"], |
| 275 | ] as const)( |
| 276 | "uses the displayed source names for integration plurality: %j", |
| 277 | (names, expected) => { |
| 278 | expect( |
| 279 | summarizeToolGroup([ |
| 280 | ...names.map((name, index) => |
| 281 | entry(`integration-${index}`, { |
| 282 | toolSource: { key: `integration-${index}`, name, kind: "integration" }, |
| 283 | }), |
| 284 | ), |
| 285 | entry("command", { |
| 286 | itemType: "command_execution", |
| 287 | command: "git status", |
| 288 | }), |
| 289 | ]).summary, |
| 290 | ).toBe(expected); |
| 291 | }, |
| 292 | ); |
| 293 | |
| 294 | it("omits the integration suffix for special browser and computer sources", () => { |
| 295 | expect( |
| 296 | summarizeToolGroup([ |
| 297 | entry("inspect", { |
| 298 | label: "Inspect page", |
| 299 | toolSource: { key: "browser-use", name: "Browser", kind: "browser" }, |
| 300 | }), |
| 301 | entry("click", { |
| 302 | label: "Click", |
| 303 | toolSource: { key: "computer-use", name: "Computer Use", kind: "computer" }, |
| 304 | }), |
| 305 | ]).summary, |
| 306 | ).toBe("Used Browser and Computer Use"); |
| 307 | }); |
| 308 | |
| 309 | it("names Computer Use once when native-app and fallback sources share its label", () => { |
| 310 | expect( |
| 311 | summarizeToolGroup([ |
| 312 | entry("inspect-app", { |
| 313 | label: "Inspect app", |
| 314 | toolSource: { |
| 315 | key: "native-app:com.t3tools.t3code", |
| 316 | name: "Computer Use", |
| 317 | kind: "computer", |
| 318 | }, |
| 319 | }), |
| 320 | entry("failed-sky", { |
| 321 | label: "Restrict the breakpoint to Git actions", |
| 322 | toolSource: { key: "computer-use", name: "Computer Use", kind: "computer" }, |
| 323 | }), |
| 324 | ]).summary, |
| 325 | ).toBe("Used Computer Use"); |
| 326 | }); |
| 327 | }); |
| 328 | |
| 329 | describe("resolveWorkEntryToolPresentation", () => { |
| 330 | it("presents and summarizes every T3 tool using the same structured identity", () => { |
| 331 | for (const tool of T3_MCP_TOOL_NAMES) { |
| 332 | const entry: WorkLogPresentationEntry = { |
| 333 | id: tool, |
| 334 | createdAt: "2026-09-19T00:00:00.000Z", |
| 335 | tone: "tool", |
| 336 | label: "Custom provider title", |
| 337 | toolData: { server: "t3-code", tool }, |
| 338 | toolLifecycleStatus: "completed", |
| 339 | itemType: "dynamic_tool", |
| 340 | toolSource: { key: "t3-code", name: "T3 Code", kind: "integration" }, |
| 341 | }; |
| 342 | const presentation = resolveWorkEntryToolPresentation(entry); |
| 343 | expect(presentation, tool).not.toBeNull(); |
| 344 | expect(presentation?.displayName, tool).not.toContain(tool); |
| 345 | const summary = summarizeToolGroup([entry]); |
| 346 | expect(summary.summary, tool).not.toMatch(/Used (?:1 tool|T3 Code integration)/); |
| 347 | expect(summary.hasFailure, tool).toBe(false); |
| 348 | const failed = { ...entry, toolLifecycleStatus: "failed" as const }; |
| 349 | expect(resolveWorkEntryToolPresentation(failed)?.displayName, tool).toMatch(/^Failed to /); |
| 350 | expect(summarizeToolGroup([failed]).hasFailure, tool).toBe(true); |
| 351 | expect(summarizeToolGroup([failed]).summary, tool).toMatch( |
| 352 | /^(?:Tried to |Requested thread creation)/, |
| 353 | ); |
| 354 | } |
| 355 | }); |
| 356 | |
| 357 | it.each([ |
| 358 | ["t3_project_list", "Listing projects", "Listed projects"], |
| 359 | ["t3_project_clone", "Cloning a repository", "Cloned a repository"], |
| 360 | ["t3_project_create", "Registering a project", "Registered a project"], |
| 361 | ["t3_thread_launch", "Launching a project thread", "Launched a project thread"], |
| 362 | ["t3_queue_edit", "Editing a queued message", "Edited a queued message"], |
| 363 | ["t3_pending_request_respond", "Answering pending questions", "Answered pending questions"], |
| 364 | ["t3_thread_configure", "Setting thread model", "Set thread model"], |
| 365 | ["t3_thread_fork", "Forking this thread", "Requested a fork of this thread"], |
| 366 | ["t3_thread_send_attachments", "Sending attachments", "Sent attachments"], |
| 367 | ["run_scheduled_task_now", "Running a scheduled task", "Requested a run of a scheduled task"], |
| 368 | ["t3_workflow_stop", "Stopping a workflow", "Requested a stop for a workflow"], |
| 369 | ])("labels %s through its lifecycle", (tool, running, completed) => { |
| 370 | expect(resolveWorkEntryToolPresentation({ label: `T3-code.${tool}` })?.displayName).toBe( |
| 371 | running, |
| 372 | ); |
| 373 | expect( |
| 374 | resolveWorkEntryToolPresentation({ |
| 375 | label: `T3-code.${tool}`, |
| 376 | toolLifecycleStatus: "completed", |
| 377 | })?.displayName, |
| 378 | ).toBe(completed); |
| 379 | }); |
| 380 | |
| 381 | it("summarizes workflow stop targets separately from their parent thread", () => { |
| 382 | const entry = { |
| 383 | id: "workflow-stop", |
| 384 | createdAt: "2026-10-07T00:00:00.000Z", |
| 385 | tone: "tool", |
| 386 | label: "Custom title", |
| 387 | itemType: "dynamic_tool", |
| 388 | toolLifecycleStatus: "completed", |
| 389 | toolData: { |
| 390 | server: "t3-code", |
| 391 | tool: "t3_workflow_stop", |
| 392 | arguments: { threadId: "parent", subagentId: "workflow-1" }, |
| 393 | result: {}, |
| 394 | }, |
| 395 | } satisfies WorkLogPresentationEntry; |
| 396 | expect( |
| 397 | summarizeToolGroup([ |
| 398 | entry, |
| 399 | entry, |
| 400 | { |
| 401 | ...entry, |
| 402 | toolData: { |
| 403 | ...entry.toolData, |
| 404 | arguments: { threadId: "parent", subagentId: "workflow-2" }, |
| 405 | }, |
| 406 | }, |
| 407 | { |
| 408 | ...entry, |
| 409 | toolData: { |
| 410 | ...entry.toolData, |
| 411 | tool: "t3_thread_interrupt", |
| 412 | arguments: { threadId: "parent" }, |
| 413 | }, |
| 414 | }, |
| 415 | ]), |
| 416 | ).toEqual({ |
| 417 | summary: "Requested stops for 2 workflows and requested interrupts for 1 thread", |
| 418 | hasFailure: false, |
| 419 | }); |
| 420 | }); |
| 421 | |
| 422 | it("summarizes project tools from MCP arguments and results without claiming failed effects", () => { |
| 423 | const entry: WorkLogPresentationEntry = { |
| 424 | id: "clone", |
| 425 | createdAt: "2026-09-19T00:00:00.000Z", |
| 426 | tone: "tool", |
| 427 | label: "Custom title", |
| 428 | itemType: "dynamic_tool", |
| 429 | toolLifecycleStatus: "completed", |
| 430 | toolData: { |
| 431 | server: "t3-code", |
| 432 | tool: "t3_project_clone", |
| 433 | arguments: { url: "https://github.com/acme/repo" }, |
| 434 | result: { cwd: "/tmp/repo" }, |
| 435 | }, |
| 436 | }; |
| 437 | const list = { ...entry, toolData: { server: "t3-code", tool: "t3_project_list" } }; |
| 438 | expect(summarizeToolGroup([list, entry])).toEqual({ |
| 439 | summary: "Listed projects 1 time and cloned 1 repository", |
| 440 | hasFailure: false, |
| 441 | }); |
| 442 | const failed = { |
| 443 | ...entry, |
| 444 | toolData: { toolName: "T3-code.t3_project_clone", rawOutput: { isError: true } }, |
| 445 | }; |
| 446 | expect(summarizeToolGroup([entry, failed])).toEqual({ |
| 447 | summary: "Cloned 1 repository", |
| 448 | hasFailure: true, |
| 449 | }); |
| 450 | }); |
| 451 | |
| 452 | it("does not summarize a foreign structured identity as T3 work", () => { |
| 453 | const entry: WorkLogPresentationEntry = { |
| 454 | id: "foreign", |
| 455 | createdAt: "2026-09-19T00:00:00.000Z", |
| 456 | tone: "tool", |
| 457 | label: "t3_project_clone", |
| 458 | toolLifecycleStatus: "completed", |
| 459 | toolData: { server: "another-server", tool: "t3_project_clone" }, |
| 460 | }; |
| 461 | expect(summarizeToolGroup([entry]).summary).toBe("Used 1 tool"); |
| 462 | }); |
| 463 | |
| 464 | it("shows returned MCP errors as failures even in the live activity row", () => { |
| 465 | const entry: WorkLogPresentationEntry = { |
| 466 | id: "clone", |
| 467 | createdAt: "2026-09-19T00:00:00.000Z", |
| 468 | tone: "tool", |
| 469 | label: "T3-code.t3_project_clone", |
| 470 | toolLifecycleStatus: "inProgress", |
| 471 | itemType: "dynamic_tool", |
| 472 | toolData: { output: { isError: true } }, |
| 473 | }; |
| 474 | expect(resolveWorkEntryToolPresentation(entry)?.displayName).toBe( |
| 475 | "Failed to clone a repository", |
| 476 | ); |
| 477 | expect(workEntryDisplayIndicatesToolFailure(entry)).toBe(true); |
| 478 | expect(workEntryIndicatesToolSuccess(entry)).toBe(false); |
| 479 | const childFailure = { |
| 480 | ...entry, |
| 481 | label: "T3-code.task_status", |
| 482 | toolLifecycleStatus: "completed" as const, |
| 483 | toolData: { output: { taskId: "child", status: "failed", summary: "command not found" } }, |
| 484 | }; |
| 485 | expect(workEntryDisplayIndicatesToolFailure(childFailure)).toBe(false); |
| 486 | expect(workEntryIndicatesToolSuccess(childFailure)).toBe(true); |
| 487 | }); |
| 488 | it.each([ |
| 489 | "mcp__t3-code__preview_click", |
| 490 | "mcp__t3_code__preview_click", |
| 491 | "mcp__t3code__preview_click", |
| 492 | "T3-code.preview_click", |
| 493 | "t3-code · preview_click completed", |
| 494 | "t3_code/preview_click", |
| 495 | "preview_click", |
| 496 | ])("recognizes browser tool names across providers: %s", (label) => { |
| 497 | expect(resolveWorkEntryToolPresentation({ label })).toEqual({ |
| 498 | displayName: "Clicking in the preview browser", |
| 499 | icon: "browser", |
| 500 | }); |
| 501 | }); |
| 502 | |
| 503 | it("labels device tools with the device icon", () => { |
| 504 | expect( |
| 505 | resolveWorkEntryToolPresentation({ |
| 506 | label: "mcp__t3-code__device_open", |
| 507 | toolLifecycleStatus: "completed", |
| 508 | }), |
| 509 | ).toEqual({ displayName: "Opened a device in the Device panel", icon: "device" }); |
| 510 | expect(resolveWorkEntryToolPresentation({ label: "t3-code · device_screenshot" })).toEqual({ |
| 511 | displayName: "Taking a screenshot of the device", |
| 512 | icon: "device", |
| 513 | }); |
| 514 | }); |
| 515 | |
| 516 | it("uses structured MCP identity when the provider supplies a custom title", () => { |
| 517 | expect( |
| 518 | resolveWorkEntryToolPresentation({ |
| 519 | label: "Tool call complete", |
| 520 | toolTitle: "Inspect the current page", |
| 521 | toolData: { server: "t3-code", tool: "preview_snapshot", result: { title: "Example" } }, |
| 522 | }), |
| 523 | ).toEqual({ displayName: "Taking a snapshot of the preview page", icon: "browser" }); |
| 524 | }); |
| 525 | |
| 526 | it.each([ |
| 527 | ["inProgress", "Clicking in the preview browser"], |
| 528 | ["completed", "Clicked in the preview browser"], |
| 529 | ["failed", "Failed to click in the preview browser"], |
| 530 | ["declined", "Declined to click in the preview browser"], |
| 531 | ["stopped", "Stopped clicking in the preview browser"], |
| 532 | ["unknown", "Clicking in the preview browser"], |
| 533 | ] as const)("describes the tool's own %s state", (toolLifecycleStatus, displayName) => { |
| 534 | expect( |
| 535 | resolveWorkEntryToolPresentation({ |
| 536 | label: "T3-code.preview_click", |
| 537 | toolLifecycleStatus: toolLifecycleStatus as WorkLogToolLifecycleStatus, |
| 538 | }), |
| 539 | ).toEqual({ displayName, icon: "browser" }); |
| 540 | }); |
| 541 | |
| 542 | it("uses the summary's state only when the provider omitted a lifecycle status", () => { |
| 543 | const entry = { label: "T3-code.preview_click" }; |
| 544 | expect(resolveWorkEntryToolPresentation(entry, "inProgress")?.displayName).toBe( |
| 545 | "Clicking in the preview browser", |
| 546 | ); |
| 547 | expect(resolveWorkEntryToolPresentation(entry, "completed")?.displayName).toBe( |
| 548 | "Clicked in the preview browser", |
| 549 | ); |
| 550 | expect( |
| 551 | resolveWorkEntryToolPresentation({ ...entry, toolLifecycleStatus: "completed" }, "inProgress") |
| 552 | ?.displayName, |
| 553 | ).toBe("Clicked in the preview browser"); |
| 554 | expect( |
| 555 | resolveWorkEntryToolPresentation({ ...entry, toolLifecycleStatus: "failed" }, "completed") |
| 556 | ?.displayName, |
| 557 | ).toBe("Failed to click in the preview browser"); |
| 558 | }); |
| 559 | |
| 560 | it.each([ |
| 561 | ["preview_type", "Typing in the preview browser", "Typed in the preview browser"], |
| 562 | [ |
| 563 | "preview_set_appearance", |
| 564 | "Setting preview browser appearance", |
| 565 | "Set preview browser appearance", |
| 566 | ], |
| 567 | [ |
| 568 | "preview_snapshot", |
| 569 | "Taking a snapshot of the preview page", |
| 570 | "Took a snapshot of the preview page", |
| 571 | ], |
| 572 | [ |
| 573 | "preview_recording_stop", |
| 574 | "Stopping recording the preview browser", |
| 575 | "Stopped recording the preview browser", |
| 576 | ], |
| 577 | ["t3_thread_read", "Reading a T3 thread", "Read a T3 thread"], |
| 578 | ["t3_thread_send", "Sending to a T3 thread", "Sent to a T3 thread"], |
| 579 | [ |
| 580 | "t3_worktree_handoff", |
| 581 | "Handing off thread to a git worktree", |
| 582 | "Handed off thread to a git worktree", |
| 583 | ], |
| 584 | ])("preserves verb forms and the rest of %s's label", (tool, running, completed) => { |
| 585 | const entry = { label: `t3-code.${tool}` }; |
| 586 | expect( |
| 587 | resolveWorkEntryToolPresentation({ ...entry, toolLifecycleStatus: "inProgress" }) |
| 588 | ?.displayName, |
| 589 | ).toBe(running); |
| 590 | expect( |
| 591 | resolveWorkEntryToolPresentation({ ...entry, toolLifecycleStatus: "completed" })?.displayName, |
| 592 | ).toBe(completed); |
| 593 | }); |
| 594 | |
| 595 | it("keeps T3 branding for non-browser tools and falls back to the original tool label", () => { |
| 596 | expect( |
| 597 | resolveWorkEntryToolPresentation({ |
| 598 | label: "mcp__t3_code__task_status", |
| 599 | toolTitle: "Check the child task", |
| 600 | }), |
| 601 | ).toEqual({ displayName: "Getting delegated task status", icon: "t3-code" }); |
| 602 | }); |
| 603 | |
| 604 | it("does not brand unknown tools or another server's matching tool name", () => { |
| 605 | for (const label of [ |
| 606 | "mcp__github__preview_click", |
| 607 | "t3-code.unknown_tool", |
| 608 | "t3-code.toString", |
| 609 | "Search files", |
| 610 | ]) { |
| 611 | expect(resolveWorkEntryToolPresentation({ label })).toBeNull(); |
| 612 | } |
| 613 | expect( |
| 614 | resolveWorkEntryToolPresentation({ |
| 615 | label: "preview_click", |
| 616 | toolData: { server: "another-server", tool: "preview_click" }, |
| 617 | }), |
| 618 | ).toBeNull(); |
| 619 | }); |
| 620 | }); |
| 621 | |
| 622 | describe("browser group summaries", () => { |
| 623 | const summarizeGroupLabel = (entries: ReadonlyArray<WorkLogPresentationEntry>) => |
| 624 | summarizeToolGroup(entries).summary; |
| 625 | const browserEntry: WorkLogPresentationEntry = { |
| 626 | id: "browser", |
| 627 | createdAt: "2026-09-01T00:00:00Z", |
| 628 | label: "MCP tool call", |
| 629 | toolData: { server: "t3-code", tool: "preview_click" }, |
| 630 | itemType: "dynamic_tool", |
| 631 | toolLifecycleStatus: "completed", |
| 632 | tone: "tool", |
| 633 | }; |
| 634 | const commandEntry: WorkLogPresentationEntry = { |
| 635 | id: "command", |
| 636 | createdAt: "2026-09-01T00:00:00Z", |
| 637 | label: "Ran command", |
| 638 | command: "/bin/bash -lc 'vp test run'", |
| 639 | itemType: "command_execution", |
| 640 | toolLifecycleStatus: "completed", |
| 641 | tone: "tool", |
| 642 | }; |
| 643 | |
| 644 | it.each([1, 18])("counts %s browser calls separately from generic tools", (count) => { |
| 645 | const entries = Array.from({ length: count }, (_, index) => ({ |
| 646 | ...browserEntry, |
| 647 | toolCallId: `browser-${index}`, |
| 648 | })); |
| 649 | expect(summarizeGroupLabel(entries)).toBe( |
| 650 | `Used browser ${count} ${count === 1 ? "time" : "times"}`, |
| 651 | ); |
| 652 | expect(toolGroupSummaryKind(entries)).toBe("browser"); |
| 653 | }); |
| 654 | |
| 655 | it("combines command and browser counts in a single sentence", () => { |
| 656 | const entries = [ |
| 657 | ...Array.from({ length: 4 }, () => commandEntry), |
| 658 | ...Array.from({ length: 15 }, () => browserEntry), |
| 659 | ]; |
| 660 | expect(summarizeGroupLabel(entries)).toBe("Ran 4 commands and used browser 15 times"); |
| 661 | expect(toolGroupSummaryKind(entries)).toBe("mixed"); |
| 662 | }); |
| 663 | |
| 664 | it("preserves first-seen action ordering alongside non-browser tools", () => { |
| 665 | expect( |
| 666 | summarizeGroupLabel([ |
| 667 | browserEntry, |
| 668 | commandEntry, |
| 669 | { |
| 670 | ...browserEntry, |
| 671 | toolData: { server: "t3-code", tool: "task_status" }, |
| 672 | }, |
| 673 | ]), |
| 674 | ).toBe("Used browser 1 time, ran 1 command, and performed 1 other action"); |
| 675 | }); |
| 676 | |
| 677 | it("recognizes Claude browser identity without treating script metadata as a shell command", () => { |
| 678 | expect( |
| 679 | summarizeGroupLabel([ |
| 680 | { |
| 681 | ...browserEntry, |
| 682 | command: "node inspect-page.js", |
| 683 | toolData: { toolName: "mcp__t3_code__preview_evaluate" }, |
| 684 | }, |
| 685 | ]), |
| 686 | ).toBe("Used browser 1 time"); |
| 687 | }); |
| 688 | |
| 689 | it("keeps foreign tools and web searches out of the browser count", () => { |
| 690 | expect( |
| 691 | summarizeGroupLabel([ |
| 692 | browserEntry, |
| 693 | { |
| 694 | ...browserEntry, |
| 695 | label: "preview_click", |
| 696 | toolData: { server: "another-server", tool: "preview_click" }, |
| 697 | }, |
| 698 | { |
| 699 | id: "search", |
| 700 | createdAt: "2026-09-01T00:00:00Z", |
| 701 | label: "Search", |
| 702 | tone: "tool", |
| 703 | itemType: "web_search", |
| 704 | }, |
| 705 | ]), |
| 706 | ).toBe("Used browser 1 time, searched the web 1 time, and performed 1 other action"); |
| 707 | }); |
| 708 | |
| 709 | it("keeps browser screenshots in the browser count while preserving their image path", () => { |
| 710 | const entry = { ...browserEntry, viewedImagePath: "/workspace/page.png" }; |
| 711 | expect(summarizeGroupLabel([entry])).toBe("Used browser 1 time"); |
| 712 | expect(workEntryViewedImagePath(entry)).toBe("/workspace/page.png"); |
| 713 | }); |
| 714 | }); |
| 715 | |
| 716 | describe("command work-log details", () => { |
| 717 | it("extracts Claude result blocks and projected output", () => { |
| 718 | expect( |
| 719 | extractCommandOutputText({ |
| 720 | result: { |
| 721 | content: [ |
| 722 | { type: "text", text: "first" }, |
| 723 | { type: "text", text: "second" }, |
| 724 | ], |
| 725 | }, |
| 726 | }), |
| 727 | ).toBe("first\nsecond"); |
| 728 | expect(extractCommandOutputText({ rawOutput: { content: "projected summary" } })).toBe( |
| 729 | "projected summary", |
| 730 | ); |
| 731 | }); |
| 732 | |
| 733 | it("only removes a detail with the matching tool-name prefix", () => { |
| 734 | expect( |
| 735 | commandDetailRepeatsCommand({ |
| 736 | detail: "Bash: printf hello", |
| 737 | command: "printf hello", |
| 738 | rawCommand: null, |
| 739 | toolName: "Bash", |
| 740 | data: { toolName: "Bash", command: "printf hello" }, |
| 741 | }), |
| 742 | ).toBe(true); |
| 743 | expect( |
| 744 | commandDetailRepeatsCommand({ |
| 745 | detail: "warning: printf hello", |
| 746 | command: "printf hello", |
| 747 | rawCommand: null, |
| 748 | toolName: "Bash", |
| 749 | data: { toolName: "Bash", command: "printf hello" }, |
| 750 | }), |
| 751 | ).toBe(false); |
| 752 | }); |
| 753 | |
| 754 | it("treats an ingestion-truncated echo of a long command as a repeat", () => { |
| 755 | const command = `git add -A && git commit -m "${"x".repeat(200)}"`; |
| 756 | const truncated = `Bash: ${command}`.slice(0, 177) + "..."; |
| 757 | expect( |
| 758 | commandDetailRepeatsCommand({ |
| 759 | detail: truncated, |
| 760 | command, |
| 761 | rawCommand: null, |
| 762 | toolName: "Bash", |
| 763 | data: { toolName: "Bash", command }, |
| 764 | }), |
| 765 | ).toBe(true); |
| 766 | expect( |
| 767 | commandDetailRepeatsCommand({ |
| 768 | detail: "Bash: printf hello...", |
| 769 | command: "printf goodbye", |
| 770 | rawCommand: null, |
| 771 | toolName: "Bash", |
| 772 | data: { toolName: "Bash", command: "printf goodbye" }, |
| 773 | }), |
| 774 | ).toBe(false); |
| 775 | }); |
| 776 | |
| 777 | it("treats ACP command echoes as synthetic even without a tool kind", () => { |
| 778 | expect( |
| 779 | commandDetailRepeatsCommand({ |
| 780 | detail: "pnpm test", |
| 781 | command: "pnpm test", |
| 782 | rawCommand: null, |
| 783 | toolName: undefined, |
| 784 | data: { toolCallId: "tool-1", command: "pnpm test" }, |
| 785 | }), |
| 786 | ).toBe(true); |
| 787 | expect( |
| 788 | commandDetailRepeatsCommand({ |
| 789 | detail: "pnpm test", |
| 790 | command: "pnpm test", |
| 791 | rawCommand: null, |
| 792 | toolName: undefined, |
| 793 | data: { command: "pnpm test" }, |
| 794 | }), |
| 795 | ).toBe(false); |
| 796 | }); |
| 797 | }); |
| 798 | |
| 799 | describe("workEntryViewedImagePath", () => { |
| 800 | const entry = { |
| 801 | id: "entry-1", |
| 802 | createdAt: "2026-01-01T00:00:00Z", |
| 803 | label: "Read", |
| 804 | tone: "tool", |
| 805 | } as const; |
| 806 | |
| 807 | it("returns a single image path from supported read entries", () => { |
| 808 | expect( |
| 809 | workEntryViewedImagePath({ ...entry, requestKind: "file-read", detail: " assets/a.png " }), |
| 810 | ).toBe("assets/a.png"); |
| 811 | expect( |
| 812 | workEntryViewedImagePath({ |
| 813 | ...entry, |
| 814 | itemType: "dynamic_tool", |
| 815 | toolTitle: "Read file", |
| 816 | detail: "C:\\workspace\\a.webp", |
| 817 | }), |
| 818 | ).toBe("C:\\workspace\\a.webp"); |
| 819 | expect( |
| 820 | workEntryViewedImagePath({ |
| 821 | ...entry, |
| 822 | itemType: "dynamic_tool", |
| 823 | detail: 'Read: {"file_path":"truncated..."}', |
| 824 | viewedImagePath: " /workspace/reference image.webp ", |
| 825 | }), |
| 826 | ).toBe("/workspace/reference image.webp"); |
| 827 | }); |
| 828 | |
| 829 | it("rejects non-image, multi-line, and non-read details", () => { |
| 830 | expect( |
| 831 | workEntryViewedImagePath({ ...entry, requestKind: "file-read", detail: "a.txt" }), |
| 832 | ).toBeNull(); |
| 833 | expect( |
| 834 | workEntryViewedImagePath({ ...entry, requestKind: "file-read", detail: "a.png\nb.png" }), |
| 835 | ).toBeNull(); |
| 836 | expect(workEntryViewedImagePath({ ...entry, detail: "a.png" })).toBeNull(); |
| 837 | }); |
| 838 | }); |
| 839 | |
| 840 | describe("toolGroupAction", () => { |
| 841 | it("groups legacy Claude image reads with other reads", () => { |
| 842 | expect( |
| 843 | toolGroupAction({ |
| 844 | id: "legacy-read", |
| 845 | createdAt: "2026-09-01T00:00:00Z", |
| 846 | label: "Tool call", |
| 847 | tone: "tool", |
| 848 | itemType: "dynamic_tool", |
| 849 | viewedImagePath: "/workspace/reference.png", |
| 850 | }), |
| 851 | ).toBe("read"); |
| 852 | }); |
| 853 | |
| 854 | it("groups Claude Read and Grep from toolName, not file contents", () => { |
| 855 | expect( |
| 856 | toolGroupAction({ |
| 857 | id: "read", |
| 858 | createdAt: "2026-09-01T00:00:00Z", |
| 859 | label: "Read", |
| 860 | tone: "tool", |
| 861 | itemType: "dynamic_tool", |
| 862 | toolTitle: "Read", |
| 863 | toolData: { input: { file_path: "src/env.ts" } }, |
| 864 | structuredPayload: { |
| 865 | type: "dynamic_tool", |
| 866 | toolName: "Read", |
| 867 | input: { file_path: "src/env.ts" }, |
| 868 | } as NonNullable<WorkLogPresentationEntry["structuredPayload"]>, |
| 869 | }), |
| 870 | ).toBe("read"); |
| 871 | expect( |
| 872 | toolGroupAction({ |
| 873 | id: "grep", |
| 874 | createdAt: "2026-09-01T00:00:00Z", |
| 875 | label: "Grep", |
| 876 | tone: "tool", |
| 877 | itemType: "dynamic_tool", |
| 878 | toolTitle: "Grep", |
| 879 | toolData: { input: { pattern: "TODO", path: "apps/web" } }, |
| 880 | structuredPayload: { |
| 881 | type: "dynamic_tool", |
| 882 | toolName: "Grep", |
| 883 | input: { pattern: "TODO", path: "apps/web" }, |
| 884 | } as NonNullable<WorkLogPresentationEntry["structuredPayload"]>, |
| 885 | }), |
| 886 | ).toBe("code-search"); |
| 887 | }); |
| 888 | }); |
| 889 | |
| 890 | describe("resolveViewedImageAsset", () => { |
| 891 | const threadId = ThreadId.make("thread-1"); |
| 892 | |
| 893 | it("serves t3 attachment paths in place like any other host path", () => { |
| 894 | const path = "/Users/demo/.t3/dev/attachments/11111111-1111-4111-8111-111111111111.png"; |
| 895 | expect(resolveViewedImageAsset(path, { threadId, workspaceRoot: "/workspace" })).toEqual({ |
| 896 | resource: { _tag: "media-file", threadId, path }, |
| 897 | alt: "11111111-1111-4111-8111-111111111111.png", |
| 898 | srcFragment: "", |
| 899 | }); |
| 900 | }); |
| 901 | |
| 902 | it("normalizes workspace image sources", () => { |
| 903 | expect( |
| 904 | resolveViewedImageAsset("screens/logo.svg?v=2#mark", { |
| 905 | threadId, |
| 906 | workspaceRoot: "/workspace", |
| 907 | }), |
| 908 | ).toEqual({ |
| 909 | resource: { |
| 910 | _tag: "media-file", |
| 911 | threadId, |
| 912 | path: "/workspace/screens/logo.svg", |
| 913 | }, |
| 914 | alt: "logo.svg", |
| 915 | srcFragment: "#mark", |
| 916 | }); |
| 917 | expect(resolveViewedImageAsset("https://example.com/logo.png", { threadId })).toBeNull(); |
| 918 | }); |
| 919 | }); |
| 920 | |
| 921 | describe("pull request tool presentation", () => { |
| 922 | it.each([ |
| 923 | "mcp__t3-code__link_pull_request", |
| 924 | "mcp__t3_code__link_pull_request", |
| 925 | "T3-code · link_pull_request", |
| 926 | "t3code/link_pull_request", |
| 927 | "link_pull_request", |
| 928 | ])("recognizes the native linking tool: %s", (label) => { |
| 929 | const entry: WorkLogPresentationEntry = { |
| 930 | id: "link", |
| 931 | createdAt: "2026-09-10T00:00:00.000Z", |
| 932 | label, |
| 933 | tone: "tool", |
| 934 | toolLifecycleStatus: "completed", |
| 935 | }; |
| 936 | expect(resolveWorkEntryToolPresentation(entry)).toMatchObject({ |
| 937 | displayName: "Linked a pull request", |
| 938 | icon: "pull-request", |
| 939 | }); |
| 940 | expect(toolGroupAction(entry)).toBe("link-pr"); |
| 941 | }); |
| 942 | |
| 943 | it.each([ |
| 944 | ["inProgress", "Linking PR #42"], |
| 945 | ["completed", "Linked PR #42"], |
| 946 | ["failed", "Failed to link PR #42"], |
| 947 | ["declined", "Declined to link PR #42"], |
| 948 | ["stopped", "Stopped linking PR #42"], |
| 949 | ] as const)("describes the target and %s status", (toolLifecycleStatus, displayName) => { |
| 950 | expect( |
| 951 | resolveWorkEntryToolPresentation({ |
| 952 | label: "MCP tool call", |
| 953 | toolTitle: "Custom title", |
| 954 | toolLifecycleStatus, |
| 955 | toolData: { |
| 956 | server: "t3-code", |
| 957 | tool: "link_pull_request", |
| 958 | arguments: { url: "https://github.com/acme/web/pull/42" }, |
| 959 | }, |
| 960 | })?.displayName, |
| 961 | ).toBe(displayName); |
| 962 | }); |
| 963 | |
| 964 | it("recognizes unlink targets supplied as repository and number", () => { |
| 965 | expect( |
| 966 | resolveWorkEntryToolPresentation({ |
| 967 | label: "MCP tool call", |
| 968 | toolLifecycleStatus: "completed", |
| 969 | toolData: { |
| 970 | toolName: "mcp__t3-code__unlink_pull_request", |
| 971 | rawInput: { repository: "acme/web", number: 42 }, |
| 972 | }, |
| 973 | }), |
| 974 | ).toMatchObject({ displayName: "Unlinked PR #42", icon: "pull-request", action: "unlink-pr" }); |
| 975 | }); |
| 976 | |
| 977 | it("summarizes native PR work separately from ordinary tools and integration metadata", () => { |
| 978 | const link: WorkLogPresentationEntry = { |
| 979 | id: "link", |
| 980 | createdAt: "2026-09-10T00:00:00.000Z", |
| 981 | label: "T3-code · link_pull_request", |
| 982 | tone: "tool", |
| 983 | itemType: "dynamic_tool", |
| 984 | toolLifecycleStatus: "completed", |
| 985 | toolSource: { key: "t3-code", name: "T3 Code", kind: "integration" }, |
| 986 | }; |
| 987 | const list: WorkLogPresentationEntry = { |
| 988 | ...link, |
| 989 | label: "T3-code · list_thread_pull_requests", |
| 990 | }; |
| 991 | expect(summarizeToolGroup([link, link, list]).summary).toBe( |
| 992 | "Linked 2 pull requests and checked linked pull requests", |
| 993 | ); |
| 994 | expect(summarizeToolGroup([{ ...link, label: "T3-code · unlink_pull_request" }]).summary).toBe( |
| 995 | "Unlinked 1 pull request", |
| 996 | ); |
| 997 | expect(toolGroupSummaryKind([link, link, list])).toBe("pull-request"); |
| 998 | expect(summarizeToolGroup([list, list]).summary).toBe("Checked linked pull requests 2 times"); |
| 999 | expect( |
| 1000 | resolveWorkEntryToolPresentation({ label: "mcp__another-server__link_pull_request" }), |
| 1001 | ).toBeNull(); |
| 1002 | }); |
| 1003 | }); |
| 1004 | |
| 1005 | describe("device group summaries", () => { |
| 1006 | const deviceEntry = (tool: string): WorkLogPresentationEntry => ({ |
| 1007 | id: tool, |
| 1008 | createdAt: "2026-09-10T00:00:00.000Z", |
| 1009 | label: "MCP tool call", |
| 1010 | toolData: { server: "t3-code", tool }, |
| 1011 | itemType: "dynamic_tool", |
| 1012 | toolLifecycleStatus: "completed", |
| 1013 | tone: "tool", |
| 1014 | }); |
| 1015 | |
| 1016 | it.each(["device_list", "device_open", "device_screenshot", "device_close"])( |
| 1017 | "recognizes %s as device controls", |
| 1018 | (tool) => { |
| 1019 | const entry = deviceEntry(tool); |
| 1020 | expect(summarizeToolGroup([entry]).summary).toBe("Used device controls 1 time"); |
| 1021 | expect(toolGroupSummaryKind([entry])).toBe("device"); |
| 1022 | }, |
| 1023 | ); |
| 1024 | |
| 1025 | it("summarizes device calls alongside shell commands", () => { |
| 1026 | expect( |
| 1027 | summarizeToolGroup([ |
| 1028 | { |
| 1029 | id: "command", |
| 1030 | createdAt: "2026-09-10T00:00:00.000Z", |
| 1031 | label: "Ran command", |
| 1032 | itemType: "command_execution", |
| 1033 | command: "pwd", |
| 1034 | tone: "tool", |
| 1035 | }, |
| 1036 | deviceEntry("device_list"), |
| 1037 | deviceEntry("device_open"), |
| 1038 | ]).summary, |
| 1039 | ).toBe("Ran 1 command and used device controls 2 times"); |
| 1040 | }); |
| 1041 | |
| 1042 | it("recognizes Claude tool names and preserves screenshot previews", () => { |
| 1043 | const entry = { |
| 1044 | ...deviceEntry("device_screenshot"), |
| 1045 | toolData: { toolName: "mcp__t3_code__device_screenshot" }, |
| 1046 | viewedImagePath: "/workspace/device.png", |
| 1047 | }; |
| 1048 | expect(summarizeToolGroup([entry]).summary).toBe("Used device controls 1 time"); |
| 1049 | expect(workEntryViewedImagePath(entry)).toBe("/workspace/device.png"); |
| 1050 | }); |
| 1051 | |
| 1052 | it("does not classify another server's tools as T3 device controls", () => { |
| 1053 | expect( |
| 1054 | summarizeToolGroup([ |
| 1055 | { |
| 1056 | ...deviceEntry("device_open"), |
| 1057 | toolData: { server: "another-server", tool: "device_open" }, |
| 1058 | }, |
| 1059 | ]).summary, |
| 1060 | ).toBe("Used 1 tool"); |
| 1061 | }); |
| 1062 | }); |
| 1063 | |
| 1064 | describe("liveThoughtLine", () => { |
| 1065 | it("keeps the first sentence, including a closing quote", () => { |
| 1066 | expect( |
| 1067 | liveThoughtLine( |
| 1068 | 'Found the cause: the repo has no commits, so `git worktree add` fails with "invalid reference: main." Now checking the UI.', |
| 1069 | ), |
| 1070 | ).toBe( |
| 1071 | 'Found the cause: the repo has no commits, so git worktree add fails with "invalid reference: main."', |
| 1072 | ); |
| 1073 | }); |
| 1074 | |
| 1075 | it("does not cut at dots inside file names or long dashes", () => { |
| 1076 | expect( |
| 1077 | liveThoughtLine("I read ThreadLaunchService.ts \u2014 it skips the fetch. Next step."), |
| 1078 | ).toBe("I read ThreadLaunchService.ts \u2014 it skips the fetch."); |
| 1079 | }); |
| 1080 | |
| 1081 | it("uses a bold-only opening line as the whole line", () => { |
| 1082 | expect( |
| 1083 | liveThoughtLine("**Narrowing dispatch files**\n\nI should check the adapter. Then more."), |
| 1084 | ).toBe("Narrowing dispatch files"); |
| 1085 | expect(liveThoughtLine("**Narrowing dispatch files**\r\n\r\nI should check the adapter.")).toBe( |
| 1086 | "Narrowing dispatch files", |
| 1087 | ); |
| 1088 | }); |
| 1089 | |
| 1090 | it("returns unpunctuated text whole and flattens markdown", () => { |
| 1091 | expect(liveThoughtLine("- Checking [the docs](https://x.dev) for **limits**")).toBe( |
| 1092 | "Checking the docs for limits", |
| 1093 | ); |
| 1094 | expect(liveThoughtLine("This is *really* ~~not~~ _fine_ in snake_case_names.")).toBe( |
| 1095 | "This is really not fine in snake_case_names.", |
| 1096 | ); |
| 1097 | expect(liveThoughtLine(" ")).toBe(""); |
| 1098 | }); |
| 1099 | }); |