@flanksource/clicky-ui 0.3.47 → 0.3.48

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.
Files changed (169) hide show
  1. package/README.md +49 -0
  2. package/dist/comments/CommentSidePanel.cjs +10 -4
  3. package/dist/comments/CommentSidePanel.cjs.map +1 -1
  4. package/dist/comments/CommentSidePanel.d.ts +2 -0
  5. package/dist/comments/CommentSidePanel.d.ts.map +1 -1
  6. package/dist/comments/CommentSidePanel.js +10 -4
  7. package/dist/comments/CommentSidePanel.js.map +1 -1
  8. package/dist/comments/CommentThread.cjs +2 -0
  9. package/dist/comments/CommentThread.cjs.map +1 -1
  10. package/dist/comments/CommentThread.d.ts +2 -1
  11. package/dist/comments/CommentThread.d.ts.map +1 -1
  12. package/dist/comments/CommentThread.js +2 -0
  13. package/dist/comments/CommentThread.js.map +1 -1
  14. package/dist/comments/CommentThreadList.cjs +3 -0
  15. package/dist/comments/CommentThreadList.cjs.map +1 -1
  16. package/dist/comments/CommentThreadList.d.ts +3 -1
  17. package/dist/comments/CommentThreadList.d.ts.map +1 -1
  18. package/dist/comments/CommentThreadList.js +3 -0
  19. package/dist/comments/CommentThreadList.js.map +1 -1
  20. package/dist/data/CodeLine.cjs +4 -2
  21. package/dist/data/CodeLine.cjs.map +1 -1
  22. package/dist/data/CodeLine.d.ts.map +1 -1
  23. package/dist/data/CodeLine.js +4 -2
  24. package/dist/data/CodeLine.js.map +1 -1
  25. package/dist/data/SymbolBadge.cjs +61 -0
  26. package/dist/data/SymbolBadge.cjs.map +1 -0
  27. package/dist/data/SymbolBadge.d.ts +21 -0
  28. package/dist/data/SymbolBadge.d.ts.map +1 -0
  29. package/dist/data/SymbolBadge.js +61 -0
  30. package/dist/data/SymbolBadge.js.map +1 -0
  31. package/dist/data/test-runner/TestDetailPanel.cjs +26 -12
  32. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
  33. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
  34. package/dist/data/test-runner/TestDetailPanel.js +27 -13
  35. package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
  36. package/dist/data/test-runner/TestRunner.cjs +6 -0
  37. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  38. package/dist/data/test-runner/TestRunner.d.ts +3 -1
  39. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  40. package/dist/data/test-runner/TestRunner.js +6 -0
  41. package/dist/data/test-runner/TestRunner.js.map +1 -1
  42. package/dist/data/test-runner/TestTreeNode.cjs +2 -1
  43. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
  44. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
  45. package/dist/data/test-runner/TestTreeNode.js +2 -1
  46. package/dist/data/test-runner/TestTreeNode.js.map +1 -1
  47. package/dist/data/test-runner/adapter.cjs +4 -0
  48. package/dist/data/test-runner/adapter.cjs.map +1 -1
  49. package/dist/data/test-runner/adapter.d.ts +9 -0
  50. package/dist/data/test-runner/adapter.d.ts.map +1 -1
  51. package/dist/data/test-runner/adapter.js +4 -0
  52. package/dist/data/test-runner/adapter.js.map +1 -1
  53. package/dist/data/test-runner/context.cjs.map +1 -1
  54. package/dist/data/test-runner/context.d.ts +2 -0
  55. package/dist/data/test-runner/context.d.ts.map +1 -1
  56. package/dist/data/test-runner/context.js.map +1 -1
  57. package/dist/data/test-runner/index.d.ts +2 -2
  58. package/dist/data/test-runner/index.d.ts.map +1 -1
  59. package/dist/data/test-runner/marks.cjs +42 -1
  60. package/dist/data/test-runner/marks.cjs.map +1 -1
  61. package/dist/data/test-runner/marks.d.ts +10 -0
  62. package/dist/data/test-runner/marks.d.ts.map +1 -1
  63. package/dist/data/test-runner/marks.js +42 -1
  64. package/dist/data/test-runner/marks.js.map +1 -1
  65. package/dist/data/traces/TerminalCastPlayer.d.ts +21 -0
  66. package/dist/data/traces/TerminalCastPlayer.d.ts.map +1 -0
  67. package/dist/data/traces/TraceHarView.d.ts +13 -0
  68. package/dist/data/traces/TraceHarView.d.ts.map +1 -0
  69. package/dist/data/traces/cast.d.ts +9 -0
  70. package/dist/data/traces/cast.d.ts.map +1 -0
  71. package/dist/data/traces/fixtures.d.ts +8 -0
  72. package/dist/data/traces/fixtures.d.ts.map +1 -0
  73. package/dist/data/traces/har-adapter.d.ts +13 -0
  74. package/dist/data/traces/har-adapter.d.ts.map +1 -0
  75. package/dist/data/traces/terminal-screen.d.ts +45 -0
  76. package/dist/data/traces/terminal-screen.d.ts.map +1 -0
  77. package/dist/data/traces/terminal-timeline.d.ts +26 -0
  78. package/dist/data/traces/terminal-timeline.d.ts.map +1 -0
  79. package/dist/data/traces/types.d.ts +141 -0
  80. package/dist/data/traces/types.d.ts.map +1 -0
  81. package/dist/data/verification/VerificationResults.cjs +4 -0
  82. package/dist/data/verification/VerificationResults.cjs.map +1 -1
  83. package/dist/data/verification/VerificationResults.d.ts +3 -1
  84. package/dist/data/verification/VerificationResults.d.ts.map +1 -1
  85. package/dist/data/verification/VerificationResults.js +4 -0
  86. package/dist/data/verification/VerificationResults.js.map +1 -1
  87. package/dist/data/verification/adapterViews.cjs +12 -3
  88. package/dist/data/verification/adapterViews.cjs.map +1 -1
  89. package/dist/data/verification/adapterViews.d.ts +4 -1
  90. package/dist/data/verification/adapterViews.d.ts.map +1 -1
  91. package/dist/data/verification/adapterViews.js +12 -3
  92. package/dist/data/verification/adapterViews.js.map +1 -1
  93. package/dist/data/verification/adapters.cjs +53 -10
  94. package/dist/data/verification/adapters.cjs.map +1 -1
  95. package/dist/data/verification/adapters.d.ts +7 -1
  96. package/dist/data/verification/adapters.d.ts.map +1 -1
  97. package/dist/data/verification/adapters.js +53 -10
  98. package/dist/data/verification/adapters.js.map +1 -1
  99. package/dist/data/verification/index.d.ts +4 -2
  100. package/dist/data/verification/index.d.ts.map +1 -1
  101. package/dist/data/verification/stepKind.cjs +217 -0
  102. package/dist/data/verification/stepKind.cjs.map +1 -0
  103. package/dist/data/verification/stepKind.d.ts +87 -0
  104. package/dist/data/verification/stepKind.d.ts.map +1 -0
  105. package/dist/data/verification/stepKind.js +217 -0
  106. package/dist/data/verification/stepKind.js.map +1 -0
  107. package/dist/data/verification/stepKindViews.cjs +111 -0
  108. package/dist/data/verification/stepKindViews.cjs.map +1 -0
  109. package/dist/data/verification/stepKindViews.d.ts +18 -0
  110. package/dist/data/verification/stepKindViews.d.ts.map +1 -0
  111. package/dist/data/verification/stepKindViews.js +111 -0
  112. package/dist/data/verification/stepKindViews.js.map +1 -0
  113. package/dist/data/verification/verifyReportTests.cjs +1 -0
  114. package/dist/data/verification/verifyReportTests.cjs.map +1 -1
  115. package/dist/data/verification/verifyReportTests.d.ts +2 -1
  116. package/dist/data/verification/verifyReportTests.d.ts.map +1 -1
  117. package/dist/data/verification/verifyReportTests.js +1 -0
  118. package/dist/data/verification/verifyReportTests.js.map +1 -1
  119. package/dist/data/version-info.cjs +3 -3
  120. package/dist/data/version-info.js +3 -3
  121. package/dist/data.cjs +14 -0
  122. package/dist/data.cjs.map +1 -1
  123. package/dist/data.d.ts +1 -0
  124. package/dist/data.d.ts.map +1 -1
  125. package/dist/data.js +16 -2
  126. package/dist/data.js.map +1 -1
  127. package/dist/index.cjs +16 -0
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.js +18 -2
  130. package/dist/index.js.map +1 -1
  131. package/dist/rpc/FilterForm.cjs +9 -0
  132. package/dist/rpc/FilterForm.cjs.map +1 -1
  133. package/dist/rpc/FilterForm.d.ts +5 -1
  134. package/dist/rpc/FilterForm.d.ts.map +1 -1
  135. package/dist/rpc/FilterForm.js +9 -0
  136. package/dist/rpc/FilterForm.js.map +1 -1
  137. package/dist/rpc/OperationArgsForm.cjs +153 -0
  138. package/dist/rpc/OperationArgsForm.cjs.map +1 -0
  139. package/dist/rpc/OperationArgsForm.d.ts +16 -0
  140. package/dist/rpc/OperationArgsForm.d.ts.map +1 -0
  141. package/dist/rpc/OperationArgsForm.js +153 -0
  142. package/dist/rpc/OperationArgsForm.js.map +1 -0
  143. package/dist/rpc/OperationScheduleDialog.cjs +61 -17
  144. package/dist/rpc/OperationScheduleDialog.cjs.map +1 -1
  145. package/dist/rpc/OperationScheduleDialog.d.ts +3 -0
  146. package/dist/rpc/OperationScheduleDialog.d.ts.map +1 -1
  147. package/dist/rpc/OperationScheduleDialog.js +62 -18
  148. package/dist/rpc/OperationScheduleDialog.js.map +1 -1
  149. package/dist/rpc/OperationSchedules.cjs +6 -1
  150. package/dist/rpc/OperationSchedules.cjs.map +1 -1
  151. package/dist/rpc/OperationSchedules.d.ts.map +1 -1
  152. package/dist/rpc/OperationSchedules.js +6 -1
  153. package/dist/rpc/OperationSchedules.js.map +1 -1
  154. package/dist/rpc/operation-schedule-model.cjs +0 -7
  155. package/dist/rpc/operation-schedule-model.cjs.map +1 -1
  156. package/dist/rpc/operation-schedule-model.d.ts +0 -2
  157. package/dist/rpc/operation-schedule-model.d.ts.map +1 -1
  158. package/dist/rpc/operation-schedule-model.js +0 -7
  159. package/dist/rpc/operation-schedule-model.js.map +1 -1
  160. package/dist/rpc/operation-schedule-types.d.ts +6 -1
  161. package/dist/rpc/operation-schedule-types.d.ts.map +1 -1
  162. package/dist/rpc.cjs +2 -0
  163. package/dist/rpc.cjs.map +1 -1
  164. package/dist/rpc.d.ts +1 -0
  165. package/dist/rpc.d.ts.map +1 -1
  166. package/dist/rpc.js +2 -0
  167. package/dist/rpc.js.map +1 -1
  168. package/dist/styles.css +1 -1
  169. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","sortReplies","Fragment","cn","CommentCard","copyText","Modal","Tabs","Markdown"],"mappings":"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"}
1
+ {"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Extra content rendered with each root thread, including its maximized view. */\n renderRootDetails?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n renderRootDetails,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {renderRootDetails?.(root)}\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {message.id === maximizedRoot.id &&\n renderRootDetails?.(maximizedRoot)}\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","sortReplies","Fragment","cn","CommentCard","copyText","Modal","Tabs","Markdown"],"mappings":"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"}
@@ -8,6 +8,8 @@ export type CommentThreadListProps = {
8
8
  defaultExpanded?: boolean;
9
9
  /** Extra content rendered above each root card (e.g. an anchor label). */
10
10
  renderRootMeta?: (comment: Comment) => ReactNode;
11
+ /** Extra content rendered with each root thread, including its maximized view. */
12
+ renderRootDetails?: (comment: Comment) => ReactNode;
11
13
  /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */
12
14
  getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;
13
15
  /** Custom body renderer forwarded to each card. */
@@ -26,5 +28,5 @@ export type CommentThreadListProps = {
26
28
  * optional inline reply box. Fully controlled — mutations route through the
27
29
  * id-keyed callbacks.
28
30
  */
29
- export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, getThreadProps, renderBody, threadToMarkdown, onUpdateStatus, onClose, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
31
+ export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, renderRootDetails, getThreadProps, renderBody, threadToMarkdown, onUpdateStatus, onClose, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
30
32
  //# sourceMappingURL=CommentThreadList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAsDF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,OAAe,EACf,eAAuB,EACvB,cAAc,EACd,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,EAAE,sBAAsB,sCA8JxB"}
1
+ {"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACpD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAsDF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,OAAe,EACf,eAAuB,EACvB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,EAAE,sBAAsB,sCAiKxB"}
@@ -65,6 +65,7 @@ function CommentThreadList({
65
65
  compact = false,
66
66
  defaultExpanded = false,
67
67
  renderRootMeta,
68
+ renderRootDetails,
68
69
  getThreadProps,
69
70
  renderBody,
70
71
  threadToMarkdown,
@@ -126,6 +127,7 @@ function CommentThreadList({
126
127
  ...onReply ? { onReply: () => setReplyingTo(root.id) } : {}
127
128
  }
128
129
  ),
130
+ renderRootDetails == null ? void 0 : renderRootDetails(root),
129
131
  replies.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: replies.map((reply) => /* @__PURE__ */ jsx(
130
132
  CommentCard,
131
133
  {
@@ -182,6 +184,7 @@ function CommentThreadList({
182
184
  ...onReply ? { onReply: () => setModalReplyTo(message.id) } : {}
183
185
  }
184
186
  ),
187
+ message.id === maximizedRoot.id && (renderRootDetails == null ? void 0 : renderRootDetails(maximizedRoot)),
185
188
  onReply && modalReplyTo === message.id && /* @__PURE__ */ jsx(
186
189
  ReplyInput,
187
190
  {
@@ -1 +1 @@
1
- {"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Extra content rendered with each root thread, including its maximized view. */\n renderRootDetails?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n renderRootDetails,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {renderRootDetails?.(root)}\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {message.id === maximizedRoot.id &&\n renderRootDetails?.(maximizedRoot)}\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -2,20 +2,22 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
+ const useTheme = require("../hooks/use-theme.cjs");
5
6
  const codeHighlight = require("./code-highlight.cjs");
6
7
  const HighlightedTokens = require("./HighlightedTokens.cjs");
7
8
  function CodeLine({ code, language, className }) {
8
9
  const [tokens, setTokens] = react.useState(void 0);
10
+ const theme = useTheme.useResolvedTheme();
9
11
  react.useEffect(() => {
10
12
  let cancelled = false;
11
13
  setTokens(void 0);
12
- codeHighlight.highlightToLines(code, { lang: language }).then((out) => {
14
+ codeHighlight.highlightToLines(code, { lang: language, theme: theme === "dark" ? "github-dark" : "github-light" }).then((out) => {
13
15
  if (!cancelled) setTokens(out == null ? void 0 : out[0]);
14
16
  });
15
17
  return () => {
16
18
  cancelled = true;
17
19
  };
18
- }, [code, language]);
20
+ }, [code, language, theme]);
19
21
  return /* @__PURE__ */ jsxRuntime.jsx("code", { className: ["whitespace-pre font-mono", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsxRuntime.jsx(HighlightedTokens.HighlightedTokens, { tokens, content: code }) });
20
22
  }
21
23
  exports.CodeLine = CodeLine;
@@ -1 +1 @@
1
- {"version":3,"file":"CodeLine.cjs","sources":["../../src/data/CodeLine.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { highlightToLines, type HighlightedLine } from \"./code-highlight\";\nimport { HighlightedTokens } from \"./HighlightedTokens\";\n\nexport interface CodeLineProps {\n code: string;\n /** Shiki language id; without one the line renders as plain text. */\n language?: string;\n className?: string;\n}\n\n// CodeLine paints a single source line, syntax-highlighted, inline — the line a\n// stack frame or trace node ran, shown without the gutter and frame of a block.\nexport function CodeLine({ code, language, className }: CodeLineProps) {\n const [tokens, setTokens] = useState<HighlightedLine | undefined>(undefined);\n\n useEffect(() => {\n let cancelled = false;\n setTokens(undefined);\n highlightToLines(code, { lang: language }).then((out) => {\n if (!cancelled) setTokens(out?.[0]);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language]);\n\n return (\n <code className={[\"whitespace-pre font-mono\", className].filter(Boolean).join(\" \")}>\n <HighlightedTokens tokens={tokens} content={code} />\n </code>\n );\n}\n"],"names":["useState","useEffect","highlightToLines","jsx","HighlightedTokens"],"mappings":";;;;;;AAaO,SAAS,SAAS,EAAE,MAAM,UAAU,aAA4B;AACrE,QAAM,CAAC,QAAQ,SAAS,IAAIA,MAAAA,SAAsC,MAAS;AAE3EC,QAAAA,UAAU,MAAM;AACd,QAAI,YAAY;AAChB,cAAU,MAAS;AACnBC,kBAAAA,iBAAiB,MAAM,EAAE,MAAM,SAAA,CAAU,EAAE,KAAK,CAAC,QAAQ;AACvD,UAAI,CAAC,UAAW,WAAU,2BAAM,EAAE;AAAA,IACpC,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,QAAQ,CAAC;AAEnB,wCACG,QAAA,EAAK,WAAW,CAAC,4BAA4B,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC/E,UAAAC,2BAAAA,IAACC,kBAAAA,qBAAkB,QAAgB,SAAS,MAAM,GACpD;AAEJ;;"}
1
+ {"version":3,"file":"CodeLine.cjs","sources":["../../src/data/CodeLine.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useResolvedTheme } from \"../hooks/use-theme\";\nimport { highlightToLines, type HighlightedLine } from \"./code-highlight\";\nimport { HighlightedTokens } from \"./HighlightedTokens\";\n\nexport interface CodeLineProps {\n code: string;\n /** Shiki language id; without one the line renders as plain text. */\n language?: string;\n className?: string;\n}\n\n// CodeLine paints a single source line, syntax-highlighted, inline — the line a\n// stack frame or trace node ran, shown without the gutter and frame of a block.\nexport function CodeLine({ code, language, className }: CodeLineProps) {\n const [tokens, setTokens] = useState<HighlightedLine | undefined>(undefined);\n const theme = useResolvedTheme();\n\n useEffect(() => {\n let cancelled = false;\n setTokens(undefined);\n highlightToLines(code, { lang: language, theme: theme === \"dark\" ? \"github-dark\" : \"github-light\" }).then((out) => {\n if (!cancelled) setTokens(out?.[0]);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language, theme]);\n\n return (\n <code className={[\"whitespace-pre font-mono\", className].filter(Boolean).join(\" \")}>\n <HighlightedTokens tokens={tokens} content={code} />\n </code>\n );\n}\n"],"names":["useState","useResolvedTheme","useEffect","highlightToLines","jsx","HighlightedTokens"],"mappings":";;;;;;;AAcO,SAAS,SAAS,EAAE,MAAM,UAAU,aAA4B;AACrE,QAAM,CAAC,QAAQ,SAAS,IAAIA,MAAAA,SAAsC,MAAS;AAC3E,QAAM,QAAQC,SAAAA,iBAAA;AAEdC,QAAAA,UAAU,MAAM;AACd,QAAI,YAAY;AAChB,cAAU,MAAS;AACnBC,kBAAAA,iBAAiB,MAAM,EAAE,MAAM,UAAU,OAAO,UAAU,SAAS,gBAAgB,eAAA,CAAgB,EAAE,KAAK,CAAC,QAAQ;AACjH,UAAI,CAAC,UAAW,WAAU,2BAAM,EAAE;AAAA,IACpC,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,UAAU,KAAK,CAAC;AAE1B,wCACG,QAAA,EAAK,WAAW,CAAC,4BAA4B,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC/E,UAAAC,2BAAAA,IAACC,kBAAAA,qBAAkB,QAAgB,SAAS,MAAM,GACpD;AAEJ;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CodeLine.d.ts","sourceRoot":"","sources":["../../src/data/CodeLine.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,aAAa,+BAmBpE"}
1
+ {"version":3,"file":"CodeLine.d.ts","sourceRoot":"","sources":["../../src/data/CodeLine.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,aAAa,+BAoBpE"}
@@ -1,19 +1,21 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
+ import { useResolvedTheme } from "../hooks/use-theme.js";
3
4
  import { highlightToLines } from "./code-highlight.js";
4
5
  import { HighlightedTokens } from "./HighlightedTokens.js";
5
6
  function CodeLine({ code, language, className }) {
6
7
  const [tokens, setTokens] = useState(void 0);
8
+ const theme = useResolvedTheme();
7
9
  useEffect(() => {
8
10
  let cancelled = false;
9
11
  setTokens(void 0);
10
- highlightToLines(code, { lang: language }).then((out) => {
12
+ highlightToLines(code, { lang: language, theme: theme === "dark" ? "github-dark" : "github-light" }).then((out) => {
11
13
  if (!cancelled) setTokens(out == null ? void 0 : out[0]);
12
14
  });
13
15
  return () => {
14
16
  cancelled = true;
15
17
  };
16
- }, [code, language]);
18
+ }, [code, language, theme]);
17
19
  return /* @__PURE__ */ jsx("code", { className: ["whitespace-pre font-mono", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx(HighlightedTokens, { tokens, content: code }) });
18
20
  }
19
21
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"CodeLine.js","sources":["../../src/data/CodeLine.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { highlightToLines, type HighlightedLine } from \"./code-highlight\";\nimport { HighlightedTokens } from \"./HighlightedTokens\";\n\nexport interface CodeLineProps {\n code: string;\n /** Shiki language id; without one the line renders as plain text. */\n language?: string;\n className?: string;\n}\n\n// CodeLine paints a single source line, syntax-highlighted, inline — the line a\n// stack frame or trace node ran, shown without the gutter and frame of a block.\nexport function CodeLine({ code, language, className }: CodeLineProps) {\n const [tokens, setTokens] = useState<HighlightedLine | undefined>(undefined);\n\n useEffect(() => {\n let cancelled = false;\n setTokens(undefined);\n highlightToLines(code, { lang: language }).then((out) => {\n if (!cancelled) setTokens(out?.[0]);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language]);\n\n return (\n <code className={[\"whitespace-pre font-mono\", className].filter(Boolean).join(\" \")}>\n <HighlightedTokens tokens={tokens} content={code} />\n </code>\n );\n}\n"],"names":[],"mappings":";;;;AAaO,SAAS,SAAS,EAAE,MAAM,UAAU,aAA4B;AACrE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsC,MAAS;AAE3E,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,cAAU,MAAS;AACnB,qBAAiB,MAAM,EAAE,MAAM,SAAA,CAAU,EAAE,KAAK,CAAC,QAAQ;AACvD,UAAI,CAAC,UAAW,WAAU,2BAAM,EAAE;AAAA,IACpC,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,QAAQ,CAAC;AAEnB,6BACG,QAAA,EAAK,WAAW,CAAC,4BAA4B,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC/E,UAAA,oBAAC,qBAAkB,QAAgB,SAAS,MAAM,GACpD;AAEJ;"}
1
+ {"version":3,"file":"CodeLine.js","sources":["../../src/data/CodeLine.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useResolvedTheme } from \"../hooks/use-theme\";\nimport { highlightToLines, type HighlightedLine } from \"./code-highlight\";\nimport { HighlightedTokens } from \"./HighlightedTokens\";\n\nexport interface CodeLineProps {\n code: string;\n /** Shiki language id; without one the line renders as plain text. */\n language?: string;\n className?: string;\n}\n\n// CodeLine paints a single source line, syntax-highlighted, inline — the line a\n// stack frame or trace node ran, shown without the gutter and frame of a block.\nexport function CodeLine({ code, language, className }: CodeLineProps) {\n const [tokens, setTokens] = useState<HighlightedLine | undefined>(undefined);\n const theme = useResolvedTheme();\n\n useEffect(() => {\n let cancelled = false;\n setTokens(undefined);\n highlightToLines(code, { lang: language, theme: theme === \"dark\" ? \"github-dark\" : \"github-light\" }).then((out) => {\n if (!cancelled) setTokens(out?.[0]);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language, theme]);\n\n return (\n <code className={[\"whitespace-pre font-mono\", className].filter(Boolean).join(\" \")}>\n <HighlightedTokens tokens={tokens} content={code} />\n </code>\n );\n}\n"],"names":[],"mappings":";;;;;AAcO,SAAS,SAAS,EAAE,MAAM,UAAU,aAA4B;AACrE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsC,MAAS;AAC3E,QAAM,QAAQ,iBAAA;AAEd,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,cAAU,MAAS;AACnB,qBAAiB,MAAM,EAAE,MAAM,UAAU,OAAO,UAAU,SAAS,gBAAgB,eAAA,CAAgB,EAAE,KAAK,CAAC,QAAQ;AACjH,UAAI,CAAC,UAAW,WAAU,2BAAM,EAAE;AAAA,IACpC,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,UAAU,KAAK,CAAC;AAE1B,6BACG,QAAA,EAAK,WAAW,CAAC,4BAA4B,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC/E,UAAA,oBAAC,qBAAkB,QAAgB,SAAS,MAAM,GACpD;AAEJ;"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const react = require("react");
5
+ const utils = require("../lib/utils.cjs");
6
+ const badgeVariants = require("./badge-variants.cjs");
7
+ const CodeLine = require("./CodeLine.cjs");
8
+ const Icon = require("./Icon.cjs");
9
+ const COLORS = /* @__PURE__ */ new Map([
10
+ ["neutral", "text-foreground"],
11
+ ["primary", "text-primary"],
12
+ ["blue", "text-blue-700 [[data-theme=dark]_&]:text-blue-300"],
13
+ ["cyan", "text-cyan-700 [[data-theme=dark]_&]:text-cyan-300"],
14
+ ["indigo", "text-indigo-700 [[data-theme=dark]_&]:text-indigo-300"],
15
+ ["sky", "text-sky-700 [[data-theme=dark]_&]:text-sky-300"],
16
+ ["slate", "text-slate-700 [[data-theme=dark]_&]:text-slate-300"],
17
+ ["violet", "text-violet-700 [[data-theme=dark]_&]:text-violet-300"]
18
+ ]);
19
+ function SymbolBadge({
20
+ style = "badge",
21
+ color = "indigo",
22
+ icon,
23
+ iconClassName,
24
+ language,
25
+ children,
26
+ className,
27
+ title,
28
+ ...props
29
+ }) {
30
+ if (typeof children === "string" ? !children.trim() || /[\r\n]/.test(children) : !react.isValidElement(children)) {
31
+ throw new Error("SymbolBadge requires a nonempty, single-line symbol; use CodeBlock for multiline source.");
32
+ }
33
+ const colorClass = COLORS.get(color);
34
+ return /* @__PURE__ */ jsxRuntime.jsxs(
35
+ "span",
36
+ {
37
+ ...props,
38
+ "data-symbol-style": style,
39
+ title: title ?? (typeof children === "string" ? children : void 0),
40
+ className: utils.cn(
41
+ badgeVariants.badgeVariants({ variant: "outline", size: "md" }),
42
+ "max-w-full items-baseline gap-1.5 rounded-md align-baseline text-[12px] leading-4",
43
+ style === "badge" && "border-transparent",
44
+ style === "badge-outline" && "border-current",
45
+ style === "inline" && "rounded-none border-0 p-0",
46
+ colorClass,
47
+ className
48
+ ),
49
+ style: {
50
+ ...colorClass === void 0 ? { color } : {},
51
+ ...style === "badge" ? { backgroundColor: `color-mix(in srgb, ${colorClass === void 0 ? color : "currentColor"} 16%, transparent)` } : {}
52
+ },
53
+ children: [
54
+ icon !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-symbol-icon": true, "aria-hidden": true, className: "relative w-[1.25em] shrink-0 before:invisible before:content-['.']", children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon, width: "1.25em", className: utils.cn("absolute top-1/2 -translate-y-1/2", iconClassName) }) }),
55
+ typeof children === "string" ? /* @__PURE__ */ jsxRuntime.jsx(CodeLine.CodeLine, { code: children, ...language !== void 0 ? { language } : {}, className: "min-w-0 truncate" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 whitespace-normal", children })
56
+ ]
57
+ }
58
+ );
59
+ }
60
+ exports.SymbolBadge = SymbolBadge;
61
+ //# sourceMappingURL=SymbolBadge.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SymbolBadge.cjs","sources":["../../src/data/SymbolBadge.tsx"],"sourcesContent":["import { isValidElement, type HTMLAttributes, type ReactElement } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { badgeVariants } from \"./badge-variants\";\nimport { CodeLine } from \"./CodeLine\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\n\nconst COLORS = new Map([\n [\"neutral\", \"text-foreground\"],\n [\"primary\", \"text-primary\"],\n [\"blue\", \"text-blue-700 [[data-theme=dark]_&]:text-blue-300\"],\n [\"cyan\", \"text-cyan-700 [[data-theme=dark]_&]:text-cyan-300\"],\n [\"indigo\", \"text-indigo-700 [[data-theme=dark]_&]:text-indigo-300\"],\n [\"sky\", \"text-sky-700 [[data-theme=dark]_&]:text-sky-300\"],\n [\"slate\", \"text-slate-700 [[data-theme=dark]_&]:text-slate-300\"],\n [\"violet\", \"text-violet-700 [[data-theme=dark]_&]:text-violet-300\"],\n]);\n\nexport type SymbolBadgeStyle = \"badge\" | \"badge-outline\" | \"inline\";\n\nexport type SymbolBadgeProps = Omit<HTMLAttributes<HTMLSpanElement>, \"style\" | \"color\" | \"children\"> & {\n /** Filled badge, colored outline, or an unframed inline symbol. */\n style?: SymbolBadgeStyle;\n /** Named palette hue or CSS color, including CSS custom properties. */\n color?: string;\n /** Imported icon component placed before the symbol. */\n icon?: StaticIconComponent;\n /** Additional glyph classes, including adjustments for fixed-color SVGs. */\n iconClassName?: string;\n} & (\n | { /** Literal, nonempty, single-line symbol or expression. */ children: string; language?: string }\n | { /** Content already formatted by the caller, such as MathML. */ children: ReactElement; language?: never }\n);\n\nexport function SymbolBadge({\n style = \"badge\",\n color = \"indigo\",\n icon,\n iconClassName,\n language,\n children,\n className,\n title,\n ...props\n}: SymbolBadgeProps) {\n if (typeof children === \"string\" ? !children.trim() || /[\\r\\n]/.test(children) : !isValidElement(children)) {\n throw new Error(\"SymbolBadge requires a nonempty, single-line symbol; use CodeBlock for multiline source.\");\n }\n const colorClass = COLORS.get(color);\n return (\n <span\n {...props}\n data-symbol-style={style}\n title={title ?? (typeof children === \"string\" ? children : undefined)}\n className={cn(\n badgeVariants({ variant: \"outline\", size: \"md\" }),\n \"max-w-full items-baseline gap-1.5 rounded-md align-baseline text-[12px] leading-4\",\n style === \"badge\" && \"border-transparent\",\n style === \"badge-outline\" && \"border-current\",\n style === \"inline\" && \"rounded-none border-0 p-0\",\n colorClass,\n className,\n )}\n style={{\n ...(colorClass === undefined ? { color } : {}),\n ...(style === \"badge\" ? { backgroundColor: `color-mix(in srgb, ${colorClass === undefined ? color : \"currentColor\"} 16%, transparent)` } : {}),\n }}\n >\n {icon !== undefined && <span data-symbol-icon aria-hidden className=\"relative w-[1.25em] shrink-0 before:invisible before:content-['.']\">\n <Icon icon={icon} width=\"1.25em\" className={cn(\"absolute top-1/2 -translate-y-1/2\", iconClassName)} />\n </span>}\n {typeof children === \"string\"\n ? <CodeLine code={children} {...(language !== undefined ? { language } : {})} className=\"min-w-0 truncate\" />\n : <span className=\"min-w-0 whitespace-normal\">{children}</span>}\n </span>\n );\n}\n"],"names":["isValidElement","jsxs","cn","badgeVariants","jsx","Icon","CodeLine"],"mappings":";;;;;;;;AAMA,MAAM,6BAAa,IAAI;AAAA,EACrB,CAAC,WAAW,iBAAiB;AAAA,EAC7B,CAAC,WAAW,cAAc;AAAA,EAC1B,CAAC,QAAQ,mDAAmD;AAAA,EAC5D,CAAC,QAAQ,mDAAmD;AAAA,EAC5D,CAAC,UAAU,uDAAuD;AAAA,EAClE,CAAC,OAAO,iDAAiD;AAAA,EACzD,CAAC,SAAS,qDAAqD;AAAA,EAC/D,CAAC,UAAU,uDAAuD;AACpE,CAAC;AAkBM,SAAS,YAAY;AAAA,EAC1B,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,MAAI,OAAO,aAAa,WAAW,CAAC,SAAS,KAAA,KAAU,SAAS,KAAK,QAAQ,IAAI,CAACA,MAAAA,eAAe,QAAQ,GAAG;AAC1G,UAAM,IAAI,MAAM,0FAA0F;AAAA,EAC5G;AACA,QAAM,aAAa,OAAO,IAAI,KAAK;AACnC,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,qBAAmB;AAAA,MACnB,OAAO,UAAU,OAAO,aAAa,WAAW,WAAW;AAAA,MAC3D,WAAWC,MAAAA;AAAAA,QACTC,cAAAA,cAAc,EAAE,SAAS,WAAW,MAAM,MAAM;AAAA,QAChD;AAAA,QACA,UAAU,WAAW;AAAA,QACrB,UAAU,mBAAmB;AAAA,QAC7B,UAAU,YAAY;AAAA,QACtB;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL,GAAI,eAAe,SAAY,EAAE,MAAA,IAAU,CAAA;AAAA,QAC3C,GAAI,UAAU,UAAU,EAAE,iBAAiB,sBAAsB,eAAe,SAAY,QAAQ,cAAc,qBAAA,IAAyB,CAAA;AAAA,MAAC;AAAA,MAG7I,UAAA;AAAA,QAAA,SAAS,UAAaC,+BAAC,QAAA,EAAK,oBAAgB,MAAC,eAAW,MAAC,WAAU,sEAClE,yCAACC,WAAA,EAAK,MAAY,OAAM,UAAS,WAAWH,MAAAA,GAAG,qCAAqC,aAAa,GAAG,EAAA,CACtG;AAAA,QACC,OAAO,aAAa,WACjBE,2BAAAA,IAACE,SAAAA,YAAS,MAAM,UAAW,GAAI,aAAa,SAAY,EAAE,aAAa,CAAA,GAAK,WAAU,oBAAmB,IACzGF,+BAAC,QAAA,EAAK,WAAU,6BAA6B,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGhE;;"}
@@ -0,0 +1,21 @@
1
+ import { HTMLAttributes, ReactElement } from 'react';
2
+ import { StaticIconComponent } from './Icon.js';
3
+ export type SymbolBadgeStyle = "badge" | "badge-outline" | "inline";
4
+ export type SymbolBadgeProps = Omit<HTMLAttributes<HTMLSpanElement>, "style" | "color" | "children"> & {
5
+ /** Filled badge, colored outline, or an unframed inline symbol. */
6
+ style?: SymbolBadgeStyle;
7
+ /** Named palette hue or CSS color, including CSS custom properties. */
8
+ color?: string;
9
+ /** Imported icon component placed before the symbol. */
10
+ icon?: StaticIconComponent;
11
+ /** Additional glyph classes, including adjustments for fixed-color SVGs. */
12
+ iconClassName?: string;
13
+ } & ({
14
+ children: string;
15
+ language?: string;
16
+ } | {
17
+ children: ReactElement;
18
+ language?: never;
19
+ });
20
+ export declare function SymbolBadge({ style, color, icon, iconClassName, language, children, className, title, ...props }: SymbolBadgeProps): import("react").JSX.Element;
21
+ //# sourceMappingURL=SymbolBadge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SymbolBadge.d.ts","sourceRoot":"","sources":["../../src/data/SymbolBadge.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAI/E,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAaxD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,eAAe,GAAG,QAAQ,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC,GAAG;IACrG,mEAAmE;IACnE,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,CACA;IAA8D,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACnG;IAAkE,QAAQ,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,CAC/G,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,KAAe,EACf,KAAgB,EAChB,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAgClB"}
@@ -0,0 +1,61 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { isValidElement } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { badgeVariants } from "./badge-variants.js";
5
+ import { CodeLine } from "./CodeLine.js";
6
+ import { Icon } from "./Icon.js";
7
+ const COLORS = /* @__PURE__ */ new Map([
8
+ ["neutral", "text-foreground"],
9
+ ["primary", "text-primary"],
10
+ ["blue", "text-blue-700 [[data-theme=dark]_&]:text-blue-300"],
11
+ ["cyan", "text-cyan-700 [[data-theme=dark]_&]:text-cyan-300"],
12
+ ["indigo", "text-indigo-700 [[data-theme=dark]_&]:text-indigo-300"],
13
+ ["sky", "text-sky-700 [[data-theme=dark]_&]:text-sky-300"],
14
+ ["slate", "text-slate-700 [[data-theme=dark]_&]:text-slate-300"],
15
+ ["violet", "text-violet-700 [[data-theme=dark]_&]:text-violet-300"]
16
+ ]);
17
+ function SymbolBadge({
18
+ style = "badge",
19
+ color = "indigo",
20
+ icon,
21
+ iconClassName,
22
+ language,
23
+ children,
24
+ className,
25
+ title,
26
+ ...props
27
+ }) {
28
+ if (typeof children === "string" ? !children.trim() || /[\r\n]/.test(children) : !isValidElement(children)) {
29
+ throw new Error("SymbolBadge requires a nonempty, single-line symbol; use CodeBlock for multiline source.");
30
+ }
31
+ const colorClass = COLORS.get(color);
32
+ return /* @__PURE__ */ jsxs(
33
+ "span",
34
+ {
35
+ ...props,
36
+ "data-symbol-style": style,
37
+ title: title ?? (typeof children === "string" ? children : void 0),
38
+ className: cn(
39
+ badgeVariants({ variant: "outline", size: "md" }),
40
+ "max-w-full items-baseline gap-1.5 rounded-md align-baseline text-[12px] leading-4",
41
+ style === "badge" && "border-transparent",
42
+ style === "badge-outline" && "border-current",
43
+ style === "inline" && "rounded-none border-0 p-0",
44
+ colorClass,
45
+ className
46
+ ),
47
+ style: {
48
+ ...colorClass === void 0 ? { color } : {},
49
+ ...style === "badge" ? { backgroundColor: `color-mix(in srgb, ${colorClass === void 0 ? color : "currentColor"} 16%, transparent)` } : {}
50
+ },
51
+ children: [
52
+ icon !== void 0 && /* @__PURE__ */ jsx("span", { "data-symbol-icon": true, "aria-hidden": true, className: "relative w-[1.25em] shrink-0 before:invisible before:content-['.']", children: /* @__PURE__ */ jsx(Icon, { icon, width: "1.25em", className: cn("absolute top-1/2 -translate-y-1/2", iconClassName) }) }),
53
+ typeof children === "string" ? /* @__PURE__ */ jsx(CodeLine, { code: children, ...language !== void 0 ? { language } : {}, className: "min-w-0 truncate" }) : /* @__PURE__ */ jsx("span", { className: "min-w-0 whitespace-normal", children })
54
+ ]
55
+ }
56
+ );
57
+ }
58
+ export {
59
+ SymbolBadge
60
+ };
61
+ //# sourceMappingURL=SymbolBadge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SymbolBadge.js","sources":["../../src/data/SymbolBadge.tsx"],"sourcesContent":["import { isValidElement, type HTMLAttributes, type ReactElement } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { badgeVariants } from \"./badge-variants\";\nimport { CodeLine } from \"./CodeLine\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\n\nconst COLORS = new Map([\n [\"neutral\", \"text-foreground\"],\n [\"primary\", \"text-primary\"],\n [\"blue\", \"text-blue-700 [[data-theme=dark]_&]:text-blue-300\"],\n [\"cyan\", \"text-cyan-700 [[data-theme=dark]_&]:text-cyan-300\"],\n [\"indigo\", \"text-indigo-700 [[data-theme=dark]_&]:text-indigo-300\"],\n [\"sky\", \"text-sky-700 [[data-theme=dark]_&]:text-sky-300\"],\n [\"slate\", \"text-slate-700 [[data-theme=dark]_&]:text-slate-300\"],\n [\"violet\", \"text-violet-700 [[data-theme=dark]_&]:text-violet-300\"],\n]);\n\nexport type SymbolBadgeStyle = \"badge\" | \"badge-outline\" | \"inline\";\n\nexport type SymbolBadgeProps = Omit<HTMLAttributes<HTMLSpanElement>, \"style\" | \"color\" | \"children\"> & {\n /** Filled badge, colored outline, or an unframed inline symbol. */\n style?: SymbolBadgeStyle;\n /** Named palette hue or CSS color, including CSS custom properties. */\n color?: string;\n /** Imported icon component placed before the symbol. */\n icon?: StaticIconComponent;\n /** Additional glyph classes, including adjustments for fixed-color SVGs. */\n iconClassName?: string;\n} & (\n | { /** Literal, nonempty, single-line symbol or expression. */ children: string; language?: string }\n | { /** Content already formatted by the caller, such as MathML. */ children: ReactElement; language?: never }\n);\n\nexport function SymbolBadge({\n style = \"badge\",\n color = \"indigo\",\n icon,\n iconClassName,\n language,\n children,\n className,\n title,\n ...props\n}: SymbolBadgeProps) {\n if (typeof children === \"string\" ? !children.trim() || /[\\r\\n]/.test(children) : !isValidElement(children)) {\n throw new Error(\"SymbolBadge requires a nonempty, single-line symbol; use CodeBlock for multiline source.\");\n }\n const colorClass = COLORS.get(color);\n return (\n <span\n {...props}\n data-symbol-style={style}\n title={title ?? (typeof children === \"string\" ? children : undefined)}\n className={cn(\n badgeVariants({ variant: \"outline\", size: \"md\" }),\n \"max-w-full items-baseline gap-1.5 rounded-md align-baseline text-[12px] leading-4\",\n style === \"badge\" && \"border-transparent\",\n style === \"badge-outline\" && \"border-current\",\n style === \"inline\" && \"rounded-none border-0 p-0\",\n colorClass,\n className,\n )}\n style={{\n ...(colorClass === undefined ? { color } : {}),\n ...(style === \"badge\" ? { backgroundColor: `color-mix(in srgb, ${colorClass === undefined ? color : \"currentColor\"} 16%, transparent)` } : {}),\n }}\n >\n {icon !== undefined && <span data-symbol-icon aria-hidden className=\"relative w-[1.25em] shrink-0 before:invisible before:content-['.']\">\n <Icon icon={icon} width=\"1.25em\" className={cn(\"absolute top-1/2 -translate-y-1/2\", iconClassName)} />\n </span>}\n {typeof children === \"string\"\n ? <CodeLine code={children} {...(language !== undefined ? { language } : {})} className=\"min-w-0 truncate\" />\n : <span className=\"min-w-0 whitespace-normal\">{children}</span>}\n </span>\n );\n}\n"],"names":[],"mappings":";;;;;;AAMA,MAAM,6BAAa,IAAI;AAAA,EACrB,CAAC,WAAW,iBAAiB;AAAA,EAC7B,CAAC,WAAW,cAAc;AAAA,EAC1B,CAAC,QAAQ,mDAAmD;AAAA,EAC5D,CAAC,QAAQ,mDAAmD;AAAA,EAC5D,CAAC,UAAU,uDAAuD;AAAA,EAClE,CAAC,OAAO,iDAAiD;AAAA,EACzD,CAAC,SAAS,qDAAqD;AAAA,EAC/D,CAAC,UAAU,uDAAuD;AACpE,CAAC;AAkBM,SAAS,YAAY;AAAA,EAC1B,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,MAAI,OAAO,aAAa,WAAW,CAAC,SAAS,KAAA,KAAU,SAAS,KAAK,QAAQ,IAAI,CAAC,eAAe,QAAQ,GAAG;AAC1G,UAAM,IAAI,MAAM,0FAA0F;AAAA,EAC5G;AACA,QAAM,aAAa,OAAO,IAAI,KAAK;AACnC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,qBAAmB;AAAA,MACnB,OAAO,UAAU,OAAO,aAAa,WAAW,WAAW;AAAA,MAC3D,WAAW;AAAA,QACT,cAAc,EAAE,SAAS,WAAW,MAAM,MAAM;AAAA,QAChD;AAAA,QACA,UAAU,WAAW;AAAA,QACrB,UAAU,mBAAmB;AAAA,QAC7B,UAAU,YAAY;AAAA,QACtB;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL,GAAI,eAAe,SAAY,EAAE,MAAA,IAAU,CAAA;AAAA,QAC3C,GAAI,UAAU,UAAU,EAAE,iBAAiB,sBAAsB,eAAe,SAAY,QAAQ,cAAc,qBAAA,IAAyB,CAAA;AAAA,MAAC;AAAA,MAG7I,UAAA;AAAA,QAAA,SAAS,UAAa,oBAAC,QAAA,EAAK,oBAAgB,MAAC,eAAW,MAAC,WAAU,sEAClE,8BAAC,MAAA,EAAK,MAAY,OAAM,UAAS,WAAW,GAAG,qCAAqC,aAAa,GAAG,EAAA,CACtG;AAAA,QACC,OAAO,aAAa,WACjB,oBAAC,YAAS,MAAM,UAAW,GAAI,aAAa,SAAY,EAAE,aAAa,CAAA,GAAK,WAAU,oBAAmB,IACzG,oBAAC,QAAA,EAAK,WAAU,6BAA6B,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGhE;"}
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
5
  const Icon = require("../Icon.cjs");
6
6
  const button = require("../../components/button.cjs");
7
+ const select = require("../../components/select.cjs");
7
8
  const SplitButton = require("../../components/SplitButton.cjs");
8
9
  const clipboard = require("../../components/clipboard.cjs");
9
10
  const utils = require("../../lib/utils.cjs");
@@ -12,6 +13,7 @@ const TabButton = require("../TabButton.cjs");
12
13
  const context = require("./context.cjs");
13
14
  const copy = require("./copy.cjs");
14
15
  const status = require("./status.cjs");
16
+ const marks = require("./marks.cjs");
15
17
  const MarkIcons = require("./MarkIcons.cjs");
16
18
  const TestFailureDetail = require("./TestFailureDetail.cjs");
17
19
  const UiClass = require("../../icons/components/UiClass.cjs");
@@ -87,11 +89,11 @@ function CopyDetailsButton({ node }) {
87
89
  }
88
90
  );
89
91
  }
90
- function DetailHeader({ node, actions }) {
92
+ function DetailHeader({ node, actionNode, actions, attemptChooser }) {
91
93
  const runner = context.useTestRunner();
92
94
  const outcome = status.outcomeFor(node);
93
- const canRerun = !!runner.onRerun && node.framework !== "task";
94
- const canStop = !!runner.onStop && !!node.can_stop && !!node.task_id;
95
+ const canRerun = !!runner.onRerun && actionNode.framework !== "task";
96
+ const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;
95
97
  const location = node.file ? `${node.file}${node.line ? `:${node.line}` : ""}` : void 0;
96
98
  return /* @__PURE__ */ jsxRuntime.jsxs(
97
99
  "div",
@@ -102,7 +104,7 @@ function DetailHeader({ node, actions }) {
102
104
  outcome ? /* @__PURE__ */ jsxRuntime.jsx(MarkIcons.OutcomeIcon, { outcome, className: "text-lg" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiClass.UiClass, className: "shrink-0 text-lg text-muted-foreground" }),
103
105
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "min-w-0 shrink truncate text-base font-semibold", title: node.name, children: node.name }),
104
106
  node.framework && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground", children: [
105
- /* @__PURE__ */ jsxRuntime.jsx(MarkIcons.ToolIcon, { tool: node.framework, className: "text-sm" }),
107
+ /* @__PURE__ */ jsxRuntime.jsx(MarkIcons.ToolIcon, { tool: marks.toolKeyFor(node), className: "text-sm" }),
106
108
  node.framework
107
109
  ] }),
108
110
  node.duration ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
@@ -118,6 +120,7 @@ function DetailHeader({ node, actions }) {
118
120
  }
119
121
  ),
120
122
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: [
123
+ attemptChooser,
121
124
  actions,
122
125
  /* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, { node }),
123
126
  canRerun && /* @__PURE__ */ jsxRuntime.jsxs(
@@ -125,7 +128,7 @@ function DetailHeader({ node, actions }) {
125
128
  {
126
129
  size: "sm",
127
130
  disabled: runner.busy.rerun,
128
- onClick: () => runner.onRerun(node),
131
+ onClick: () => runner.onRerun(actionNode),
129
132
  title: "Rerun this test or subtree",
130
133
  children: [
131
134
  /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiRefresh.UiRefresh, className: "mr-1 text-sm" }),
@@ -139,7 +142,7 @@ function DetailHeader({ node, actions }) {
139
142
  size: "sm",
140
143
  variant: "destructive",
141
144
  disabled: runner.busy.stop,
142
- onClick: () => runner.onStop(node),
145
+ onClick: () => runner.onStop(actionNode),
143
146
  title: "Stop this running task",
144
147
  children: [
145
148
  /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiStop.UiStop, className: "mr-1 text-sm" }),
@@ -155,7 +158,8 @@ function DetailHeader({ node, actions }) {
155
158
  function TestDetailPanel() {
156
159
  var _a;
157
160
  const runner = context.useTestRunner();
158
- const node = runner.selected;
161
+ const selected = runner.selected;
162
+ const [attemptSelection, setAttemptSelection] = react.useState(null);
159
163
  const controlled = runner.activeTab !== void 0;
160
164
  const [internalTab, setInternalTab] = react.useState("detail");
161
165
  const activeTab = controlled ? runner.activeTab : internalTab;
@@ -164,22 +168,32 @@ function TestDetailPanel() {
164
168
  if (controlled) (_a2 = runner.onTabChange) == null ? void 0 : _a2.call(runner, id);
165
169
  else setInternalTab(id);
166
170
  };
167
- if (!node) {
171
+ if (!selected) {
168
172
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center text-sm text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
169
173
  /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiClass.UiClass, className: "mb-2 block text-4xl" }),
170
174
  "Select a test to view its details."
171
175
  ] }) });
172
176
  }
177
+ const attempts = runner.adapters.resolveAttempts(selected, runner);
178
+ const key = selected.task_id ?? selected.name;
179
+ const attemptId = runner.activeAttempt ?? ((attemptSelection == null ? void 0 : attemptSelection.key) === key ? attemptSelection.id : void 0);
180
+ const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);
181
+ const node = attempts.length > 1 && attempt ? attempt.node : selected;
182
+ const attemptChooser = attempts.length > 1 && attempt ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-44 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(select.Select, { "aria-label": "Attempt", value: String(attempt.id), className: "h-7 text-xs", onChange: (event) => {
183
+ const id = Number(event.target.value);
184
+ if (runner.onAttemptChange) runner.onAttemptChange(id);
185
+ else setAttemptSelection({ key, id });
186
+ }, options: attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== void 0 ? ` · ${status.formatTestDuration(a.durationMs * 1e6)}` : ""}` })) }) }) : null;
173
187
  const detailAdapter = runner.adapters.resolveDetail(node, runner);
174
- const actionAdapter = runner.adapters.resolveActions(node, runner);
188
+ const actionAdapter = runner.adapters.resolveActions(selected, runner);
175
189
  const tabs = runner.adapters.resolveTabs(node, runner);
176
190
  const ownsScroll = runner.adapters.ownsScroll(node, runner);
177
191
  const body = (detailAdapter == null ? void 0 : detailAdapter.renderDetail) ? detailAdapter.renderDetail({ node, runner }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultDetailBody, { node });
178
- const actions = (_a = actionAdapter == null ? void 0 : actionAdapter.nodeActions) == null ? void 0 : _a.call(actionAdapter, { node, runner });
192
+ const actions = (_a = actionAdapter == null ? void 0 : actionAdapter.nodeActions) == null ? void 0 : _a.call(actionAdapter, { node: selected, runner });
179
193
  const detailRegion = utils.cn("min-h-0 flex-1 [container-type:size]", !ownsScroll && "overflow-y-auto");
180
194
  if (tabs.length === 0) {
181
195
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
182
- /* @__PURE__ */ jsxRuntime.jsx(DetailHeader, { node, actions }),
196
+ /* @__PURE__ */ jsxRuntime.jsx(DetailHeader, { node, actionNode: selected, actions, attemptChooser }),
183
197
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: detailRegion, children: body })
184
198
  ] });
185
199
  }
@@ -187,7 +201,7 @@ function TestDetailPanel() {
187
201
  const allTabs = [detailTab, ...tabs];
188
202
  const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;
189
203
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
190
- /* @__PURE__ */ jsxRuntime.jsx(DetailHeader, { node, actions }),
204
+ /* @__PURE__ */ jsxRuntime.jsx(DetailHeader, { node, actionNode: selected, actions, attemptChooser }),
191
205
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1", children: allTabs.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
192
206
  TabButton.TabButton,
193
207
  {