spiracha 1.4.0 → 2.0.0

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 (319) hide show
  1. package/AGENTS.md +116 -164
  2. package/README.md +92 -237
  3. package/apps/ui/AGENTS.md +36 -7
  4. package/apps/ui/README.md +79 -6
  5. package/apps/ui/components.json +21 -0
  6. package/apps/ui/package.json +65 -0
  7. package/apps/ui/{dist/client → public}/icon.svg +2 -1
  8. package/apps/ui/src/components/antigravity-conversations-table.tsx +206 -0
  9. package/apps/ui/src/components/antigravity-keychain-panel.tsx +76 -0
  10. package/apps/ui/src/components/antigravity-workspaces-table.tsx +63 -0
  11. package/apps/ui/src/components/app-shell.tsx +124 -0
  12. package/apps/ui/src/components/breadcrumbs.tsx +53 -0
  13. package/apps/ui/src/components/claude-code-sessions-table.tsx +114 -0
  14. package/apps/ui/src/components/claude-code-workspaces-table.tsx +55 -0
  15. package/apps/ui/src/components/cursor-threads-table.tsx +189 -0
  16. package/apps/ui/src/components/cursor-workspaces-table.tsx +140 -0
  17. package/apps/ui/src/components/data-table.tsx +241 -0
  18. package/apps/ui/src/components/delete-confirm-dialog.tsx +88 -0
  19. package/apps/ui/src/components/export-dialog.tsx +184 -0
  20. package/apps/ui/src/components/json-panel.tsx +17 -0
  21. package/apps/ui/src/components/kiro-sessions-table.tsx +112 -0
  22. package/apps/ui/src/components/kiro-workspaces-table.tsx +53 -0
  23. package/apps/ui/src/components/list-search-input.tsx +18 -0
  24. package/apps/ui/src/components/loading-panel.tsx +23 -0
  25. package/apps/ui/src/components/metadata-section.tsx +31 -0
  26. package/apps/ui/src/components/metric-card.tsx +19 -0
  27. package/apps/ui/src/components/opencode-sessions-table.tsx +128 -0
  28. package/apps/ui/src/components/opencode-workspaces-table.tsx +57 -0
  29. package/apps/ui/src/components/page-header.tsx +33 -0
  30. package/apps/ui/src/components/projects-loading-state.tsx +74 -0
  31. package/apps/ui/src/components/projects-table.tsx +108 -0
  32. package/apps/ui/src/components/qoder-sessions-table.tsx +112 -0
  33. package/apps/ui/src/components/qoder-workspaces-table.tsx +53 -0
  34. package/apps/ui/src/components/recent-threads-list.tsx +52 -0
  35. package/apps/ui/src/components/reload-error-panel.tsx +20 -0
  36. package/apps/ui/src/components/text-document-panel.tsx +19 -0
  37. package/apps/ui/src/components/theme-toggle.tsx +48 -0
  38. package/apps/ui/src/components/threads-table.tsx +202 -0
  39. package/apps/ui/src/components/transcript-view.tsx +552 -0
  40. package/apps/ui/src/components/ui/alert-dialog.tsx +160 -0
  41. package/apps/ui/src/components/ui/badge.tsx +45 -0
  42. package/apps/ui/src/components/ui/button.tsx +62 -0
  43. package/apps/ui/src/components/ui/checkbox.tsx +29 -0
  44. package/apps/ui/src/components/ui/dialog.tsx +137 -0
  45. package/apps/ui/src/components/ui/dropdown-menu.tsx +217 -0
  46. package/apps/ui/src/components/ui/input.tsx +21 -0
  47. package/apps/ui/src/components/ui/scroll-area.tsx +46 -0
  48. package/apps/ui/src/components/ui/select.tsx +163 -0
  49. package/apps/ui/src/components/ui/separator.tsx +28 -0
  50. package/apps/ui/src/components/ui/sheet.tsx +107 -0
  51. package/apps/ui/src/components/ui/skeleton.tsx +7 -0
  52. package/apps/ui/src/components/ui/table.tsx +76 -0
  53. package/apps/ui/src/components/ui/tabs.tsx +71 -0
  54. package/apps/ui/src/components/ui/tooltip.tsx +44 -0
  55. package/apps/ui/src/integrations/tanstack-query/devtools.tsx +6 -0
  56. package/apps/ui/src/integrations/tanstack-query/root-provider.tsx +10 -0
  57. package/apps/ui/src/lib/antigravity-conversation-state.ts +28 -0
  58. package/apps/ui/src/lib/antigravity-queries.ts +33 -0
  59. package/apps/ui/src/lib/antigravity-server.ts +128 -0
  60. package/apps/ui/src/lib/antigravity-transcript-events.ts +335 -0
  61. package/apps/ui/src/lib/claude-code-queries.ts +26 -0
  62. package/apps/ui/src/lib/claude-code-server.ts +75 -0
  63. package/apps/ui/src/lib/claude-code-transcript-events.ts +154 -0
  64. package/apps/ui/src/lib/codex-queries.ts +66 -0
  65. package/apps/ui/src/lib/codex-server.ts +209 -0
  66. package/apps/ui/src/lib/cursor-queries.ts +37 -0
  67. package/apps/ui/src/lib/cursor-server.ts +272 -0
  68. package/apps/ui/src/lib/cursor-transcript-events.ts +229 -0
  69. package/apps/ui/src/lib/download.ts +166 -0
  70. package/apps/ui/src/lib/formatters.ts +149 -0
  71. package/apps/ui/src/lib/kiro-queries.ts +22 -0
  72. package/apps/ui/src/lib/kiro-server.ts +73 -0
  73. package/apps/ui/src/lib/kiro-transcript-events.ts +113 -0
  74. package/apps/ui/src/lib/opencode-queries.ts +37 -0
  75. package/apps/ui/src/lib/opencode-server.ts +73 -0
  76. package/apps/ui/src/lib/opencode-transcript-events.ts +221 -0
  77. package/apps/ui/src/lib/package-metadata.ts +16 -0
  78. package/apps/ui/src/lib/path-utils.ts +13 -0
  79. package/apps/ui/src/lib/qoder-queries.ts +22 -0
  80. package/apps/ui/src/lib/qoder-server.ts +79 -0
  81. package/apps/ui/src/lib/qoder-transcript-events.ts +139 -0
  82. package/apps/ui/src/lib/route-search.ts +68 -0
  83. package/apps/ui/src/lib/settings-store.tsx +59 -0
  84. package/apps/ui/src/lib/source-session-export-server.ts +56 -0
  85. package/apps/ui/src/lib/text-filter.ts +22 -0
  86. package/apps/ui/src/lib/thread-id.ts +3 -0
  87. package/apps/ui/src/lib/thread-transcript-stats.ts +57 -0
  88. package/apps/ui/src/lib/utils.ts +7 -0
  89. package/apps/ui/src/routeTree.gen.ts +730 -0
  90. package/apps/ui/src/router.tsx +20 -0
  91. package/apps/ui/src/routes/$threadId.tsx +22 -0
  92. package/apps/ui/src/routes/__root.tsx +127 -0
  93. package/apps/ui/src/routes/analytics.tsx +143 -0
  94. package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +438 -0
  95. package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +127 -0
  96. package/apps/ui/src/routes/antigravity.index.tsx +51 -0
  97. package/apps/ui/src/routes/api.v1.conversation-query.ts +12 -0
  98. package/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts +12 -0
  99. package/apps/ui/src/routes/api.v1.conversations.$source.$id.ts +12 -0
  100. package/apps/ui/src/routes/api.v1.conversations.ts +12 -0
  101. package/apps/ui/src/routes/api.v1.resolve.ts +12 -0
  102. package/apps/ui/src/routes/api.v1.sources.ts +12 -0
  103. package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +342 -0
  104. package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +135 -0
  105. package/apps/ui/src/routes/claude-code.index.tsx +48 -0
  106. package/apps/ui/src/routes/codex.$project.tsx +390 -0
  107. package/apps/ui/src/routes/codex.index.tsx +116 -0
  108. package/apps/ui/src/routes/cursor-threads.$composerId.tsx +435 -0
  109. package/apps/ui/src/routes/cursor.$workspaceKey.tsx +416 -0
  110. package/apps/ui/src/routes/cursor.index.tsx +120 -0
  111. package/apps/ui/src/routes/index.tsx +124 -0
  112. package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +345 -0
  113. package/apps/ui/src/routes/kiro.$workspaceKey.tsx +136 -0
  114. package/apps/ui/src/routes/kiro.index.tsx +48 -0
  115. package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +334 -0
  116. package/apps/ui/src/routes/opencode.$workspaceKey.tsx +135 -0
  117. package/apps/ui/src/routes/opencode.index.tsx +48 -0
  118. package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +341 -0
  119. package/apps/ui/src/routes/qoder.$workspaceKey.tsx +139 -0
  120. package/apps/ui/src/routes/qoder.index.tsx +48 -0
  121. package/apps/ui/src/routes/settings.tsx +91 -0
  122. package/apps/ui/src/routes/threads.$threadId.tsx +619 -0
  123. package/apps/ui/src/styles.css +122 -0
  124. package/apps/ui/tsconfig.json +29 -0
  125. package/apps/ui/vite.config.ts +84 -0
  126. package/bin/spiracha.ts +32 -0
  127. package/package.json +50 -60
  128. package/src/client.ts +290 -0
  129. package/src/lib/antigravity-db.ts +213 -19
  130. package/src/lib/antigravity-exporter-types.ts +1 -0
  131. package/src/lib/claude-code-db.ts +655 -0
  132. package/src/lib/claude-code-exporter-types.ts +110 -0
  133. package/src/lib/claude-code-transcript-phase.ts +25 -0
  134. package/src/lib/claude-code-transcript.ts +180 -0
  135. package/src/lib/codex-analytics.ts +1 -1
  136. package/src/lib/codex-browser-db.ts +856 -61
  137. package/src/lib/codex-browser-export.ts +24 -16
  138. package/src/lib/codex-browser-types.ts +1 -1
  139. package/src/lib/codex-thread-cache.ts +17 -1
  140. package/src/lib/{codex-exporter-types.ts → codex-thread-types.ts} +2 -30
  141. package/src/lib/{codex-exporter-transcript.ts → codex-transcript-renderer.ts} +64 -28
  142. package/src/lib/concurrency.ts +25 -0
  143. package/src/lib/conversation-api.ts +792 -0
  144. package/src/lib/conversation-data/adapter-helpers.ts +110 -0
  145. package/src/lib/conversation-data/antigravity-adapter.ts +148 -0
  146. package/src/lib/conversation-data/claude-code-adapter.ts +175 -0
  147. package/src/lib/conversation-data/codex-adapter.ts +245 -0
  148. package/src/lib/conversation-data/cursor-adapter.ts +171 -0
  149. package/src/lib/conversation-data/index.ts +311 -0
  150. package/src/lib/conversation-data/kiro-adapter.ts +140 -0
  151. package/src/lib/conversation-data/message-selector.ts +37 -0
  152. package/src/lib/conversation-data/opencode-adapter.ts +180 -0
  153. package/src/lib/conversation-data/path-match.ts +79 -0
  154. package/src/lib/conversation-data/qoder-adapter.ts +230 -0
  155. package/src/lib/conversation-data/types.ts +116 -0
  156. package/src/lib/cursor-db.ts +67 -21
  157. package/src/lib/cursor-exporter-types.ts +0 -19
  158. package/src/lib/kiro-db.ts +949 -0
  159. package/src/lib/kiro-exporter-types.ts +100 -0
  160. package/src/lib/kiro-transcript-phase.ts +41 -0
  161. package/src/lib/kiro-transcript.ts +115 -0
  162. package/src/lib/opencode-db.ts +610 -0
  163. package/src/lib/opencode-exporter-types.ts +129 -0
  164. package/src/lib/opencode-think-tags.ts +126 -0
  165. package/src/lib/opencode-transcript-phase.ts +50 -0
  166. package/src/lib/opencode-transcript.ts +194 -0
  167. package/src/lib/qoder-acp-client.ts +268 -0
  168. package/src/lib/qoder-db.ts +1601 -0
  169. package/src/lib/qoder-exporter-types.ts +114 -0
  170. package/src/lib/qoder-transcript-phase.ts +41 -0
  171. package/src/lib/qoder-transcript.ts +112 -0
  172. package/src/lib/shared.ts +25 -0
  173. package/src/lib/ui-export-archive.ts +61 -0
  174. package/apps/ui/dist/client/assets/_threadId-CAIeH5mq.js +0 -1
  175. package/apps/ui/dist/client/assets/analytics-DK2jdvmI.js +0 -1
  176. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DmD0sN8X.js +0 -7
  177. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +0 -1
  178. package/apps/ui/dist/client/assets/antigravity-keychain-panel-BRHVjFAM.js +0 -1
  179. package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +0 -1
  180. package/apps/ui/dist/client/assets/antigravity._workspaceKey-CGV7amas.js +0 -1
  181. package/apps/ui/dist/client/assets/antigravity.index-DEEkjlZa.js +0 -1
  182. package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +0 -1
  183. package/apps/ui/dist/client/assets/badge-DfWvPfKF.js +0 -1
  184. package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +0 -1
  185. package/apps/ui/dist/client/assets/createServerFn-CgRRVpBH.js +0 -3
  186. package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +0 -1
  187. package/apps/ui/dist/client/assets/cursor-threads._composerId-Ti2gTm63.js +0 -1
  188. package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +0 -1
  189. package/apps/ui/dist/client/assets/cursor._workspaceKey-DzD_c6j9.js +0 -1
  190. package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +0 -1
  191. package/apps/ui/dist/client/assets/cursor.index-PkCFVcl4.js +0 -2
  192. package/apps/ui/dist/client/assets/data-table-Br78_dk3.js +0 -4
  193. package/apps/ui/dist/client/assets/delete-confirm-dialog-BHRmyLHr.js +0 -11
  194. package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +0 -1
  195. package/apps/ui/dist/client/assets/dist-BhZOxAPP.js +0 -1
  196. package/apps/ui/dist/client/assets/dist-DvMS2965.js +0 -1
  197. package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +0 -1
  198. package/apps/ui/dist/client/assets/dropdown-menu-Dy5cLzTy.js +0 -1
  199. package/apps/ui/dist/client/assets/es2015-BkCWttyM.js +0 -41
  200. package/apps/ui/dist/client/assets/export-dialog-C86SYHJn.js +0 -1
  201. package/apps/ui/dist/client/assets/formatters-BM9kB7ed.js +0 -1
  202. package/apps/ui/dist/client/assets/index-CPvAP-jk.js +0 -149
  203. package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +0 -1
  204. package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +0 -1
  205. package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +0 -1
  206. package/apps/ui/dist/client/assets/projects._project-Ckf6muWZ.js +0 -1
  207. package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +0 -1
  208. package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +0 -1
  209. package/apps/ui/dist/client/assets/projects.index-V5ibNZII.js +0 -3
  210. package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +0 -1
  211. package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +0 -1
  212. package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +0 -1
  213. package/apps/ui/dist/client/assets/routes-Df2tgpyd.js +0 -1
  214. package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +0 -1
  215. package/apps/ui/dist/client/assets/select-Bf2GwfPR.js +0 -1
  216. package/apps/ui/dist/client/assets/settings-5VQIzME0.js +0 -1
  217. package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +0 -1
  218. package/apps/ui/dist/client/assets/styles-BhRkXgwB.css +0 -1
  219. package/apps/ui/dist/client/assets/tabs-OO4VU5KR.js +0 -7
  220. package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +0 -2
  221. package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +0 -1
  222. package/apps/ui/dist/client/assets/threads._threadId-BgDhFj2I.js +0 -1
  223. package/apps/ui/dist/client/assets/useMutation-CC_B7uy5.js +0 -1
  224. package/apps/ui/dist/client/assets/useQuery-C5By6WKU.js +0 -1
  225. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CRW2kqj0.js +0 -232
  226. package/apps/ui/dist/server/assets/_threadId-B6SrBR9E.js +0 -6
  227. package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +0 -16
  228. package/apps/ui/dist/server/assets/analytics-CtpOMVMO.js +0 -146
  229. package/apps/ui/dist/server/assets/antigravity-conversation-state-HgzS302O.js +0 -16
  230. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BtexWY-K.js +0 -613
  231. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-D426O-64.js +0 -11
  232. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-zMrR6v6R.js +0 -20
  233. package/apps/ui/dist/server/assets/antigravity-db-Bh8_U9uw.js +0 -580
  234. package/apps/ui/dist/server/assets/antigravity-keychain-DOiuHDwK.js +0 -126
  235. package/apps/ui/dist/server/assets/antigravity-keychain-panel-CC7FLmAK.js +0 -55
  236. package/apps/ui/dist/server/assets/antigravity-queries-CGrJO9Vr.js +0 -37
  237. package/apps/ui/dist/server/assets/antigravity-server-CHRjVFqN.js +0 -114
  238. package/apps/ui/dist/server/assets/antigravity._workspaceKey-3m_MzNFA.js +0 -11
  239. package/apps/ui/dist/server/assets/antigravity._workspaceKey-B0QeYUOh.js +0 -28
  240. package/apps/ui/dist/server/assets/antigravity._workspaceKey-C3kP-qyN.js +0 -210
  241. package/apps/ui/dist/server/assets/antigravity.index-CPCYkqCi.js +0 -104
  242. package/apps/ui/dist/server/assets/antigravity.index-DudTB3Tq.js +0 -11
  243. package/apps/ui/dist/server/assets/badge-EvdhKK_Z.js +0 -26
  244. package/apps/ui/dist/server/assets/button-CmTDnzOn.js +0 -46
  245. package/apps/ui/dist/server/assets/checkbox-C0hovF41.js +0 -19
  246. package/apps/ui/dist/server/assets/codex-queries-CtgeZ7VL.js +0 -99
  247. package/apps/ui/dist/server/assets/codex-server-CNXSJuc2.js +0 -2004
  248. package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +0 -18
  249. package/apps/ui/dist/server/assets/createServerRpc-CF_DEwnm.js +0 -12
  250. package/apps/ui/dist/server/assets/createSsrRpc-C754NPki.js +0 -16
  251. package/apps/ui/dist/server/assets/cursor-db-cYZEU3WQ.js +0 -830
  252. package/apps/ui/dist/server/assets/cursor-exporter-types-CI3goo-c.js +0 -34
  253. package/apps/ui/dist/server/assets/cursor-queries-NCIM0Nat.js +0 -67
  254. package/apps/ui/dist/server/assets/cursor-recovery-nq-kR62j.js +0 -361
  255. package/apps/ui/dist/server/assets/cursor-server-C3q7hrp-.js +0 -213
  256. package/apps/ui/dist/server/assets/cursor-threads._composerId-BB0Y_Mao.js +0 -11
  257. package/apps/ui/dist/server/assets/cursor-threads._composerId-BPJFWfJj.js +0 -18
  258. package/apps/ui/dist/server/assets/cursor-threads._composerId-BkMMTQ3v.js +0 -582
  259. package/apps/ui/dist/server/assets/cursor-transcript-2iL3KFSK.js +0 -125
  260. package/apps/ui/dist/server/assets/cursor._workspaceKey-C5PdTaKB.js +0 -28
  261. package/apps/ui/dist/server/assets/cursor._workspaceKey-Ckj7apKI.js +0 -401
  262. package/apps/ui/dist/server/assets/cursor._workspaceKey-nmg3YIOQ.js +0 -11
  263. package/apps/ui/dist/server/assets/cursor.index-CcsX7DG0.js +0 -11
  264. package/apps/ui/dist/server/assets/cursor.index-DkWT-mxw.js +0 -189
  265. package/apps/ui/dist/server/assets/data-table-Cdct823O.js +0 -189
  266. package/apps/ui/dist/server/assets/delete-confirm-dialog-BcBNCxWB.js +0 -144
  267. package/apps/ui/dist/server/assets/download-DMmiy1xf.js +0 -92
  268. package/apps/ui/dist/server/assets/dropdown-menu-Dy_9t6TN.js +0 -36
  269. package/apps/ui/dist/server/assets/empty-plugin-adapters-B3lHh1La.js +0 -5
  270. package/apps/ui/dist/server/assets/export-dialog-BItjWgkZ.js +0 -240
  271. package/apps/ui/dist/server/assets/formatters-FJaGZgJk.js +0 -91
  272. package/apps/ui/dist/server/assets/loading-panel-BGFnWseS.js +0 -27
  273. package/apps/ui/dist/server/assets/metric-card-ByEeLu0r.js +0 -23
  274. package/apps/ui/dist/server/assets/model-label-B1NWGc65.js +0 -13
  275. package/apps/ui/dist/server/assets/page-header-VNSaM3xd.js +0 -29
  276. package/apps/ui/dist/server/assets/path-transforms-DL2IwtYd.js +0 -31
  277. package/apps/ui/dist/server/assets/projects._project-Bpbc3C-L.js +0 -20
  278. package/apps/ui/dist/server/assets/projects._project-Bshqk7JA.js +0 -12
  279. package/apps/ui/dist/server/assets/projects._project-CvZho6EQ.js +0 -395
  280. package/apps/ui/dist/server/assets/projects.index-BLXOx5eL.js +0 -12
  281. package/apps/ui/dist/server/assets/projects.index-BkLiF2FF.js +0 -182
  282. package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +0 -14
  283. package/apps/ui/dist/server/assets/reload-error-panel-BJMxY3U1.js +0 -25
  284. package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +0 -38
  285. package/apps/ui/dist/server/assets/router-6m-ihwqA.js +0 -410
  286. package/apps/ui/dist/server/assets/routes-Bsact1uB.js +0 -184
  287. package/apps/ui/dist/server/assets/routes-BzkJgq4W.js +0 -34
  288. package/apps/ui/dist/server/assets/select-GW76p-ld.js +0 -76
  289. package/apps/ui/dist/server/assets/settings-OayxIYQQ.js +0 -100
  290. package/apps/ui/dist/server/assets/settings-store-DpEJEQ7M.js +0 -52
  291. package/apps/ui/dist/server/assets/shared-CPRNYIql.js +0 -134
  292. package/apps/ui/dist/server/assets/sqlite-error-LZDrnxdd.js +0 -13
  293. package/apps/ui/dist/server/assets/start-CI_0bSiY.js +0 -4
  294. package/apps/ui/dist/server/assets/tabs-CGA13IZM.js +0 -502
  295. package/apps/ui/dist/server/assets/text-filter-CGKxMCKt.js +0 -36
  296. package/apps/ui/dist/server/assets/threads._threadId-BSSK4nkI.js +0 -26
  297. package/apps/ui/dist/server/assets/threads._threadId-Bs0yuqO9.js +0 -18
  298. package/apps/ui/dist/server/assets/threads._threadId-D2GYkVn6.js +0 -639
  299. package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +0 -83
  300. package/apps/ui/dist/server/assets/utils-C_uf36nf.js +0 -8
  301. package/apps/ui/dist/server/server.js +0 -5847
  302. package/bin/codex-chats-claude.js +0 -5
  303. package/bin/codex-chats.js +0 -5
  304. package/bin/spiracha.js +0 -5
  305. package/src/export-chats.ts +0 -120
  306. package/src/export-claude.ts +0 -36
  307. package/src/export-cursor.ts +0 -244
  308. package/src/lib/claude-exporter.ts +0 -864
  309. package/src/lib/codex-exporter-cli.ts +0 -277
  310. package/src/lib/codex-exporter-db.ts +0 -319
  311. package/src/lib/codex-exporter.ts +0 -115
  312. package/src/lib/cursor-exporter.ts +0 -266
  313. package/src/lib/interactive-cli.ts +0 -433
  314. package/src/lib/native-open.ts +0 -54
  315. package/src/mcp-server.ts +0 -137
  316. package/src/spiracha.ts +0 -116
  317. package/src/ui-cli.ts +0 -310
  318. /package/apps/ui/{dist/client → public}/manifest.json +0 -0
  319. /package/apps/ui/{dist/client → public}/robots.txt +0 -0
@@ -0,0 +1,619 @@
1
+ import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
2
+ import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
3
+ import { Download, Trash2 } from 'lucide-react';
4
+ import { useState } from 'react';
5
+ import { Breadcrumbs } from '#/components/breadcrumbs';
6
+ import { DeleteConfirmDialog } from '#/components/delete-confirm-dialog';
7
+ import { ExportDialog } from '#/components/export-dialog';
8
+ import { JsonPanel } from '#/components/json-panel';
9
+ import { LoadingPanel } from '#/components/loading-panel';
10
+ import { MetadataSection } from '#/components/metadata-section';
11
+ import { MetricCard } from '#/components/metric-card';
12
+ import { PageHeader } from '#/components/page-header';
13
+ import { TranscriptView } from '#/components/transcript-view';
14
+ import { Badge } from '#/components/ui/badge';
15
+ import { Button } from '#/components/ui/button';
16
+ import { Checkbox } from '#/components/ui/checkbox';
17
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '#/components/ui/tabs';
18
+ import { threadSnapshotQueryOptions, threadTranscriptQueryOptions } from '#/lib/codex-queries';
19
+ import { deleteThreadFn, exportThreadFn, type getThreadSnapshotFn } from '#/lib/codex-server';
20
+ import { downloadTextFile, downloadUrlFile } from '#/lib/download';
21
+ import { formatBooleanLabel, formatBytes, formatDateTime, formatList, formatTokens } from '#/lib/formatters';
22
+ import { useSettings } from '#/lib/settings-store';
23
+
24
+ type ThreadSnapshot = Awaited<ReturnType<typeof getThreadSnapshotFn>>;
25
+
26
+ type TranscriptControlsProps = {
27
+ rawJsonDisabled?: boolean;
28
+ showCommentary: boolean;
29
+ showExtraEvents: boolean;
30
+ showRawJson: boolean;
31
+ showToolCalls: boolean;
32
+ showUserMessages: boolean;
33
+ onShowCommentaryChange: (checked: boolean) => void;
34
+ onShowExtraEventsChange: (checked: boolean) => void;
35
+ onShowRawJsonChange: (checked: boolean) => void;
36
+ onShowToolCallsChange: (checked: boolean) => void;
37
+ onShowUserMessagesChange: (checked: boolean) => void;
38
+ };
39
+
40
+ type ThreadMetadataProps = {
41
+ snapshot: ThreadSnapshot;
42
+ };
43
+
44
+ const buildThreadItems = (snapshot: ThreadSnapshot) => {
45
+ return [
46
+ { label: 'Thread ID', value: <span data-mono="true">{snapshot.thread.id}</span> },
47
+ { label: 'Project', value: snapshot.project },
48
+ { label: 'CWD', value: <span data-mono="true">{snapshot.thread.cwd}</span> },
49
+ {
50
+ label: 'Created',
51
+ value: (
52
+ <span suppressHydrationWarning>
53
+ {formatDateTime(snapshot.thread.created_at_ms ?? snapshot.thread.created_at * 1000)}
54
+ </span>
55
+ ),
56
+ },
57
+ {
58
+ label: 'Updated',
59
+ value: (
60
+ <span suppressHydrationWarning>
61
+ {formatDateTime(snapshot.thread.updated_at_ms ?? snapshot.thread.updated_at * 1000)}
62
+ </span>
63
+ ),
64
+ },
65
+ {
66
+ label: 'Session started',
67
+ value: (
68
+ <span suppressHydrationWarning>
69
+ {formatDateTime(snapshot.transcript?.sessionMeta.timestamp ?? null)}
70
+ </span>
71
+ ),
72
+ },
73
+ { label: 'Rollout size', value: formatBytes(snapshot.rollout.fileSizeBytes) },
74
+ { label: 'Archived', value: formatBooleanLabel(Boolean(snapshot.thread.archived)) },
75
+ {
76
+ label: 'Archived at',
77
+ value: (
78
+ <span suppressHydrationWarning>
79
+ {formatDateTime(snapshot.thread.archived_at ? snapshot.thread.archived_at * 1000 : null)}
80
+ </span>
81
+ ),
82
+ },
83
+ ];
84
+ };
85
+
86
+ const buildRuntimeItems = (snapshot: ThreadSnapshot) => {
87
+ return [
88
+ { label: 'Source', value: snapshot.thread.source },
89
+ { label: 'Originator', value: snapshot.transcript?.sessionMeta.originator ?? 'n/a' },
90
+ { label: 'Model provider', value: snapshot.thread.model_provider },
91
+ { label: 'Reasoning effort', value: snapshot.thread.reasoning_effort ?? 'n/a' },
92
+ { label: 'CLI version', value: snapshot.thread.cli_version },
93
+ { label: 'Approval mode', value: snapshot.thread.approval_mode },
94
+ { label: 'Memory mode', value: snapshot.thread.memory_mode },
95
+ { label: 'Has user event', value: formatBooleanLabel(Boolean(snapshot.thread.has_user_event)) },
96
+ ];
97
+ };
98
+
99
+ const buildGitItems = (snapshot: ThreadSnapshot) => {
100
+ return [
101
+ { label: 'Git branch', value: snapshot.thread.git_branch ?? 'n/a' },
102
+ { label: 'Git SHA', value: snapshot.thread.git_sha ?? 'n/a' },
103
+ { label: 'Git remote', value: snapshot.thread.git_origin_url ?? 'n/a' },
104
+ { label: 'Agent nickname', value: snapshot.thread.agent_nickname ?? 'n/a' },
105
+ { label: 'Agent role', value: snapshot.thread.agent_role ?? 'n/a' },
106
+ { label: 'Agent path', value: snapshot.thread.agent_path ?? 'n/a' },
107
+ ];
108
+ };
109
+
110
+ const buildRelationItems = (snapshot: ThreadSnapshot) => {
111
+ const parentThreadValue = snapshot.relations.parentThreadId ? (
112
+ <Link
113
+ className="text-[var(--accent)]"
114
+ params={{ threadId: snapshot.relations.parentThreadId }}
115
+ to="/threads/$threadId"
116
+ >
117
+ {snapshot.relations.parentThreadId}
118
+ </Link>
119
+ ) : (
120
+ 'n/a'
121
+ );
122
+
123
+ const childThreadValue =
124
+ snapshot.relations.childEdges.length > 0 ? (
125
+ <div className="flex flex-wrap gap-2">
126
+ {snapshot.relations.childEdges.map((edge) => (
127
+ <Link
128
+ key={edge.child_thread_id}
129
+ className="rounded-full border border-[var(--border)] px-3 py-1 text-[var(--accent)] text-xs"
130
+ params={{ threadId: edge.child_thread_id }}
131
+ to="/threads/$threadId"
132
+ >
133
+ {edge.child_thread_id}
134
+ </Link>
135
+ ))}
136
+ </div>
137
+ ) : (
138
+ 'n/a'
139
+ );
140
+
141
+ return [
142
+ { label: 'Parent thread', value: parentThreadValue },
143
+ { label: 'Child threads', value: childThreadValue },
144
+ { label: 'First user message', value: snapshot.thread.first_user_message || 'n/a' },
145
+ { label: 'Preview', value: snapshot.thread.preview || 'n/a' },
146
+ ];
147
+ };
148
+
149
+ const buildTranscriptStatsItems = (snapshot: ThreadSnapshot) => {
150
+ if (!snapshot.transcript) {
151
+ if (snapshot.transcriptState === 'missing') {
152
+ return [
153
+ { label: 'Transcript load', value: 'Transcript file missing from disk' },
154
+ { label: 'Rollout path', value: snapshot.thread.rollout_path },
155
+ {
156
+ label: 'Preview mode',
157
+ value: 'Export and transcript browsing are unavailable until the file exists again.',
158
+ },
159
+ ];
160
+ }
161
+
162
+ return [
163
+ { label: 'Transcript load', value: 'Deferred for oversized rollout' },
164
+ { label: 'Rollout size', value: formatBytes(snapshot.rollout.fileSizeBytes) },
165
+ { label: 'Preview mode', value: 'Load the transcript manually to inspect it.' },
166
+ ];
167
+ }
168
+
169
+ return [
170
+ {
171
+ label: 'Event kinds',
172
+ value: formatList([...new Set(snapshot.transcript.events.map((event) => event.kind))]),
173
+ },
174
+ { label: 'Stats scope', value: snapshot.transcript.statsArePartial ? 'Preview only' : 'Full transcript' },
175
+ { label: 'Tool calls', value: String(snapshot.transcript.stats.toolCallCount) },
176
+ { label: 'Exec calls', value: String(snapshot.transcript.stats.execCommandCount) },
177
+ { label: 'Web search events', value: String(snapshot.transcript.stats.webSearchEventCount) },
178
+ { label: 'Assistant messages', value: String(snapshot.transcript.stats.assistantMessageCount) },
179
+ { label: 'Commentary updates', value: String(snapshot.transcript.stats.commentaryCount) },
180
+ ];
181
+ };
182
+
183
+ function TranscriptControls({
184
+ rawJsonDisabled = false,
185
+ showCommentary,
186
+ showExtraEvents,
187
+ showRawJson,
188
+ showToolCalls,
189
+ showUserMessages,
190
+ onShowCommentaryChange,
191
+ onShowExtraEventsChange,
192
+ onShowRawJsonChange,
193
+ onShowToolCallsChange,
194
+ onShowUserMessagesChange,
195
+ }: TranscriptControlsProps) {
196
+ return (
197
+ <div className="flex flex-wrap gap-4 rounded-xl border border-[var(--border)] bg-[var(--panel)] px-4 py-3 shadow-[var(--panel-shadow)]">
198
+ <div className="flex items-center gap-2 text-sm">
199
+ <Checkbox
200
+ checked={showToolCalls}
201
+ id="transcript-show-tool-calls"
202
+ onCheckedChange={(checked) => onShowToolCallsChange(checked === true)}
203
+ />
204
+ <label htmlFor="transcript-show-tool-calls">Show tool calls</label>
205
+ </div>
206
+ <div className="flex items-center gap-2 text-sm">
207
+ <Checkbox
208
+ checked={showCommentary}
209
+ id="transcript-show-commentary"
210
+ onCheckedChange={(checked) => onShowCommentaryChange(checked === true)}
211
+ />
212
+ <label htmlFor="transcript-show-commentary">Show commentary</label>
213
+ </div>
214
+ <div className="flex items-center gap-2 text-sm">
215
+ <Checkbox
216
+ checked={showExtraEvents}
217
+ id="transcript-show-extra-events"
218
+ onCheckedChange={(checked) => onShowExtraEventsChange(checked === true)}
219
+ />
220
+ <label htmlFor="transcript-show-extra-events">Show extra events</label>
221
+ </div>
222
+ <div className="flex items-center gap-2 text-sm">
223
+ <Checkbox
224
+ checked={showRawJson}
225
+ disabled={rawJsonDisabled}
226
+ id="transcript-show-raw-json"
227
+ onCheckedChange={(checked) => onShowRawJsonChange(checked === true)}
228
+ />
229
+ <label htmlFor="transcript-show-raw-json">Raw JSON</label>
230
+ </div>
231
+ <div className="flex items-center gap-2 text-sm">
232
+ <Checkbox
233
+ checked={showUserMessages}
234
+ id="transcript-show-user-messages"
235
+ onCheckedChange={(checked) => onShowUserMessagesChange(checked === true)}
236
+ />
237
+ <label htmlFor="transcript-show-user-messages">User</label>
238
+ </div>
239
+ </div>
240
+ );
241
+ }
242
+
243
+ function ThreadMetadataPanels({ snapshot }: ThreadMetadataProps) {
244
+ return (
245
+ <div className="grid gap-4 xl:grid-cols-[1.1fr_0.9fr]">
246
+ <div className="space-y-4">
247
+ <MetadataSection items={buildThreadItems(snapshot)} title="Thread" />
248
+ <MetadataSection items={buildRuntimeItems(snapshot)} title="Runtime" />
249
+ <MetadataSection items={buildGitItems(snapshot)} title="Git and agent" />
250
+ </div>
251
+
252
+ <div className="space-y-4">
253
+ <MetadataSection items={buildRelationItems(snapshot)} title="Relations and summary" />
254
+
255
+ <section className="rounded-[1.6rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]">
256
+ <h3 className="font-semibold text-[var(--muted-foreground)] text-sm uppercase tracking-[0.18em]">
257
+ Available tools
258
+ </h3>
259
+ <div className="mt-4 space-y-3">
260
+ {snapshot.availableTools.map((tool) => (
261
+ <div
262
+ key={`${tool.name}-${tool.namespace ?? 'global'}`}
263
+ className="rounded-xl border border-[var(--border)] bg-[var(--panel-secondary)] p-3.5"
264
+ >
265
+ <div className="flex flex-wrap items-center gap-2">
266
+ <p className="font-medium font-mono text-sm">{tool.name}</p>
267
+ {tool.namespace ? <Badge variant="outline">{tool.namespace}</Badge> : null}
268
+ </div>
269
+ <p className="mt-1.5 text-[var(--muted-foreground)] text-sm">
270
+ {tool.description || 'No description.'}
271
+ </p>
272
+ </div>
273
+ ))}
274
+ </div>
275
+ </section>
276
+
277
+ <MetadataSection items={buildTranscriptStatsItems(snapshot)} title="Transcript stats" />
278
+ </div>
279
+ </div>
280
+ );
281
+ }
282
+
283
+ function ThreadRawPanels({ snapshot }: ThreadMetadataProps) {
284
+ if (!snapshot.transcript) {
285
+ return (
286
+ <div className="rounded-xl border border-[var(--border)] bg-[var(--panel)] px-5 py-4 text-sm">
287
+ {snapshot.transcriptState === 'missing'
288
+ ? 'The rollout JSONL file is missing from disk, so raw transcript payloads are unavailable.'
289
+ : 'Raw transcript payloads are deferred for oversized rollouts. Use Export if you only need the full thread contents, or load the transcript manually from the Transcript tab.'}
290
+ </div>
291
+ );
292
+ }
293
+
294
+ return (
295
+ <div className="space-y-4">
296
+ <JsonPanel title="Session meta" value={snapshot.transcript.sessionMeta} />
297
+ <JsonPanel title="Turn contexts" value={snapshot.transcript.turnContexts} />
298
+ <JsonPanel title="Sandbox policy" value={snapshot.thread.sandbox_policy} />
299
+ </div>
300
+ );
301
+ }
302
+
303
+ function DeferredTranscriptNotice({
304
+ fileSizeBytes,
305
+ missing,
306
+ pending,
307
+ onLoad,
308
+ }: {
309
+ fileSizeBytes: number | null;
310
+ missing?: boolean;
311
+ pending: boolean;
312
+ onLoad: () => void;
313
+ }) {
314
+ return (
315
+ <section className="rounded-[1.6rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]">
316
+ <h3 className="font-semibold text-base">
317
+ {missing ? 'Transcript file missing' : 'This is a very big thread'}
318
+ </h3>
319
+ <p className="mt-2 text-[var(--muted-foreground)] text-sm leading-6">
320
+ {missing
321
+ ? 'The rollout JSONL referenced by this thread is no longer present on disk. Export may still work if the file is restored, but transcript browsing is unavailable right now.'
322
+ : `Spiracha skipped loading the transcript automatically because the rollout file is ${formatBytes(fileSizeBytes)}. Export still works immediately. If you need to inspect the thread here, load a bounded preview manually.`}
323
+ </p>
324
+ {missing ? null : (
325
+ <div className="mt-4">
326
+ <Button disabled={pending} variant="outline" onClick={onLoad}>
327
+ {pending ? 'Loading preview...' : 'Load transcript preview'}
328
+ </Button>
329
+ </div>
330
+ )}
331
+ </section>
332
+ );
333
+ }
334
+
335
+ function ThreadErrorComponent({ error }: { error: Error }) {
336
+ const isSqlite = error.message.includes('unable to open database') || error.message.includes('database is locked');
337
+ return (
338
+ <div className="rounded-xl border border-[var(--border)] bg-[var(--panel)] px-6 py-10 text-center">
339
+ <p className="font-medium text-[var(--destructive)] text-sm">
340
+ {isSqlite ? 'Database unavailable' : 'Failed to load thread'}
341
+ </p>
342
+ <p className="mt-2 text-[var(--muted-foreground)] text-sm">
343
+ {isSqlite ? 'Codex may have an exclusive lock on the database. Reload to retry.' : error.message}
344
+ </p>
345
+ <button
346
+ className="mt-4 text-[var(--accent)] text-sm underline-offset-2 hover:underline"
347
+ type="button"
348
+ onClick={() => window.location.reload()}
349
+ >
350
+ Reload
351
+ </button>
352
+ </div>
353
+ );
354
+ }
355
+
356
+ const getThreadExportErrorMessage = (transcriptMissing: boolean, error: unknown): string | null => {
357
+ if (transcriptMissing) {
358
+ return 'The rollout JSONL file is missing from disk, so this thread cannot be exported right now.';
359
+ }
360
+
361
+ return error instanceof Error ? error.message : null;
362
+ };
363
+
364
+ export const Route = createFileRoute('/threads/$threadId')({
365
+ component: ThreadDetailPage,
366
+ errorComponent: ThreadErrorComponent,
367
+ loader: ({ context, params }) => context.queryClient.ensureQueryData(threadSnapshotQueryOptions(params.threadId)),
368
+ pendingComponent: () => (
369
+ <LoadingPanel
370
+ description="Loading the transcript, metadata, and parsed event stream for this thread."
371
+ title="Loading thread"
372
+ />
373
+ ),
374
+ });
375
+
376
+ function ThreadDetailPage() {
377
+ const navigate = useNavigate();
378
+ const queryClient = useQueryClient();
379
+ const params = Route.useParams();
380
+ const snapshot = useSuspenseQuery(threadSnapshotQueryOptions(params.threadId)).data;
381
+ const { settings } = useSettings();
382
+ const transcriptMissing = snapshot.transcriptState === 'missing';
383
+ const [shouldLoadTranscript, setShouldLoadTranscript] = useState(
384
+ !snapshot.rollout.shouldDeferTranscriptLoad && !transcriptMissing,
385
+ );
386
+ const [showToolCalls, setShowToolCalls] = useState(false);
387
+ const [showCommentary, setShowCommentary] = useState(false);
388
+ const [showExtraEvents, setShowExtraEvents] = useState(false);
389
+ const [showRawJson, setShowRawJson] = useState(false);
390
+ const [showUserMessages, setShowUserMessages] = useState(true);
391
+ const [exportOpen, setExportOpen] = useState(false);
392
+ const [deleteOpen, setDeleteOpen] = useState(false);
393
+ const transcriptQuery = useQuery({
394
+ ...threadTranscriptQueryOptions(params.threadId),
395
+ enabled: shouldLoadTranscript && snapshot.transcript === null && !transcriptMissing,
396
+ });
397
+ const transcript = snapshot.transcript ?? transcriptQuery.data ?? null;
398
+ const viewSnapshot = { ...snapshot, transcript };
399
+
400
+ const exportThreadMutation = useMutation({
401
+ mutationFn: async (options: {
402
+ includeCommentary: boolean;
403
+ includeTools: boolean;
404
+ includeMetadata: boolean;
405
+ outputFormat: 'md' | 'txt';
406
+ zipArchive: boolean;
407
+ }) => {
408
+ console.info('[spiracha:export-ui] request', {
409
+ outputFormat: options.outputFormat,
410
+ project: snapshot.project,
411
+ selectedThreadCount: 1,
412
+ selectedThreadIds: [snapshot.thread.id],
413
+ zipArchive: options.zipArchive,
414
+ });
415
+
416
+ const download = await exportThreadFn({
417
+ data: {
418
+ ...options,
419
+ ...settings,
420
+ threadId: snapshot.thread.id,
421
+ },
422
+ });
423
+
424
+ console.info('[spiracha:export-ui] response', {
425
+ downloadUrl: download.mode === 'download_url' ? download.downloadUrl : null,
426
+ fileName: download.fileName,
427
+ mode: download.mode,
428
+ project: snapshot.project,
429
+ selectedThreadCount: 1,
430
+ });
431
+
432
+ if (download.mode === 'download') {
433
+ downloadTextFile(download.fileName, download.content, download.mimeType);
434
+ return;
435
+ }
436
+
437
+ await downloadUrlFile(download.fileName, download.downloadUrl);
438
+ },
439
+ onError: (error) => {
440
+ console.error('[spiracha:export-ui] failed', {
441
+ error: error instanceof Error ? error.message : String(error),
442
+ project: snapshot.project,
443
+ selectedThreadCount: 1,
444
+ selectedThreadIds: [snapshot.thread.id],
445
+ });
446
+ },
447
+ onSuccess: async () => {
448
+ setExportOpen(false);
449
+ },
450
+ });
451
+
452
+ const deleteThreadMutation = useMutation({
453
+ mutationFn: (input: { deleteSessionFiles: boolean }) =>
454
+ deleteThreadFn({
455
+ data: {
456
+ deleteSessionFiles: input.deleteSessionFiles,
457
+ threadId: snapshot.thread.id,
458
+ },
459
+ }),
460
+ onSuccess: async () => {
461
+ await Promise.all([
462
+ queryClient.invalidateQueries({ queryKey: ['analytics'] }),
463
+ queryClient.invalidateQueries({ queryKey: ['dashboard'] }),
464
+ queryClient.invalidateQueries({ queryKey: ['project-threads', snapshot.project] }),
465
+ queryClient.invalidateQueries({ queryKey: ['projects'] }),
466
+ ]);
467
+
468
+ navigate({
469
+ params: {
470
+ project: snapshot.project,
471
+ },
472
+ to: '/codex/$project',
473
+ });
474
+ },
475
+ });
476
+
477
+ return (
478
+ <div className="space-y-5">
479
+ <PageHeader
480
+ actions={
481
+ <>
482
+ <Button className="rounded-full" variant="outline" onClick={() => setExportOpen(true)}>
483
+ <Download className="mr-2 size-4" />
484
+ Export
485
+ </Button>
486
+ <Button
487
+ className="rounded-full border-[var(--destructive)]/20 text-[var(--destructive)]"
488
+ variant="outline"
489
+ onClick={() => setDeleteOpen(true)}
490
+ >
491
+ <Trash2 className="mr-2 size-4" />
492
+ Delete
493
+ </Button>
494
+ </>
495
+ }
496
+ breadcrumb={
497
+ <Breadcrumbs
498
+ items={[
499
+ { label: 'Codex', to: '/codex' },
500
+ {
501
+ label: snapshot.project,
502
+ params: { project: snapshot.project },
503
+ to: '/codex/$project',
504
+ },
505
+ { label: snapshot.thread.title },
506
+ ]}
507
+ />
508
+ }
509
+ eyebrow={snapshot.project}
510
+ subtitle={snapshot.thread.preview}
511
+ title={snapshot.thread.title}
512
+ />
513
+
514
+ <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
515
+ <MetricCard label="Model" value={snapshot.thread.model ?? 'unknown'} />
516
+ <MetricCard label="Tokens" value={formatTokens(snapshot.thread.tokens_used)} />
517
+ <MetricCard
518
+ label="Updated"
519
+ value={
520
+ <span suppressHydrationWarning>
521
+ {formatDateTime(snapshot.thread.updated_at_ms ?? snapshot.thread.updated_at * 1000)}
522
+ </span>
523
+ }
524
+ />
525
+ <MetricCard
526
+ label="Thread source"
527
+ value={snapshot.thread.thread_source ?? transcript?.sessionMeta.threadSource ?? 'n/a'}
528
+ />
529
+ </div>
530
+
531
+ <Tabs className="space-y-4" defaultValue="transcript">
532
+ <TabsList className="grid w-fit min-w-[24rem] grid-cols-3 rounded-full border border-[var(--border)] bg-[var(--panel)] p-1">
533
+ <TabsTrigger className="rounded-full px-5 text-sm" value="transcript">
534
+ Transcript
535
+ </TabsTrigger>
536
+ <TabsTrigger className="rounded-full px-5 text-sm" value="metadata">
537
+ Metadata
538
+ </TabsTrigger>
539
+ <TabsTrigger className="rounded-full px-5 text-sm" value="raw">
540
+ Raw
541
+ </TabsTrigger>
542
+ </TabsList>
543
+
544
+ <TabsContent className="space-y-3" value="transcript">
545
+ <TranscriptControls
546
+ rawJsonDisabled={!transcript?.rawIncluded}
547
+ showCommentary={showCommentary}
548
+ showExtraEvents={showExtraEvents}
549
+ showRawJson={showRawJson}
550
+ showToolCalls={showToolCalls}
551
+ showUserMessages={showUserMessages}
552
+ onShowCommentaryChange={setShowCommentary}
553
+ onShowExtraEventsChange={setShowExtraEvents}
554
+ onShowRawJsonChange={setShowRawJson}
555
+ onShowToolCallsChange={setShowToolCalls}
556
+ onShowUserMessagesChange={setShowUserMessages}
557
+ />
558
+ {transcript ? (
559
+ <TranscriptView
560
+ assistantModel={snapshot.thread.model}
561
+ events={transcript.events}
562
+ projectPath={snapshot.thread.cwd}
563
+ showCommentary={showCommentary}
564
+ showExtraEvents={showExtraEvents}
565
+ showRawJson={showRawJson && transcript.rawIncluded}
566
+ showToolCalls={showToolCalls}
567
+ showUserMessages={showUserMessages}
568
+ />
569
+ ) : (
570
+ <DeferredTranscriptNotice
571
+ fileSizeBytes={snapshot.rollout.fileSizeBytes}
572
+ missing={snapshot.transcriptState === 'missing'}
573
+ pending={transcriptQuery.isFetching}
574
+ onLoad={() => setShouldLoadTranscript(true)}
575
+ />
576
+ )}
577
+ {transcriptQuery.isError ? (
578
+ <p className="text-[var(--destructive)] text-sm">
579
+ Failed to load transcript preview:{' '}
580
+ {transcriptQuery.error instanceof Error ? transcriptQuery.error.message : 'Unknown error'}
581
+ </p>
582
+ ) : null}
583
+ </TabsContent>
584
+
585
+ <TabsContent value="metadata">
586
+ <ThreadMetadataPanels snapshot={viewSnapshot} />
587
+ </TabsContent>
588
+
589
+ <TabsContent value="raw">
590
+ <ThreadRawPanels snapshot={viewSnapshot} />
591
+ </TabsContent>
592
+ </Tabs>
593
+
594
+ <DeleteConfirmDialog
595
+ confirmLabel={deleteThreadMutation.isPending ? 'Deleting...' : 'Delete thread'}
596
+ defaultDeleteSessionFiles
597
+ description="Delete this thread from the Codex database. Enable Delete Session files if you also want to remove the rollout JSONL from disk."
598
+ open={deleteOpen}
599
+ showDeleteSessionFilesOption
600
+ title="Delete this thread from Codex DB?"
601
+ onConfirm={({ deleteSessionFiles }) => deleteThreadMutation.mutate({ deleteSessionFiles })}
602
+ onOpenChange={setDeleteOpen}
603
+ />
604
+
605
+ <ExportDialog
606
+ disabled={transcriptMissing}
607
+ errorMessage={getThreadExportErrorMessage(transcriptMissing, exportThreadMutation.error)}
608
+ open={exportOpen}
609
+ pending={exportThreadMutation.isPending}
610
+ onExport={(options) => {
611
+ if (!transcriptMissing) {
612
+ exportThreadMutation.mutate(options);
613
+ }
614
+ }}
615
+ onOpenChange={setExportOpen}
616
+ />
617
+ </div>
618
+ );
619
+ }