- Pretty-print known T3 MCP tool names in work rows - Show the T3 Code logo for recognized MCP tool entries - Add logic and UI tests for the new tool presentation
Unified · Split
| @@ -8,10 +8,7 @@ import { · 7 unmodified lines | |||
| 8 | liveWorkEntryLabel, | 8 | liveWorkEntryLabel, |
| 9 | normalizeCompactToolLabel, | 9 | normalizeCompactToolLabel, |
| 10 | resolveAssistantMessageCopyState, | 10 | resolveAssistantMessageCopyState, |
| 11 | resolveWorkGroupScrollIndex, | 11 | resolveTimelineToolPresentation, |
| 12 | shouldFollowWorkGroupAppend, | ||
| 13 | shouldPreserveAssistantLineBreaks, | ||
| 14 | workEntryDisplayLabel, | ||
| 15 | } from "./MessagesTimeline.logic"; | 12 | } from "./MessagesTimeline.logic"; |
| 16 | import { | 13 | import { |
| 17 | createMessageAttachmentPreviewProjector, | 14 | createMessageAttachmentPreviewProjector, |
| @@ -354,182 +351,6 @@ describe("streaming row projection", () => { · 336 unmodified lines | |||
| 354 | }); | 351 | }); |
| 355 | }); | 352 | }); |
| 356 | 353 | ||
| 357 | describe("expanded tool group scrolling", () => { | ||
| 358 | const entries = [{ id: "first" }, { id: "second" }]; | ||
| 359 | |||
| 360 | it("follows appended calls only at the hard end", () => { | ||
| 361 | const appended = [...entries, { id: "third" }]; | ||
| 362 | expect(shouldFollowWorkGroupAppend(entries, appended, 0)).toBe(true); | ||
| 363 | expect(shouldFollowWorkGroupAppend(entries, appended, 0.5)).toBe(true); | ||
| 364 | expect(shouldFollowWorkGroupAppend(entries, appended, 1)).toBe(true); | ||
| 365 | expect(shouldFollowWorkGroupAppend(entries, appended, 1.01)).toBe(false); | ||
| 366 | expect(shouldFollowWorkGroupAppend(entries, appended, 10)).toBe(false); | ||
| 367 | expect(shouldFollowWorkGroupAppend(entries, appended, Infinity)).toBe(false); | ||
| 368 | }); | ||
| 369 | |||
| 370 | it("does not follow output updates, prepends, or replacements", () => { | ||
| 371 | expect( | ||
| 372 | shouldFollowWorkGroupAppend( | ||
| 373 | entries, | ||
| 374 | entries.map((entry) => ({ ...entry })), | ||
| 375 | 0, | ||
| 376 | ), | ||
| 377 | ).toBe(false); | ||
| 378 | expect(shouldFollowWorkGroupAppend(entries, [{ id: "older" }, ...entries], 0)).toBe(false); | ||
| 379 | expect( | ||
| 380 | shouldFollowWorkGroupAppend( | ||
| 381 | entries, | ||
| 382 | [{ id: "replacement" }, entries[1]!, { id: "third" }], | ||
| 383 | 0, | ||
| 384 | ), | ||
| 385 | ).toBe(false); | ||
| 386 | expect(shouldFollowWorkGroupAppend([], entries, 0)).toBe(false); | ||
| 387 | }); | ||
| 388 | |||
| 389 | it("restores the visible tool and its offset inside expanded output", () => { | ||
| 390 | const anchor = { entryId: "second", offset: 120 }; | ||
| 391 | expect(resolveWorkGroupScrollIndex(entries, anchor)).toEqual({ index: 1, viewOffset: -120 }); | ||
| 392 | expect(resolveWorkGroupScrollIndex([{ id: "older" }, ...entries], anchor)).toEqual({ | ||
| 393 | index: 2, | ||
| 394 | viewOffset: -120, | ||
| 395 | }); | ||
| 396 | }); | ||
| 397 | |||
| 398 | it("starts normally when the saved tool no longer exists", () => { | ||
| 399 | expect(resolveWorkGroupScrollIndex(entries, undefined)).toBeUndefined(); | ||
| 400 | expect( | ||
| 401 | resolveWorkGroupScrollIndex(entries, { entryId: "removed", offset: 120 }), | ||
| 402 | ).toBeUndefined(); | ||
| 403 | }); | ||
| 404 | }); | ||
| 405 | |||
| 406 | describe("work entry labels", () => { | ||
| 407 | const entry = { | ||
| 408 | id: "tool-1", | ||
| 409 | createdAt: "2026-09-01T12:00:00Z", | ||
| 410 | label: "Tool call", | ||
| 411 | tone: "tool" as const, | ||
| 412 | }; | ||
| 413 | |||
| 414 | it.each([ | ||
| 415 | ["inProgress", "Clicking in the preview browser"], | ||
| 416 | ["completed", "Clicked in the preview browser"], | ||
| 417 | ["failed", "Failed to click in the preview browser"], | ||
| 418 | ["declined", "Declined to click in the preview browser"], | ||
| 419 | ["stopped", "Stopped clicking in the preview browser"], | ||
| 420 | ] as const)("uses the same friendly %s label in both views", (toolLifecycleStatus, label) => { | ||
| 421 | const browserEntry = { | ||
| 422 | ...entry, | ||
| 423 | toolTitle: "T3-code.preview_click", | ||
| 424 | detail: '{"ok":true}', | ||
| 425 | toolLifecycleStatus, | ||
| 426 | }; | ||
| 427 | expect(liveWorkEntryLabel(browserEntry, undefined, toolLifecycleStatus === "inProgress")).toBe( | ||
| 428 | label, | ||
| 429 | ); | ||
| 430 | expect(workEntryDisplayLabel(browserEntry, undefined)).toBe(label); | ||
| 431 | }); | ||
| 432 | |||
| 433 | it("uses the active summary state for legacy tools without a lifecycle status", () => { | ||
| 434 | const browserEntry = { ...entry, toolTitle: "T3-code.preview_click" }; | ||
| 435 | expect(liveWorkEntryLabel(browserEntry, undefined, true)).toBe( | ||
| 436 | "Clicking in the preview browser", | ||
| 437 | ); | ||
| 438 | expect(liveWorkEntryLabel(browserEntry, undefined, false)).toBe( | ||
| 439 | "Clicked in the preview browser", | ||
| 440 | ); | ||
| 441 | }); | ||
| 442 | |||
| 443 | it("does not describe a finished call as still running while the turn continues", () => { | ||
| 444 | const browserEntry = { | ||
| 445 | ...entry, | ||
| 446 | toolTitle: "T3-code.preview_click", | ||
| 447 | toolLifecycleStatus: "completed" as const, | ||
| 448 | }; | ||
| 449 | expect(liveWorkEntryLabel(browserEntry, undefined, true)).toBe( | ||
| 450 | "Clicked in the preview browser", | ||
| 451 | ); | ||
| 452 | }); | ||
| 453 | |||
| 454 | it("keeps custom titles and output for unrecognized tools", () => { | ||
| 455 | const unknownEntry = { ...entry, toolTitle: "mcp__github__search_issues" }; | ||
| 456 | expect(liveWorkEntryLabel(unknownEntry, undefined, true)).toBe("Mcp__github__search_issues"); | ||
| 457 | expect(workEntryDisplayLabel({ ...unknownEntry, detail: "Found 3 issues" }, undefined)).toBe( | ||
| 458 | "Found 3 issues", | ||
| 459 | ); | ||
| 460 | }); | ||
| 461 | |||
| 462 | it("keeps command summaries compact without replacing the full command in expanded rows", () => { | ||
| 463 | const commandEntry = { ...entry, command: "vp test run", detail: "All tests passed" }; | ||
| 464 | expect(liveWorkEntryLabel(commandEntry, undefined, true)).toBe("Running vp"); | ||
| 465 | expect(liveWorkEntryLabel(commandEntry, undefined, false)).toBe("Ran vp"); | ||
| 466 | expect(workEntryDisplayLabel(commandEntry, undefined)).toBe("vp test run"); | ||
| 467 | }); | ||
| 468 | |||
| 469 | it("summarizes the program inside a shell wrapper while preserving the expanded command", () => { | ||
| 470 | const command = "/bin/zsh -lc 'vp test run apps/web/src/session-logic.test.ts'"; | ||
| 471 | const commandEntry = { ...entry, command }; | ||
| 472 | expect(liveWorkEntryLabel(commandEntry, undefined, true)).toBe("Running vp"); | ||
| 473 | expect(liveWorkEntryLabel(commandEntry, undefined, false)).toBe("Ran vp"); | ||
| 474 | expect(workEntryDisplayLabel(commandEntry, undefined)).toBe(command); | ||
| 475 | }); | ||
| 476 | |||
| 477 | it.each([ | ||
| 478 | ["inProgress", "Running vp"], | ||
| 479 | ["completed", "Ran vp"], | ||
| 480 | ["failed", "Failed vp"], | ||
| 481 | ["declined", "Declined vp"], | ||
| 482 | ["stopped", "Stopped vp"], | ||
| 483 | ] as const)( | ||
| 484 | "uses the command's %s outcome even while the turn continues", | ||
| 485 | (toolLifecycleStatus, label) => { | ||
| 486 | const commandEntry = { | ||
| 487 | ...entry, | ||
| 488 | command: "/bin/bash -lc 'vp test run'", | ||
| 489 | toolLifecycleStatus, | ||
| 490 | }; | ||
| 491 | expect(liveWorkEntryLabel(commandEntry, undefined, true)).toBe(label); | ||
| 492 | expect(liveWorkEntryLabel(commandEntry, undefined, false)).toBe(label); | ||
| 493 | }, | ||
| 494 | ); | ||
| 495 | |||
| 496 | it("gives a completed browser group its own count and summary icon category", () => { | ||
| 497 | const rows = deriveMessagesTimelineRows({ | ||
| 498 | timelineEntries: [ | ||
| 499 | { | ||
| 500 | id: "browser-entry", | ||
| 501 | kind: "work", | ||
| 502 | createdAt: entry.createdAt, | ||
| 503 | entry: { | ||
| 504 | ...entry, | ||
| 505 | itemType: "mcp_tool_call", | ||
| 506 | toolLifecycleStatus: "completed", | ||
| 507 | toolData: { server: "t3-code", tool: "preview_click" }, | ||
| 508 | }, | ||
| 509 | }, | ||
| 510 | ], | ||
| 511 | isWorking: false, | ||
| 512 | activeTurnStartedAt: null, | ||
| 513 | turnDiffSummaryByAssistantMessageId: new Map(), | ||
| 514 | revertTurnCountByUserMessageId: new Map(), | ||
| 515 | }); | ||
| 516 | expect(rows).toMatchObject([ | ||
| 517 | { kind: "work-toggle", summary: "Used browser 1 time", summaryKind: "browser" }, | ||
| 518 | ]); | ||
| 519 | }); | ||
| 520 | }); | ||
| 521 | |||
| 522 | describe("shouldPreserveAssistantLineBreaks", () => { | ||
| 523 | it("preserves Claude insight formatting without changing regular markdown", () => { | ||
| 524 | expect( | ||
| 525 | shouldPreserveAssistantLineBreaks( | ||
| 526 | "★ Insight ─────────────────\\nFirst observation\\nSecond observation\\n─────────────────", | ||
| 527 | ), | ||
| 528 | ).toBe(true); | ||
| 529 | expect(shouldPreserveAssistantLineBreaks("A normal\\nmarkdown paragraph")).toBe(false); | ||
| 530 | }); | ||
| 531 | }); | ||
| 532 | |||
| 533 | describe("computeMessageDurationStart", () => { | 354 | describe("computeMessageDurationStart", () => { |
| 534 | it("returns message createdAt when there is no preceding user message", () => { | 355 | it("returns message createdAt when there is no preceding user message", () => { |
| 535 | const result = computeMessageDurationStart([ | 356 | const result = computeMessageDurationStart([ |
| @@ -729,6 +550,33 @@ describe("normalizeCompactToolLabel", () => { · 193 unmodified lines | |||
| 729 | }); | 550 | }); |
| 730 | }); | 551 | }); |
| 731 | 552 | ||
| 553 | describe("resolveTimelineToolPresentation", () => { | ||
| 554 | it("pretty prints Claude and Cursor T3 MCP tool names", () => { | ||
| 555 | expect(resolveTimelineToolPresentation("mcp__t3-code__t3_thread_read")).toEqual({ | ||
| 556 | displayName: "Read a T3 thread", | ||
| 557 | logo: "t3-code", | ||
| 558 | }); | ||
| 559 | }); | ||
| 560 | |||
| 561 | it("pretty prints Codex T3 MCP tool names", () => { | ||
| 562 | expect(resolveTimelineToolPresentation("t3-code.create_threads")).toEqual({ | ||
| 563 | displayName: "Create T3 threads", | ||
| 564 | logo: "t3-code", | ||
| 565 | }); | ||
| 566 | }); | ||
| 567 | |||
| 568 | it("pretty prints bare T3 MCP toolkit names", () => { | ||
| 569 | expect(resolveTimelineToolPresentation("list_scheduled_tasks")).toEqual({ | ||
| 570 | displayName: "List scheduled tasks", | ||
| 571 | logo: "t3-code", | ||
| 572 | }); | ||
| 573 | }); | ||
| 574 | |||
| 575 | it("keeps unknown MCP tools on the generic renderer path", () => { | ||
| 576 | expect(resolveTimelineToolPresentation("mcp__github__search_issues")).toBeNull(); | ||
| 577 | }); | ||
| 578 | }); | ||
| 579 | |||
| 732 | describe("resolveAssistantMessageCopyState", () => { | 580 | describe("resolveAssistantMessageCopyState", () => { |
| 733 | it("returns enabled copy state for completed assistant messages", () => { | 581 | it("returns enabled copy state for completed assistant messages", () => { |
| 734 | expect( | 582 | expect( |
| @@ -279,17 +279,64 @@ export function normalizeCompactToolLabel(value: string): string { · 278 unmodified lines | |||
| 279 | return value.replace(/\s+(?:complete|completed)\s*$/i, "").trim(); | 279 | return value.replace(/\s+(?:complete|completed)\s*$/i, "").trim(); |
| 280 | } | 280 | } |
| 281 | 281 | ||
| 282 | function expandedWorkGroupRow( | 282 | export interface TimelineToolPresentation { |
| 283 | groupId: string, | 283 | readonly displayName: string; |
| 284 | createdAt: string, | 284 | readonly logo: "t3-code"; |
| 285 | groupedEntries: WorkLogEntry[], | 285 | } |
| 286 | ): Extract<MessagesTimelineRow, { kind: "work" }> { | 286 | |
| 287 | const T3_MCP_SERVER_ALIASES = new Set(["t3-code", "t3_code", "t3code"]); | ||
| 288 | |||
| 289 | const T3_MCP_TOOL_DISPLAY_NAMES: Record<string, string> = { | ||
| 290 | orchestrator_capabilities: "Get orchestration capabilities", | ||
| 291 | delegate_task: "Delegate a child task", | ||
| 292 | task_status: "Get delegated task status", | ||
| 293 | task_cancel: "Cancel delegated task", | ||
| 294 | schedule_task: "Schedule a recurring task", | ||
| 295 | list_scheduled_tasks: "List scheduled tasks", | ||
| 296 | update_scheduled_task: "Update a scheduled task", | ||
| 297 | delete_scheduled_task: "Delete a scheduled task", | ||
| 298 | create_threads: "Create T3 threads", | ||
| 299 | t3_thread_start: "Start a T3 thread", | ||
| 300 | t3_thread_list: "List T3 threads", | ||
| 301 | t3_thread_read: "Read a T3 thread", | ||
| 302 | t3_thread_send: "Send to a T3 thread", | ||
| 303 | t3_thread_wait: "Wait for a T3 thread", | ||
| 304 | t3_thread_interrupt: "Interrupt a T3 thread", | ||
| 305 | }; | ||
| 306 | |||
| 307 | function resolveT3McpToolName(value: string): string | null { | ||
| 308 | const label = normalizeCompactToolLabel(value); | ||
| 309 | const mcpMatch = /^mcp__(?<server>.+?)__(?<tool>.+)$/.exec(label); | ||
| 310 | if (mcpMatch?.groups) { | ||
| 311 | const { server, tool } = mcpMatch.groups; | ||
| 312 | return server !== undefined && tool !== undefined && T3_MCP_SERVER_ALIASES.has(server) | ||
| 313 | ? tool | ||
| 314 | : null; | ||
| 315 | } | ||
| 316 | |||
| 317 | const namespaceMatch = /^(?<server>t3-code|t3_code|t3code)[.:/](?<tool>.+)$/.exec(label); | ||
| 318 | if (namespaceMatch?.groups) { | ||
| 319 | return namespaceMatch.groups.tool ?? null; | ||
| 320 | } | ||
| 321 | |||
| 322 | return Object.hasOwn(T3_MCP_TOOL_DISPLAY_NAMES, label) ? label : null; | ||
| 323 | } | ||
| 324 | |||
| 325 | export function resolveTimelineToolPresentation( | ||
| 326 | toolName: string | null | undefined, | ||
| 327 | ): TimelineToolPresentation | null { | ||
| 328 | const resolvedToolName = | ||
| 329 | toolName === undefined || toolName === null ? null : resolveT3McpToolName(toolName); | ||
| 330 | if (resolvedToolName === null) { | ||
| 331 | return null; | ||
| 332 | } | ||
| 333 | const displayName = T3_MCP_TOOL_DISPLAY_NAMES[resolvedToolName]; | ||
| 334 | if (displayName === undefined) { | ||
| 335 | return null; | ||
| 336 | } | ||
| 287 | return { | 337 | return { |
| 288 | kind: "work", | 338 | displayName, |
| 289 | id: `${groupId}:details`, | 339 | logo: "t3-code", |
| 290 | createdAt, | ||
| 291 | groupedEntries, | ||
| 292 | isExpandedToolGroup: true, | ||
| 293 | }; | 340 | }; |
| 294 | } | 341 | } |
| 295 | 342 | ||
| @@ -1034,6 +1034,69 @@ describe("MessagesTimeline", () => { · 1033 unmodified lines | |||
| 1034 | expect(markup).toContain("Inherited"); | 1034 | expect(markup).toContain("Inherited"); |
| 1035 | }); | 1035 | }); |
| 1036 | 1036 | ||
| 1037 | it("renders T3 MCP dynamic tools with the product logo and pretty name", async () => { | ||
| 1038 | const { MessagesTimeline } = await import("./MessagesTimeline"); | ||
| 1039 | const item = { | ||
| 1040 | id: "tool-t3-thread-read", | ||
| 1041 | threadId: "thread-source", | ||
| 1042 | runId: null, | ||
| 1043 | nodeId: null, | ||
| 1044 | providerThreadId: null, | ||
| 1045 | providerTurnId: null, | ||
| 1046 | nativeItemRef: null, | ||
| 1047 | parentItemId: null, | ||
| 1048 | ordinal: 0, | ||
| 1049 | status: "completed", | ||
| 1050 | title: null, | ||
| 1051 | startedAt: null, | ||
| 1052 | completedAt: null, | ||
| 1053 | updatedAt: {}, | ||
| 1054 | type: "dynamic_tool", | ||
| 1055 | toolName: "mcp__t3-code__t3_thread_read", | ||
| 1056 | input: { threadId: "thread-child" }, | ||
| 1057 | output: { messages: [] }, | ||
| 1058 | } as const; | ||
| 1059 | const projectedItem = { | ||
| 1060 | position: 0, | ||
| 1061 | visibility: "local", | ||
| 1062 | sourceThreadId: "thread-source", | ||
| 1063 | sourceItemId: item.id, | ||
| 1064 | item, | ||
| 1065 | } as const; | ||
| 1066 | const markup = renderToStaticMarkup( | ||
| 1067 | <MessagesTimeline | ||
| 1068 | {...buildProps()} | ||
| 1069 | timelineEntries={ | ||
| 1070 | [ | ||
| 1071 | { | ||
| 1072 | id: item.id, | ||
| 1073 | kind: "work", | ||
| 1074 | createdAt: MESSAGE_CREATED_AT, | ||
| 1075 | entry: { | ||
| 1076 | id: item.id, | ||
| 1077 | createdAt: MESSAGE_CREATED_AT, | ||
| 1078 | runId: null, | ||
| 1079 | label: item.toolName, | ||
| 1080 | tone: "tool", | ||
| 1081 | itemType: item.type, | ||
| 1082 | toolTitle: item.toolName, | ||
| 1083 | toolLifecycleStatus: "completed", | ||
| 1084 | toolData: { input: item.input, output: item.output }, | ||
| 1085 | structuredPayload: item, | ||
| 1086 | projectedItem, | ||
| 1087 | }, | ||
| 1088 | }, | ||
| 1089 | ] as never | ||
| 1090 | } | ||
| 1091 | />, | ||
| 1092 | ); | ||
| 1093 | |||
| 1094 | expect(markup).toContain('data-tool-logo="t3-code"'); | ||
| 1095 | expect(markup).toContain('src="/apple-touch-icon.png"'); | ||
| 1096 | expect(markup).toContain("Read a T3 thread"); | ||
| 1097 | expect(markup).not.toContain("mcp__t3-code__t3_thread_read"); | ||
| 1098 | }); | ||
| 1099 | |||
| 1037 | it("formats changed file paths from the workspace root", async () => { | 1100 | it("formats changed file paths from the workspace root", async () => { |
| 1038 | const { MessagesTimeline } = await import("./MessagesTimeline"); | 1101 | const { MessagesTimeline } = await import("./MessagesTimeline"); |
| 1039 | const markup = renderToStaticMarkup( | 1102 | const markup = renderToStaticMarkup( |
| @@ -73,6 +73,7 @@ import { · 72 unmodified lines | |||
| 73 | computeStableMessagesTimelineRows, | 73 | computeStableMessagesTimelineRows, |
| 74 | deriveMessagesTimelineRows, | 74 | deriveMessagesTimelineRows, |
| 75 | normalizeCompactToolLabel, | 75 | normalizeCompactToolLabel, |
| 76 | resolveTimelineToolPresentation, | ||
| 76 | resolveAssistantMessageCopyState, | 77 | resolveAssistantMessageCopyState, |
| 77 | resolveTimelineIsAtEnd, | 78 | resolveTimelineIsAtEnd, |
| 78 | resolveTimelineMinimapHasPersistentGutter, | 79 | resolveTimelineMinimapHasPersistentGutter, |
| @@ -2266,6 +2267,20 @@ function WorkEntryIconSvg({ name, className }: { name: WorkEntryIconName; classN · 2187 unmodified lines | |||
| 2266 | } | 2267 | } |
| 2267 | } | 2268 | } |
| 2268 | 2269 | ||
| 2270 | function T3CodeToolLogo({ className }: { className?: string }) { | ||
| 2271 | return ( | ||
| 2272 | <span | ||
| 2273 | className={cn( | ||
| 2274 | "flex size-4 shrink-0 items-center justify-center overflow-hidden rounded-[4px] bg-background ring-1 ring-border/65", | ||
| 2275 | className, | ||
| 2276 | )} | ||
| 2277 | title="T3 Code MCP tool" | ||
| 2278 | > | ||
| 2279 | <img alt="" aria-hidden="true" className="size-4 object-cover" src="/apple-touch-icon.png" /> | ||
| 2280 | </span> | ||
| 2281 | ); | ||
| 2282 | } | ||
| 2283 | |||
| 2269 | function workToneIcon(tone: TimelineWorkEntry["tone"]): { | 2284 | function workToneIcon(tone: TimelineWorkEntry["tone"]): { |
| 2270 | iconName: WorkEntryIconName; | 2285 | iconName: WorkEntryIconName; |
| 2271 | className: string; | 2286 | className: string; |
| @@ -2515,7 +2530,8 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { · 243 unmodified lines | |||
| 2515 | const iconConfig = workToneIcon(workEntry.tone); | 2530 | const iconConfig = workToneIcon(workEntry.tone); |
| 2516 | const showWarningIndicator = false; | 2531 | const showWarningIndicator = false; |
| 2517 | const entryIconName = showWarningIndicator ? "x" : workEntryIconName(workEntry); | 2532 | const entryIconName = showWarningIndicator ? "x" : workEntryIconName(workEntry); |
| 2518 | const heading = toolWorkEntryHeading(workEntry); | 2533 | const toolPresentation = resolveTimelineToolPresentation(workEntry.toolTitle ?? workEntry.label); |
| 2534 | const heading = toolPresentation?.displayName ?? toolWorkEntryHeading(workEntry); | ||
| 2519 | const rawPreview = workEntryPreview(workEntry, workspaceRoot); | 2535 | const rawPreview = workEntryPreview(workEntry, workspaceRoot); |
| 2520 | const preview = | 2536 | const preview = |
| 2521 | rawPreview && | 2537 | rawPreview && |
| @@ -2572,16 +2588,21 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { · 50 unmodified lines | |||
| 2572 | canExpand && | 2588 | canExpand && |
| 2573 | "cursor-pointer hover:bg-accent/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70", | 2589 | "cursor-pointer hover:bg-accent/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70", |
| 2574 | )} | 2590 | )} |
| 2591 | data-tool-logo={toolPresentation?.logo} | ||
| 2575 | data-v2-item-type={workEntry.projectedItem?.item.type} | 2592 | data-v2-item-type={workEntry.projectedItem?.item.type} |
| 2576 | data-v2-item-visibility={workEntry.projectedItem?.visibility} | 2593 | data-v2-item-visibility={workEntry.projectedItem?.visibility} |
| 2577 | {...rowToggleProps} | 2594 | {...rowToggleProps} |
| 2578 | > | 2595 | > |
| 2579 | <div className="flex select-none items-center gap-1.5 transition-[opacity,translate] duration-200"> | 2596 | <div className="flex select-none items-center gap-1.5 transition-[opacity,translate] duration-200"> |
| 2580 | <span className={iconWrapperClass}> | 2597 | <span className={iconWrapperClass}> |
| 2581 | <WorkEntryIconSvg | 2598 | {toolPresentation?.logo === "t3-code" ? ( |
| 2582 | name={entryIconName} | 2599 | <T3CodeToolLogo /> |
| 2583 | className="block size-3.5 shrink-0 stroke-[1.8] opacity-80" | 2600 | ) : ( |
| 2584 | /> | 2601 | <WorkEntryIconSvg |
| 2602 | name={entryIconName} | ||
| 2603 | className="block size-3.5 shrink-0 stroke-[1.8] opacity-80" | ||
| 2604 | /> | ||
| 2605 | )} | ||
| 2585 | </span> | 2606 | </span> |
| 2586 | <div className="flex min-w-0 flex-1 items-center gap-1.5"> | 2607 | <div className="flex min-w-0 flex-1 items-center gap-1.5"> |
| 2587 | <div className="min-w-0 flex-1 overflow-hidden"> | 2608 | <div className="min-w-0 flex-1 overflow-hidden"> |