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,341 @@
1
+ import type { ThreadEvent, ThreadTranscriptStats } from '@spiracha/lib/codex-browser-types';
2
+ import type { QoderSessionTranscript } from '@spiracha/lib/qoder-exporter-types';
3
+ import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
4
+ import { createFileRoute, Link } from '@tanstack/react-router';
5
+ import { Download } from 'lucide-react';
6
+ import { useMemo, useState } from 'react';
7
+ import { Breadcrumbs } from '#/components/breadcrumbs';
8
+ import { ExportDialog } from '#/components/export-dialog';
9
+ import { JsonPanel } from '#/components/json-panel';
10
+ import { LoadingPanel } from '#/components/loading-panel';
11
+ import { MetadataSection } from '#/components/metadata-section';
12
+ import { MetricCard } from '#/components/metric-card';
13
+ import { PageHeader } from '#/components/page-header';
14
+ import { ReloadErrorPanel } from '#/components/reload-error-panel';
15
+ import { TranscriptView } from '#/components/transcript-view';
16
+ import { Button } from '#/components/ui/button';
17
+ import { Checkbox } from '#/components/ui/checkbox';
18
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '#/components/ui/tabs';
19
+ import { downloadTextFile, downloadUrlFile } from '#/lib/download';
20
+ import { formatDateTime, formatList, formatNumber } from '#/lib/formatters';
21
+ import { qoderSessionDetailQueryOptions } from '#/lib/qoder-queries';
22
+ import { exportQoderSessionFn } from '#/lib/qoder-server';
23
+ import { getQoderThreadTranscriptStats, qoderTranscriptToThreadEvents } from '#/lib/qoder-transcript-events';
24
+
25
+ type ExportDialogOptions = {
26
+ includeCommentary: boolean;
27
+ includeMetadata: boolean;
28
+ includeTools: boolean;
29
+ outputFormat: 'md' | 'txt';
30
+ zipArchive: boolean;
31
+ };
32
+
33
+ type TranscriptControlsProps = {
34
+ rawJsonDisabled?: boolean;
35
+ showCommentary: boolean;
36
+ showExtraEvents: boolean;
37
+ showRawJson: boolean;
38
+ showToolCalls: boolean;
39
+ showUserMessages: boolean;
40
+ onShowCommentaryChange: (checked: boolean) => void;
41
+ onShowExtraEventsChange: (checked: boolean) => void;
42
+ onShowRawJsonChange: (checked: boolean) => void;
43
+ onShowToolCallsChange: (checked: boolean) => void;
44
+ onShowUserMessagesChange: (checked: boolean) => void;
45
+ };
46
+
47
+ const QoderSessionDetailErrorComponent = ({ error }: { error: Error }) => {
48
+ return <ReloadErrorPanel description={error.message} title="Failed to load Qoder session" />;
49
+ };
50
+
51
+ const buildSessionMetadata = (detail: QoderSessionTranscript) => [
52
+ { label: 'Session ID', value: <span data-mono="true">{detail.session.sessionId}</span> },
53
+ { label: 'Task ID', value: detail.session.taskId ?? 'unknown' },
54
+ { label: 'Request ID', value: detail.session.requestId ?? 'unknown' },
55
+ {
56
+ label: 'Workspace',
57
+ value: (
58
+ <Link
59
+ className="text-[var(--accent)]"
60
+ params={{ workspaceKey: detail.session.workspaceKey }}
61
+ to="/qoder/$workspaceKey"
62
+ >
63
+ {detail.session.workspaceLabel}
64
+ </Link>
65
+ ),
66
+ },
67
+ { label: 'Worktree', value: detail.session.worktree },
68
+ { label: 'Workspace data ID', value: detail.session.workspaceStorageId ?? 'unknown' },
69
+ { label: 'State file', value: detail.session.sourceStatePath ?? 'unknown' },
70
+ { label: 'Status', value: detail.session.status ?? 'unknown' },
71
+ { label: 'Model', value: detail.session.model ?? 'unknown' },
72
+ { label: 'Execution mode', value: detail.session.executionMode ?? 'unknown' },
73
+ { label: 'Agent class', value: detail.session.agentClass ?? 'unknown' },
74
+ { label: 'Created', value: formatDateTime(detail.session.createdAtMs) },
75
+ {
76
+ label: 'Last active',
77
+ value: formatDateTime(detail.session.lastActiveAtMs),
78
+ },
79
+ ];
80
+
81
+ const buildTranscriptStatsItems = (
82
+ detail: QoderSessionTranscript,
83
+ events: ThreadEvent[],
84
+ stats: ThreadTranscriptStats,
85
+ ) => [
86
+ { label: 'Event kinds', value: formatList([...new Set(events.map((event) => event.kind))]) },
87
+ { label: 'Messages', value: formatNumber(stats.messageCount) },
88
+ { label: 'User messages', value: formatNumber(stats.userMessageCount) },
89
+ { label: 'Assistant messages', value: formatNumber(stats.assistantMessageCount) },
90
+ { label: 'Final answers', value: formatNumber(stats.finalAnswerCount) },
91
+ { label: 'File operations', value: formatNumber(detail.session.fileOperationCount) },
92
+ { label: 'Snapshots', value: formatNumber(detail.session.snapshotFileCount) },
93
+ { label: 'Renderable parts', value: formatNumber(detail.renderablePartCount) },
94
+ ];
95
+
96
+ const QoderTranscriptControls = ({
97
+ rawJsonDisabled = false,
98
+ showCommentary,
99
+ showExtraEvents,
100
+ showRawJson,
101
+ showToolCalls,
102
+ showUserMessages,
103
+ onShowCommentaryChange,
104
+ onShowExtraEventsChange,
105
+ onShowRawJsonChange,
106
+ onShowToolCallsChange,
107
+ onShowUserMessagesChange,
108
+ }: TranscriptControlsProps) => {
109
+ return (
110
+ <div className="flex flex-wrap gap-4 rounded-xl border border-[var(--border)] bg-[var(--panel)] px-4 py-3 shadow-[var(--panel-shadow)]">
111
+ <div className="flex items-center gap-2 text-sm">
112
+ <Checkbox
113
+ checked={showToolCalls}
114
+ id="qoder-transcript-show-tool-calls"
115
+ onCheckedChange={(checked) => onShowToolCallsChange(checked === true)}
116
+ />
117
+ <label htmlFor="qoder-transcript-show-tool-calls">Show tool calls</label>
118
+ </div>
119
+ <div className="flex items-center gap-2 text-sm">
120
+ <Checkbox
121
+ checked={showCommentary}
122
+ id="qoder-transcript-show-commentary"
123
+ onCheckedChange={(checked) => onShowCommentaryChange(checked === true)}
124
+ />
125
+ <label htmlFor="qoder-transcript-show-commentary">Show commentary</label>
126
+ </div>
127
+ <div className="flex items-center gap-2 text-sm">
128
+ <Checkbox
129
+ checked={showExtraEvents}
130
+ id="qoder-transcript-show-extra-events"
131
+ onCheckedChange={(checked) => onShowExtraEventsChange(checked === true)}
132
+ />
133
+ <label htmlFor="qoder-transcript-show-extra-events">Show extra events</label>
134
+ </div>
135
+ <div className="flex items-center gap-2 text-sm">
136
+ <Checkbox
137
+ checked={showRawJson}
138
+ disabled={rawJsonDisabled}
139
+ id="qoder-transcript-show-raw-json"
140
+ onCheckedChange={(checked) => onShowRawJsonChange(checked === true)}
141
+ />
142
+ <label htmlFor="qoder-transcript-show-raw-json">Raw JSON</label>
143
+ </div>
144
+ <div className="flex items-center gap-2 text-sm">
145
+ <Checkbox
146
+ checked={showUserMessages}
147
+ id="qoder-transcript-show-user-messages"
148
+ onCheckedChange={(checked) => onShowUserMessagesChange(checked === true)}
149
+ />
150
+ <label htmlFor="qoder-transcript-show-user-messages">User</label>
151
+ </div>
152
+ </div>
153
+ );
154
+ };
155
+
156
+ const QoderRawPanels = ({ detail, events }: { detail: QoderSessionTranscript; events: ThreadEvent[] }) => {
157
+ return (
158
+ <div className="space-y-4">
159
+ <JsonPanel title="Session summary" value={detail.session} />
160
+ <JsonPanel title="Qoder entries" value={detail.entries} />
161
+ <JsonPanel title="Raw Qoder session" value={detail.rawSession} />
162
+ <JsonPanel title="Transcript events" value={events} />
163
+ </div>
164
+ );
165
+ };
166
+
167
+ const QoderSessionDetailPage = () => {
168
+ const detail = useSuspenseQuery(qoderSessionDetailQueryOptions(Route.useParams().sessionId)).data;
169
+ const [pendingExport, setPendingExport] = useState(false);
170
+ const [showToolCalls, setShowToolCalls] = useState(false);
171
+ const [showCommentary, setShowCommentary] = useState(false);
172
+ const [showExtraEvents, setShowExtraEvents] = useState(false);
173
+ const [showRawJson, setShowRawJson] = useState(false);
174
+ const [showUserMessages, setShowUserMessages] = useState(true);
175
+ const transcriptEvents = useMemo(() => qoderTranscriptToThreadEvents(detail), [detail]);
176
+ const transcriptStats = useMemo(() => getQoderThreadTranscriptStats(transcriptEvents), [transcriptEvents]);
177
+ const modelLabel = detail.session.model ?? 'Qoder';
178
+
179
+ const exportSessionMutation = useMutation({
180
+ mutationFn: async (options: ExportDialogOptions) => {
181
+ const download = await exportQoderSessionFn({
182
+ data: {
183
+ includeCommentary: options.includeCommentary,
184
+ includeMetadata: options.includeMetadata,
185
+ includeTools: options.includeTools,
186
+ outputFormat: options.outputFormat,
187
+ sessionId: detail.session.sessionId,
188
+ zipArchive: options.zipArchive,
189
+ },
190
+ });
191
+ if (download.mode === 'download') {
192
+ downloadTextFile(download.fileName, download.content, download.mimeType);
193
+ return;
194
+ }
195
+
196
+ await downloadUrlFile(download.fileName, download.downloadUrl);
197
+ },
198
+ onSuccess: () => {
199
+ setPendingExport(false);
200
+ },
201
+ });
202
+
203
+ return (
204
+ <div className="space-y-6">
205
+ <PageHeader
206
+ actions={
207
+ <Button
208
+ className="rounded-full"
209
+ type="button"
210
+ variant="outline"
211
+ onClick={() => setPendingExport(true)}
212
+ >
213
+ <Download className="mr-2 size-4" />
214
+ Export
215
+ </Button>
216
+ }
217
+ breadcrumb={
218
+ <Breadcrumbs
219
+ items={[
220
+ { label: 'Qoder', to: '/qoder' },
221
+ {
222
+ label: detail.session.workspaceLabel,
223
+ params: { workspaceKey: detail.session.workspaceKey },
224
+ to: '/qoder/$workspaceKey',
225
+ },
226
+ { label: detail.session.title },
227
+ ]}
228
+ />
229
+ }
230
+ eyebrow="Qoder session"
231
+ subtitle="Session detail for the selected Qoder local history entry and local state."
232
+ title={detail.session.title}
233
+ />
234
+
235
+ <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
236
+ <MetricCard label="Prompts" value={formatNumber(detail.session.messageCount)} />
237
+ <MetricCard label="File ops" value={formatNumber(detail.session.fileOperationCount)} />
238
+ <MetricCard label="Snapshots" value={formatNumber(detail.session.snapshotFileCount)} />
239
+ <MetricCard label="Model" value={modelLabel} />
240
+ </div>
241
+
242
+ <Tabs className="space-y-4" defaultValue="transcript">
243
+ <TabsList className="grid w-fit min-w-[24rem] grid-cols-3 rounded-full border border-[var(--border)] bg-[var(--panel)] p-1">
244
+ <TabsTrigger className="rounded-full px-5 text-sm" value="transcript">
245
+ Transcript
246
+ </TabsTrigger>
247
+ <TabsTrigger className="rounded-full px-5 text-sm" value="metadata">
248
+ Metadata
249
+ </TabsTrigger>
250
+ <TabsTrigger className="rounded-full px-5 text-sm" value="raw">
251
+ Raw
252
+ </TabsTrigger>
253
+ </TabsList>
254
+
255
+ <TabsContent className="space-y-3" value="transcript">
256
+ <QoderTranscriptControls
257
+ rawJsonDisabled={transcriptEvents.length === 0}
258
+ showCommentary={showCommentary}
259
+ showExtraEvents={showExtraEvents}
260
+ showRawJson={showRawJson}
261
+ showToolCalls={showToolCalls}
262
+ showUserMessages={showUserMessages}
263
+ onShowCommentaryChange={setShowCommentary}
264
+ onShowExtraEventsChange={setShowExtraEvents}
265
+ onShowRawJsonChange={setShowRawJson}
266
+ onShowToolCallsChange={setShowToolCalls}
267
+ onShowUserMessagesChange={setShowUserMessages}
268
+ />
269
+ {transcriptEvents.length > 0 ? (
270
+ <TranscriptView
271
+ assistantModel={modelLabel}
272
+ events={transcriptEvents}
273
+ projectPath={detail.session.worktree}
274
+ showCommentary={showCommentary}
275
+ showExtraEvents={showExtraEvents}
276
+ showRawJson={showRawJson}
277
+ showToolCalls={showToolCalls}
278
+ showUserMessages={showUserMessages}
279
+ />
280
+ ) : (
281
+ <section className="rounded-[1.6rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]">
282
+ <h3 className="font-semibold text-[var(--muted-foreground)] text-sm uppercase tracking-[0.18em]">
283
+ Transcript
284
+ </h3>
285
+ <p className="mt-4 text-[var(--muted-foreground)] text-sm">
286
+ No renderable Qoder transcript content was found for this session.
287
+ </p>
288
+ </section>
289
+ )}
290
+ </TabsContent>
291
+
292
+ <TabsContent value="metadata">
293
+ <div className="grid gap-4 xl:grid-cols-[0.95fr_1.05fr]">
294
+ <MetadataSection items={buildSessionMetadata(detail)} title="Session metadata" />
295
+ <MetadataSection
296
+ items={buildTranscriptStatsItems(detail, transcriptEvents, transcriptStats)}
297
+ title="Transcript stats"
298
+ />
299
+ </div>
300
+ </TabsContent>
301
+
302
+ <TabsContent value="raw">
303
+ <QoderRawPanels detail={detail} events={transcriptEvents} />
304
+ </TabsContent>
305
+ </Tabs>
306
+
307
+ <ExportDialog
308
+ errorMessage={
309
+ exportSessionMutation.isError
310
+ ? exportSessionMutation.error instanceof Error
311
+ ? exportSessionMutation.error.message
312
+ : 'Export failed'
313
+ : null
314
+ }
315
+ open={pendingExport}
316
+ pending={exportSessionMutation.isPending}
317
+ title={`Export ${detail.session.title}`}
318
+ onExport={(options) => exportSessionMutation.mutate(options)}
319
+ onOpenChange={(open) => {
320
+ setPendingExport(open);
321
+ if (!open) {
322
+ exportSessionMutation.reset();
323
+ }
324
+ }}
325
+ />
326
+ </div>
327
+ );
328
+ };
329
+
330
+ export const Route = createFileRoute('/qoder-sessions/$sessionId')({
331
+ component: QoderSessionDetailPage,
332
+ errorComponent: QoderSessionDetailErrorComponent,
333
+ loader: ({ context, params }) =>
334
+ context.queryClient.ensureQueryData(qoderSessionDetailQueryOptions(params.sessionId)),
335
+ pendingComponent: () => (
336
+ <LoadingPanel
337
+ description="Loading the Qoder transcript, file operations, and session metadata."
338
+ title="Loading session"
339
+ />
340
+ ),
341
+ });
@@ -0,0 +1,139 @@
1
+ import type { QoderSessionSummary, QoderWorkspaceGroup } from '@spiracha/lib/qoder-exporter-types';
2
+ import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
3
+ import { createFileRoute } from '@tanstack/react-router';
4
+ import { useDeferredValue, useState } from 'react';
5
+ import { ExportDialog } from '#/components/export-dialog';
6
+ import { ListSearchInput } from '#/components/list-search-input';
7
+ import { LoadingPanel } from '#/components/loading-panel';
8
+ import { PageHeader } from '#/components/page-header';
9
+ import { QoderSessionsTable } from '#/components/qoder-sessions-table';
10
+ import { ReloadErrorPanel } from '#/components/reload-error-panel';
11
+ import { downloadTextFile, downloadUrlFile } from '#/lib/download';
12
+ import { qoderSessionsQueryOptions, qoderWorkspacesQueryOptions } from '#/lib/qoder-queries';
13
+ import { exportQoderSessionFn } from '#/lib/qoder-server';
14
+ import { matchesTextQuery } from '#/lib/text-filter';
15
+
16
+ type ExportDialogOptions = {
17
+ includeCommentary: boolean;
18
+ includeMetadata: boolean;
19
+ includeTools: boolean;
20
+ outputFormat: 'md' | 'txt';
21
+ zipArchive: boolean;
22
+ };
23
+
24
+ const findWorkspaceOrThrow = (workspaces: QoderWorkspaceGroup[], workspaceKey: string) => {
25
+ const workspace = workspaces.find((candidate) => candidate.key === workspaceKey);
26
+ if (!workspace) {
27
+ throw new Error(`Qoder workspace not found: ${workspaceKey}`);
28
+ }
29
+
30
+ return workspace;
31
+ };
32
+
33
+ const QoderWorkspaceErrorComponent = ({ error }: { error: Error }) => {
34
+ return <ReloadErrorPanel description={error.message} title="Failed to load Qoder workspace" />;
35
+ };
36
+
37
+ const QoderWorkspacePage = () => {
38
+ const params = Route.useParams();
39
+ const workspaces = useSuspenseQuery(qoderWorkspacesQueryOptions()).data;
40
+ const workspace = findWorkspaceOrThrow(workspaces, params.workspaceKey);
41
+ const sessions = useSuspenseQuery(qoderSessionsQueryOptions(workspace.key)).data;
42
+ const [searchInput, setSearchInput] = useState('');
43
+ const [pendingExport, setPendingExport] = useState<QoderSessionSummary | null>(null);
44
+ const deferredSearch = useDeferredValue(searchInput);
45
+
46
+ const exportMutation = useMutation({
47
+ mutationFn: async (options: ExportDialogOptions) => {
48
+ if (!pendingExport) {
49
+ throw new Error('No Qoder session selected for export');
50
+ }
51
+
52
+ const download = await exportQoderSessionFn({
53
+ data: {
54
+ includeCommentary: options.includeCommentary,
55
+ includeMetadata: options.includeMetadata,
56
+ includeTools: options.includeTools,
57
+ outputFormat: options.outputFormat,
58
+ sessionId: pendingExport.sessionId,
59
+ zipArchive: options.zipArchive,
60
+ },
61
+ });
62
+ if (download.mode === 'download') {
63
+ downloadTextFile(download.fileName, download.content, download.mimeType);
64
+ return;
65
+ }
66
+
67
+ await downloadUrlFile(download.fileName, download.downloadUrl);
68
+ },
69
+ onSuccess: () => {
70
+ setPendingExport(null);
71
+ },
72
+ });
73
+
74
+ const visibleSessions = sessions.filter((session) =>
75
+ matchesTextQuery(deferredSearch, [
76
+ session.title,
77
+ session.sessionId,
78
+ session.taskId,
79
+ session.requestId,
80
+ session.model,
81
+ session.status,
82
+ session.executionMode,
83
+ session.agentClass,
84
+ session.query,
85
+ session.sourceStatePath,
86
+ ]),
87
+ );
88
+
89
+ return (
90
+ <div className="space-y-6">
91
+ <PageHeader
92
+ actions={
93
+ <ListSearchInput
94
+ placeholder="Search session title, id, status, or request"
95
+ value={searchInput}
96
+ onValueChange={setSearchInput}
97
+ />
98
+ }
99
+ eyebrow="Qoder workspace"
100
+ subtitle="Inspect local Qoder prompts, session metadata, and file-operation history."
101
+ title={workspace.label}
102
+ />
103
+
104
+ <QoderSessionsTable sessions={visibleSessions} onExportSession={setPendingExport} />
105
+
106
+ <ExportDialog
107
+ open={pendingExport !== null}
108
+ pending={exportMutation.isPending}
109
+ title={pendingExport ? `Export ${pendingExport.title}` : 'Export session'}
110
+ onExport={(options) => exportMutation.mutate(options)}
111
+ onOpenChange={(open) => {
112
+ if (!open) {
113
+ setPendingExport(null);
114
+ exportMutation.reset();
115
+ }
116
+ }}
117
+ />
118
+
119
+ {exportMutation.isError ? (
120
+ <p className="text-[var(--destructive)] text-sm">
121
+ {exportMutation.error instanceof Error ? exportMutation.error.message : 'Session export failed'}
122
+ </p>
123
+ ) : null}
124
+ </div>
125
+ );
126
+ };
127
+
128
+ export const Route = createFileRoute('/qoder/$workspaceKey')({
129
+ component: QoderWorkspacePage,
130
+ errorComponent: QoderWorkspaceErrorComponent,
131
+ loader: async ({ context, params }) => {
132
+ const workspaces = await context.queryClient.ensureQueryData(qoderWorkspacesQueryOptions());
133
+ findWorkspaceOrThrow(workspaces, params.workspaceKey);
134
+ await context.queryClient.ensureQueryData(qoderSessionsQueryOptions(params.workspaceKey));
135
+ },
136
+ pendingComponent: () => (
137
+ <LoadingPanel description="Loading Qoder sessions and local metadata." title="Loading workspace" />
138
+ ),
139
+ });
@@ -0,0 +1,48 @@
1
+ import { useSuspenseQuery } from '@tanstack/react-query';
2
+ import { createFileRoute } from '@tanstack/react-router';
3
+ import { useDeferredValue, useState } from 'react';
4
+ import { ListSearchInput } from '#/components/list-search-input';
5
+ import { PageHeader } from '#/components/page-header';
6
+ import { QoderWorkspacesTable } from '#/components/qoder-workspaces-table';
7
+ import { ReloadErrorPanel } from '#/components/reload-error-panel';
8
+ import { qoderWorkspacesQueryOptions } from '#/lib/qoder-queries';
9
+ import { matchesTextQuery } from '#/lib/text-filter';
10
+
11
+ const QoderErrorComponent = ({ error }: { error: Error }) => {
12
+ return <ReloadErrorPanel description={error.message} title="Failed to load Qoder workspaces" />;
13
+ };
14
+
15
+ const QoderPage = () => {
16
+ const workspaces = useSuspenseQuery(qoderWorkspacesQueryOptions()).data;
17
+ const [searchInput, setSearchInput] = useState('');
18
+ const deferredSearch = useDeferredValue(searchInput);
19
+
20
+ const visibleWorkspaces = workspaces.filter((workspace) =>
21
+ matchesTextQuery(deferredSearch, [workspace.label, workspace.worktree, workspace.key]),
22
+ );
23
+
24
+ return (
25
+ <div className="space-y-6">
26
+ <PageHeader
27
+ actions={
28
+ <ListSearchInput
29
+ placeholder="Search workspace name or path"
30
+ value={searchInput}
31
+ onValueChange={setSearchInput}
32
+ />
33
+ }
34
+ eyebrow="Inventory"
35
+ subtitle="Workspace groups are derived from local Qoder history and workspace data."
36
+ title="Qoder"
37
+ />
38
+
39
+ <QoderWorkspacesTable workspaces={visibleWorkspaces} />
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export const Route = createFileRoute('/qoder/')({
45
+ component: QoderPage,
46
+ errorComponent: QoderErrorComponent,
47
+ loader: ({ context }) => context.queryClient.ensureQueryData(qoderWorkspacesQueryOptions()),
48
+ });
@@ -0,0 +1,91 @@
1
+ import { createFileRoute } from '@tanstack/react-router';
2
+ import { Settings2 } from 'lucide-react';
3
+ import { PageHeader } from '#/components/page-header';
4
+ import { Checkbox } from '#/components/ui/checkbox';
5
+ import { useSettings } from '#/lib/settings-store';
6
+
7
+ export const Route = createFileRoute('/settings')({
8
+ component: SettingsPage,
9
+ });
10
+
11
+ function SettingsPage() {
12
+ const { settings, updateSetting } = useSettings();
13
+
14
+ return (
15
+ <div className="space-y-6">
16
+ <PageHeader
17
+ eyebrow="Configuration"
18
+ subtitle="Control how paths and usernames appear in transcript messages."
19
+ title="Settings"
20
+ />
21
+
22
+ <section className="rounded-[1.6rem] border border-[var(--border)] bg-[var(--panel)] p-5 shadow-[var(--panel-shadow)]">
23
+ <h3 className="flex items-center gap-2 font-semibold text-[var(--muted-foreground)] text-xs uppercase tracking-[0.18em]">
24
+ <Settings2 className="size-3.5" />
25
+ Privacy
26
+ </h3>
27
+
28
+ <div className="mt-5 space-y-5">
29
+ <div className="flex items-center gap-3">
30
+ <Checkbox
31
+ checked={settings.redactUsername}
32
+ id="redact-username"
33
+ onCheckedChange={(checked) => updateSetting('redactUsername', checked === true)}
34
+ />
35
+ <div className="min-w-0">
36
+ <label
37
+ className="cursor-pointer font-medium text-sm leading-none"
38
+ htmlFor="redact-username"
39
+ >
40
+ Redact Username
41
+ </label>
42
+ <p className="mt-1.5 text-[var(--muted-foreground)] text-xs leading-5">
43
+ Replaces{' '}
44
+ <code className="rounded bg-[var(--code-background)] px-1 py-0.5 font-mono text-[var(--code-foreground)]">
45
+ /Users/[username]/
46
+ </code>{' '}
47
+ with{' '}
48
+ <code className="rounded bg-[var(--code-background)] px-1 py-0.5 font-mono text-[var(--code-foreground)]">
49
+ ~/
50
+ </code>{' '}
51
+ in all transcript messages.
52
+ </p>
53
+ </div>
54
+ </div>
55
+
56
+ <div className="flex items-center gap-3">
57
+ <Checkbox
58
+ checked={settings.convertToProjectRoot}
59
+ id="convert-project-root"
60
+ onCheckedChange={(checked) => updateSetting('convertToProjectRoot', checked === true)}
61
+ />
62
+ <div className="min-w-0">
63
+ <label
64
+ className="cursor-pointer font-medium text-sm leading-none"
65
+ htmlFor="convert-project-root"
66
+ >
67
+ Convert Absolute Paths to Project Root
68
+ </label>
69
+ <p className="mt-1.5 text-[var(--muted-foreground)] text-xs leading-5">
70
+ Converts the current thread or project cwd from{' '}
71
+ <code className="rounded bg-[var(--code-background)] px-1 py-0.5 font-mono text-[var(--code-foreground)]">
72
+ /Users/[username]/workspace/[projectname]/
73
+ </code>{' '}
74
+ to project-root-relative paths, so{' '}
75
+ <code className="rounded bg-[var(--code-background)] px-1 py-0.5 font-mono text-[var(--code-foreground)]">
76
+ /Users/jane/workspace/myapp/src/index.ts
77
+ </code>{' '}
78
+ appear as{' '}
79
+ <code className="rounded bg-[var(--code-background)] px-1 py-0.5 font-mono text-[var(--code-foreground)]">
80
+ src/index.ts
81
+ </code>
82
+ . If Redact Username is also enabled, remaining absolute paths outside the project root
83
+ are redacted afterward.
84
+ </p>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </section>
89
+ </div>
90
+ );
91
+ }