spiracha 2.1.0 → 2.2.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.
- package/AGENTS.md +27 -12
- package/README.md +37 -19
- package/apps/ui/AGENTS.md +18 -10
- package/apps/ui/README.md +17 -10
- package/apps/ui/src/components/analytics-breakdowns.tsx +106 -0
- package/apps/ui/src/components/antigravity-conversations-table.tsx +1 -0
- package/apps/ui/src/components/app-shell.tsx +45 -3
- package/apps/ui/src/components/data-table.tsx +17 -1
- package/apps/ui/src/components/delete-confirm-dialog.tsx +4 -3
- package/apps/ui/src/components/export-dialog.tsx +77 -62
- package/apps/ui/src/components/projects-table.tsx +4 -1
- package/apps/ui/src/components/route-error-panel.tsx +13 -0
- package/apps/ui/src/components/thread-goals-panel.tsx +50 -0
- package/apps/ui/src/components/thread-tools-panel.tsx +142 -0
- package/apps/ui/src/components/threads-table.tsx +4 -1
- package/apps/ui/src/lib/antigravity-server.ts +70 -17
- package/apps/ui/src/lib/antigravity-transcript-events.ts +46 -70
- package/apps/ui/src/lib/claude-code-server.ts +14 -4
- package/apps/ui/src/lib/codex-queries.ts +3 -3
- package/apps/ui/src/lib/codex-server.ts +3 -3
- package/apps/ui/src/lib/cursor-server.ts +41 -69
- package/apps/ui/src/lib/cursor-transcript-events.ts +11 -86
- package/apps/ui/src/lib/delete-batch.ts +52 -0
- package/apps/ui/src/lib/error-presentation.ts +43 -0
- package/apps/ui/src/lib/export-mutation.ts +20 -0
- package/apps/ui/src/lib/export-options.ts +15 -0
- package/apps/ui/src/lib/grok-server.ts +2 -3
- package/apps/ui/src/lib/kiro-server.ts +14 -2
- package/apps/ui/src/lib/mutation-error.ts +15 -0
- package/apps/ui/src/lib/opencode-server.ts +13 -2
- package/apps/ui/src/lib/path-utils.ts +1 -1
- package/apps/ui/src/lib/qoder-queries.ts +15 -0
- package/apps/ui/src/lib/qoder-transcript-events.ts +1 -1
- package/apps/ui/src/lib/route-state-reset.tsx +10 -0
- package/apps/ui/src/lib/settings-server.ts +33 -0
- package/apps/ui/src/lib/settings-store.tsx +82 -38
- package/apps/ui/src/lib/settings.ts +65 -0
- package/apps/ui/src/lib/source-session-export-server.ts +1 -1
- package/apps/ui/src/lib/thread-metadata.ts +22 -0
- package/apps/ui/src/lib/thread-transcript-stats.ts +10 -1
- package/apps/ui/src/lib/workspace-delete-navigation.ts +8 -0
- package/apps/ui/src/routeTree.gen.ts +282 -282
- package/apps/ui/src/routes/__root.tsx +12 -15
- package/apps/ui/src/routes/analytics.tsx +10 -61
- package/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx +73 -19
- package/apps/ui/src/routes/antigravity.$workspaceKey.tsx +42 -60
- package/apps/ui/src/routes/antigravity.index.tsx +2 -2
- package/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx +25 -11
- package/apps/ui/src/routes/claude-code.$workspaceKey.tsx +27 -32
- package/apps/ui/src/routes/claude-code.index.tsx +2 -2
- package/apps/ui/src/routes/codex.$project.tsx +33 -31
- package/apps/ui/src/routes/codex.index.tsx +5 -10
- package/apps/ui/src/routes/cursor-threads.$composerId.tsx +29 -16
- package/apps/ui/src/routes/cursor.$workspaceKey.tsx +33 -27
- package/apps/ui/src/routes/cursor.index.tsx +5 -2
- package/apps/ui/src/routes/grok-sessions.$sessionId.tsx +25 -12
- package/apps/ui/src/routes/grok.$workspaceKey.tsx +20 -26
- package/apps/ui/src/routes/grok.index.tsx +2 -2
- package/apps/ui/src/routes/index.tsx +2 -18
- package/apps/ui/src/routes/kiro-sessions.$sessionId.tsx +25 -12
- package/apps/ui/src/routes/kiro.$workspaceKey.tsx +27 -32
- package/apps/ui/src/routes/kiro.index.tsx +2 -2
- package/apps/ui/src/routes/opencode-sessions.$sessionId.tsx +29 -36
- package/apps/ui/src/routes/opencode.$workspaceKey.tsx +45 -61
- package/apps/ui/src/routes/opencode.index.tsx +8 -17
- package/apps/ui/src/routes/qoder-sessions.$sessionId.tsx +12 -11
- package/apps/ui/src/routes/qoder.$workspaceKey.tsx +19 -25
- package/apps/ui/src/routes/qoder.index.tsx +2 -2
- package/apps/ui/src/routes/threads.$threadId.tsx +87 -56
- package/apps/ui/vite.config.ts +22 -12
- package/bin/spiracha.ts +28 -1
- package/package.json +26 -17
- package/src/client.ts +61 -16
- package/src/lib/antigravity-db.ts +236 -82
- package/src/lib/antigravity-keychain.ts +7 -8
- package/src/lib/antigravity-transcript-phase.ts +47 -0
- package/src/lib/claude-code-db.ts +45 -19
- package/src/lib/codex-analytics.ts +32 -5
- package/src/lib/codex-browser-db.ts +540 -137
- package/src/lib/codex-browser-export.ts +2 -7
- package/src/lib/codex-browser-types.ts +21 -1
- package/src/lib/codex-thread-cache.ts +88 -33
- package/src/lib/codex-thread-parser.ts +78 -35
- package/src/lib/codex-thread-recovery.ts +38 -11
- package/src/lib/codex-transcript-renderer.ts +49 -61
- package/src/lib/concurrency.ts +15 -3
- package/src/lib/conversation-api.ts +34 -31
- package/src/lib/conversation-data/adapter-helpers.ts +25 -3
- package/src/lib/conversation-data/antigravity-adapter.ts +54 -29
- package/src/lib/conversation-data/claude-code-adapter.ts +32 -6
- package/src/lib/conversation-data/codex-adapter.ts +91 -80
- package/src/lib/conversation-data/cursor-adapter.ts +48 -34
- package/src/lib/conversation-data/grok-adapter.ts +19 -6
- package/src/lib/conversation-data/index.ts +29 -10
- package/src/lib/conversation-data/kiro-adapter.ts +18 -5
- package/src/lib/conversation-data/message-selector.ts +2 -5
- package/src/lib/conversation-data/opencode-adapter.ts +43 -15
- package/src/lib/conversation-data/path-match.ts +1 -13
- package/src/lib/conversation-data/qoder-adapter.ts +23 -18
- package/src/lib/conversation-zip-export.ts +20 -2
- package/src/lib/cursor-db.ts +34 -23
- package/src/lib/cursor-id.ts +17 -0
- package/src/lib/cursor-recovery.ts +146 -35
- package/src/lib/cursor-transcript-phase.ts +40 -0
- package/src/lib/cursor-transcript.ts +21 -5
- package/src/lib/grok-db.ts +76 -34
- package/src/lib/grok-transcript-phase.ts +11 -2
- package/src/lib/grok-transcript.ts +15 -2
- package/src/lib/kiro-db.ts +161 -68
- package/src/lib/kiro-transcript.ts +0 -4
- package/src/lib/opencode-db.ts +4 -8
- package/src/lib/opencode-think-tags.ts +17 -3
- package/src/lib/portable-path.ts +9 -0
- package/src/lib/qoder-acp-client.ts +11 -2
- package/src/lib/qoder-db.ts +51 -17
- package/src/lib/qoder-transcript-phase.ts +64 -0
- package/src/lib/qoder-transcript.ts +30 -2
- package/src/lib/shared.ts +51 -15
- package/src/lib/sqlite-error.ts +31 -5
- package/src/lib/transcript-load-limiter.ts +8 -3
- package/src/lib/ui-cache.ts +152 -17
- package/src/lib/ui-export-archive.ts +14 -55
- package/src/lib/ui-export-files.ts +75 -15
- package/src/lib/ui-export-zip.ts +40 -0
- package/apps/ui/package.json +0 -65
package/AGENTS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
This repo is a Bun-first local app for browsing, exporting, and exposing agent conversation history from Codex, Claude Code, Kiro, Qoder, Cursor, Antigravity, and OpenCode.
|
|
5
|
+
This repo is a Bun-first local app for browsing, exporting, and exposing agent conversation history from Codex, Claude Code, Grok, Kiro, Qoder, Cursor, Antigravity, and OpenCode.
|
|
6
6
|
|
|
7
7
|
The command-line exporter, MCP server, and Codex plugin were removed in the 2.0 hard cut. Do not add bridge commands, compatibility aliases, or deprecated entrypoints back. New client workflows should use the stable HTTP API exposed by the UI server or the stable `spiracha/client` package export.
|
|
8
8
|
|
|
@@ -52,10 +52,14 @@ Codex browser/export modules:
|
|
|
52
52
|
- project/thread browsing queries, delete flows, dashboard summaries, DB path resolution
|
|
53
53
|
- `src/lib/codex-browser-export.ts`
|
|
54
54
|
- UI-facing thread download rendering
|
|
55
|
+
- `src/lib/codex-browser-types.ts`
|
|
56
|
+
- Codex browser query and presentation contracts
|
|
55
57
|
- `src/lib/codex-thread-types.ts`
|
|
56
58
|
- Codex DB row and transcript rendering types
|
|
57
59
|
- `src/lib/codex-transcript-renderer.ts`
|
|
58
60
|
- Markdown/plain text rendering for Codex session files
|
|
61
|
+
- `src/lib/codex-transcript-filter.ts`
|
|
62
|
+
- centralized hidden bootstrap and transcript-text filtering
|
|
59
63
|
- `src/lib/codex-thread-parser.ts`
|
|
60
64
|
- structured Codex event parsing used by analytics and the UI
|
|
61
65
|
- `src/lib/codex-analytics.ts`
|
|
@@ -66,29 +70,35 @@ Codex browser/export modules:
|
|
|
66
70
|
- Codex project recovery helpers
|
|
67
71
|
|
|
68
72
|
Source-specific browser/export modules:
|
|
69
|
-
- `src/lib/claude-code-db.ts`, `src/lib/claude-code-transcript-phase.ts`, `src/lib/claude-code-transcript.ts`
|
|
70
|
-
- `src/lib/
|
|
71
|
-
- `src/lib/
|
|
72
|
-
- `src/lib/
|
|
73
|
-
- `src/lib/
|
|
74
|
-
- `src/lib/
|
|
73
|
+
- `src/lib/claude-code-db.ts`, `src/lib/claude-code-exporter-types.ts`, `src/lib/claude-code-transcript-phase.ts`, `src/lib/claude-code-transcript.ts`
|
|
74
|
+
- `src/lib/grok-db.ts`, `src/lib/grok-exporter-types.ts`, `src/lib/grok-transcript-phase.ts`, `src/lib/grok-transcript.ts`
|
|
75
|
+
- `src/lib/kiro-db.ts`, `src/lib/kiro-exporter-types.ts`, `src/lib/kiro-transcript-phase.ts`, `src/lib/kiro-transcript.ts`
|
|
76
|
+
- `src/lib/qoder-db.ts`, `src/lib/qoder-acp-client.ts`, `src/lib/qoder-exporter-types.ts`, `src/lib/qoder-transcript-phase.ts`, `src/lib/qoder-transcript.ts`
|
|
77
|
+
- `src/lib/cursor-db.ts`, `src/lib/cursor-exporter-types.ts`, `src/lib/cursor-recovery.ts`, `src/lib/cursor-transcript-phase.ts`, `src/lib/cursor-transcript.ts`
|
|
78
|
+
- `src/lib/antigravity-db.ts`, `src/lib/antigravity-exporter-types.ts`, `src/lib/antigravity-keychain.ts`, `src/lib/antigravity-projects.ts`, `src/lib/antigravity-transcript-phase.ts`
|
|
79
|
+
- `src/lib/opencode-db.ts`, `src/lib/opencode-exporter-types.ts`, `src/lib/opencode-transcript-phase.ts`, `src/lib/opencode-think-tags.ts`, `src/lib/opencode-transcript.ts`
|
|
75
80
|
|
|
76
81
|
Shared utilities:
|
|
77
82
|
- `src/lib/concurrency.ts`
|
|
78
83
|
- `src/lib/model-label.ts`
|
|
79
84
|
- `src/lib/path-transforms.ts`
|
|
85
|
+
- `src/lib/portable-path.ts`
|
|
80
86
|
- `src/lib/shared.ts`
|
|
81
87
|
- `src/lib/sqlite-error.ts`
|
|
82
88
|
- `src/lib/sqlite-retry.ts`
|
|
83
89
|
- `src/lib/ui-cache.ts`
|
|
84
90
|
- `src/lib/ui-export-archive.ts`
|
|
85
91
|
- `src/lib/ui-export-files.ts`
|
|
92
|
+
- `src/lib/ui-export-zip.ts`
|
|
93
|
+
- `src/lib/conversation-zip-export.ts`
|
|
94
|
+
- `src/lib/transcript-load-limiter.ts`
|
|
95
|
+
- `src/coverage-check.ts`
|
|
86
96
|
|
|
87
|
-
UI
|
|
97
|
+
UI source tree:
|
|
88
98
|
- `apps/ui/`
|
|
89
99
|
- TanStack Start browser UI
|
|
90
100
|
- API routes live under `apps/ui/src/routes/api.v1.*.ts`
|
|
91
|
-
- source routes include `/threads/$threadId`, `/claude-code-sessions/$sessionId`, `/kiro-sessions/$sessionId`, `/qoder-sessions/$sessionId`, `/cursor-threads/$composerId`, `/antigravity-conversations/$conversationId`, and `/opencode-sessions/$sessionId`
|
|
101
|
+
- source routes include `/threads/$threadId`, `/claude-code-sessions/$sessionId`, `/grok-sessions/$sessionId`, `/kiro-sessions/$sessionId`, `/qoder-sessions/$sessionId`, `/cursor-threads/$composerId`, `/antigravity-conversations/$conversationId`, and `/opencode-sessions/$sessionId`
|
|
92
102
|
|
|
93
103
|
## Stable API Contract
|
|
94
104
|
|
|
@@ -105,6 +115,9 @@ The local UI server exposes:
|
|
|
105
115
|
- `POST /api/v1/conversation-query`
|
|
106
116
|
- `GET /api/v1/conversations/:source/:id`
|
|
107
117
|
- `GET /api/v1/conversations/:source/:id/export`
|
|
118
|
+
- `DELETE /api/v1/conversations/:source/:id`
|
|
119
|
+
- `POST /api/v1/conversations/delete`
|
|
120
|
+
- `POST /api/v1/conversations/export`
|
|
108
121
|
- `GET /api/v1/resolve?ref=<url-or-deeplink>`
|
|
109
122
|
|
|
110
123
|
Defaults:
|
|
@@ -126,8 +139,9 @@ Current tests cover:
|
|
|
126
139
|
- Cursor recovery/prune behavior
|
|
127
140
|
- Antigravity discovery, transcript parsing, Keychain state, and artifact export rendering
|
|
128
141
|
- OpenCode MiniMax `<think>` tag extraction, including code-literal preservation
|
|
129
|
-
- UI component and adapter behavior through the Vitest suite wrapped by `src/ui-
|
|
142
|
+
- UI component and adapter behavior through the Vitest suite wrapped by `src/ui-suite.test.ts`
|
|
130
143
|
- package manifest hard-cut guarantees through `src/package-manifest.test.ts`
|
|
144
|
+
- a 90% line-coverage gate for both the root Bun suite and UI Vitest suite, with function and hotspot reporting
|
|
131
145
|
|
|
132
146
|
When changing risky areas:
|
|
133
147
|
- Stable API changes: update `src/lib/conversation-api.test.ts` and focused tests under `src/lib/conversation-data/`.
|
|
@@ -147,13 +161,14 @@ rtk bun run build
|
|
|
147
161
|
rtk bun run coverage
|
|
148
162
|
rtk bun start
|
|
149
163
|
rtk bun run ui:preview
|
|
150
|
-
rtk bun run
|
|
164
|
+
rtk bun run test:ui
|
|
151
165
|
```
|
|
152
166
|
|
|
153
167
|
## Notes
|
|
154
168
|
|
|
155
169
|
- Keep root-package source modules imported by the UI available through `@spiracha/lib/*`.
|
|
156
|
-
- The
|
|
170
|
+
- The repository has one package manifest. Keep UI runtime dependencies needed by packaged `bunx spiracha` in root `dependencies` and build/test-only tooling in root `devDependencies`.
|
|
171
|
+
- Root-owned UI Vite commands use `apps/ui` as their internal working directory and run through `bun --bun` because TanStack and server functions depend on that application root and Bun-only modules like `bun:sqlite`. Do not add a nested manifest to achieve this. UI Vitest commands use the normal Node runtime.
|
|
157
172
|
- TanStack Start server functions should use `.validator(...)`, not deprecated `.inputValidator(...)`.
|
|
158
173
|
- API routes should use route-level `server.handlers`.
|
|
159
174
|
- Keep `*-transcript-phase.ts` modules browser-safe; UI client adapters import them directly.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](LICENSE.md)
|
|
8
8
|
[](https://bun.sh)
|
|
9
9
|
|
|
10
|
-
Spiracha is a local TanStack Start
|
|
10
|
+
Spiracha is a Bun package with a local TanStack Start UI and a direct data client for browsing and exporting agent conversation history from Codex, Claude Code, Grok, Kiro, Qoder, Cursor, Antigravity, and OpenCode.
|
|
11
11
|
|
|
12
12
|
The legacy CLI, MCP server, and Codex plugin surfaces have been removed in the 2.0 hard cut. Spiracha now exposes the UI and a stable local data API; client-specific workflows such as review collection belong in the client that calls the API.
|
|
13
13
|
|
|
@@ -24,17 +24,20 @@ Spiracha asks Vite for port 3000 and automatically uses the next available port
|
|
|
24
24
|
For repository development:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
bun install
|
|
28
|
+
bun start
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Open the local URL printed by Vite.
|
|
32
32
|
|
|
33
|
+
Spiracha requires Bun 1.3.14 or newer. Set `PORT` to request a different starting port, for example `PORT=4100 bunx spiracha`; the launcher uses the next available port if that one is occupied.
|
|
34
|
+
|
|
33
35
|
## What It Does
|
|
34
36
|
|
|
35
|
-
- Browse local conversations across Codex, Claude Code, Kiro, Qoder, Cursor, Antigravity, and OpenCode.
|
|
36
|
-
-
|
|
37
|
-
-
|
|
37
|
+
- Browse local conversations across Codex, Claude Code, Grok, Kiro, Qoder, Cursor, Antigravity, and OpenCode.
|
|
38
|
+
- Search Codex projects from the app shell, with results delegated to the shareable `/codex?q=...` inventory filter.
|
|
39
|
+
- Inspect source-specific detail pages with transcript, tool, reasoning, metadata, raw event, export, and delete flows where supported by the source. Codex thread detail includes a tool-focused activity view plus recorded goals and sandbox policy.
|
|
40
|
+
- Export transcripts from the UI as Markdown, text, or zip bundles with source-specific commentary/final-answer filtering. The last submitted export choices persist across dialog openings; canceled drafts do not.
|
|
38
41
|
- Expose a stable API for local clients that need normalized conversation metadata and message payloads.
|
|
39
42
|
- Resolve Spiracha UI links and native source links into normalized `{ source, id }` references for cross-thread context lookup.
|
|
40
43
|
|
|
@@ -95,13 +98,13 @@ Response envelope:
|
|
|
95
98
|
}
|
|
96
99
|
],
|
|
97
100
|
"meta": {
|
|
98
|
-
"
|
|
101
|
+
"has_next": false,
|
|
99
102
|
"next_cursor": null
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
```
|
|
103
106
|
|
|
104
|
-
For
|
|
107
|
+
For direct access from Bun scripts and CLIs, use the public client export. Local mode reads the source data without starting the TanStack server:
|
|
105
108
|
|
|
106
109
|
```ts
|
|
107
110
|
import { createConversationClient } from "spiracha/client";
|
|
@@ -114,12 +117,16 @@ const page = await client.listConversations({
|
|
|
114
117
|
});
|
|
115
118
|
```
|
|
116
119
|
|
|
120
|
+
Library and CLI use is quiet by default. Set `SPIRACHA_TRANSCRIPT_LOAD_LOGS=1` or
|
|
121
|
+
`SPIRACHA_OPENCODE_DB_LOGS=1` only when diagnosing loader or OpenCode database timing.
|
|
122
|
+
|
|
117
123
|
## Source Locations
|
|
118
124
|
|
|
119
125
|
| Source | Default location | Primary override |
|
|
120
126
|
| --- | --- | --- |
|
|
121
127
|
| Codex | shared Codex DB probe list | `SPIRACHA_CODEX_DB` |
|
|
122
128
|
| Claude Code | `~/.claude/projects` | `SPIRACHA_CLAUDE_CODE_PROJECTS_DIR` |
|
|
129
|
+
| Grok | `~/.grok/sessions` | `SPIRACHA_GROK_SESSIONS_DIR` |
|
|
123
130
|
| Kiro | `~/Library/Application Support/Kiro/User/globalStorage/kiro.kiroagent/workspace-sessions` | `SPIRACHA_KIRO_WORKSPACE_SESSIONS_DIR` |
|
|
124
131
|
| Qoder | `~/Library/Application Support/Qoder/User/globalStorage/state.vscdb` and `~/Library/Application Support/Qoder/User/workspaceStorage` | `SPIRACHA_QODER_GLOBAL_STATE_DB`, `SPIRACHA_QODER_WORKSPACE_STORAGE_DIR` |
|
|
125
132
|
| Cursor | `~/Library/Application Support/Cursor/User` on macOS | `SPIRACHA_CURSOR_USER_DIR`, `SPIRACHA_CURSOR_PROJECTS_DIR` |
|
|
@@ -127,27 +134,38 @@ const page = await client.listConversations({
|
|
|
127
134
|
| OpenCode | `${XDG_DATA_HOME:-~/.local/share}/opencode/opencode.db` | `SPIRACHA_OPENCODE_DB` |
|
|
128
135
|
| UI exports | OS temp directory under `spiracha-ui-exports` | `SPIRACHA_UI_EXPORT_DIR` |
|
|
129
136
|
|
|
137
|
+
### Qoder live ACP hydration
|
|
138
|
+
|
|
139
|
+
Qoder detail/export reads first use persisted state and CLI transcript files. When those do not contain assistant messages and Qoder is running, Spiracha can connect to Qoder's local JSON-RPC ACP Unix socket, issue `initialize` and `session/load`, and collect the streamed session updates. The default socket is the Qoder `SharedClientCache/qoder.sock`; override it with `SPIRACHA_QODER_SOCKET_PATH` (or the legacy environment spelling `SPIRACHA_QODER_SOCKET`). Connection failures and timeouts fall back to the persisted transcript rather than preventing the session from loading.
|
|
140
|
+
|
|
130
141
|
## UI Routes
|
|
131
142
|
|
|
132
143
|
- `/codex` and `/codex/$project` for Codex inventory and project threads.
|
|
133
144
|
- `/threads/$threadId` for Codex thread detail.
|
|
134
|
-
- `/claude-code`, `/kiro`, `/qoder`, `/cursor`, `/antigravity`, and `/opencode` for source inventories.
|
|
135
|
-
- Source detail routes include `/claude-code-sessions/$sessionId`, `/kiro-sessions/$sessionId`, `/qoder-sessions/$sessionId`, `/cursor-threads/$composerId`, `/antigravity-conversations/$conversationId`, and `/opencode-sessions/$sessionId`.
|
|
136
|
-
- `/analytics` for Codex token and tool-
|
|
145
|
+
- `/claude-code`, `/grok`, `/kiro`, `/qoder`, `/cursor`, `/antigravity`, and `/opencode` for source inventories.
|
|
146
|
+
- Source detail routes include `/claude-code-sessions/$sessionId`, `/grok-sessions/$sessionId`, `/kiro-sessions/$sessionId`, `/qoder-sessions/$sessionId`, `/cursor-threads/$composerId`, `/antigravity-conversations/$conversationId`, and `/opencode-sessions/$sessionId`.
|
|
147
|
+
- `/analytics` for project-scoped Codex token totals, average and median thread size, archive counts, tool usage, model tokens, client sources, and reasoning-effort breakdowns.
|
|
148
|
+
- `/settings` for transcript path conversion and username redaction. Export dialogs remember their own last submitted options.
|
|
137
149
|
|
|
138
150
|
## Development
|
|
139
151
|
|
|
140
152
|
```bash
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
bun test
|
|
154
|
+
bun run lint
|
|
155
|
+
bun run typecheck
|
|
156
|
+
bun run build
|
|
157
|
+
bun run coverage
|
|
158
|
+
bun start
|
|
159
|
+
bun run test:ui
|
|
148
160
|
```
|
|
149
161
|
|
|
150
|
-
|
|
162
|
+
`bun run coverage` enforces at least 90% line coverage independently for the root Bun suite and the UI Vitest suite, and reports function coverage and per-file hotspots for follow-up.
|
|
163
|
+
|
|
164
|
+
Run one root test file with `bun test src/lib/shared.test.ts`. Run one UI test file with `bun run test:ui --run apps/ui/src/components/export-dialog.vitest.tsx`.
|
|
165
|
+
|
|
166
|
+
Spiracha has one package manifest. The `apps/ui` directory remains a source boundary, but all UI and direct-client commands and dependencies are owned at the repository root. Root-owned Vite commands use `apps/ui` as their internal working directory and run through `bun --bun` because TanStack server functions import Bun-only modules such as `bun:sqlite`; Vitest uses its normal Node runtime.
|
|
167
|
+
|
|
168
|
+
TanStack Router generates `apps/ui/src/routeTree.gen.ts` during development/build. Do not edit it manually; after adding or renaming route files, run `bun run build` (or start the dev server) and include the generated update.
|
|
151
169
|
|
|
152
170
|
## Breaking Consequences
|
|
153
171
|
|
package/apps/ui/AGENTS.md
CHANGED
|
@@ -2,22 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
This
|
|
5
|
+
This directory is the local browser UI source tree for Spiracha. It reads Codex, Claude Code, Grok, Kiro, Qoder, Cursor, Antigravity, and OpenCode local data through TanStack Start server functions and shared root-package helpers.
|
|
6
6
|
|
|
7
7
|
## Commands
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
rtk bun
|
|
10
|
+
rtk bun start
|
|
11
11
|
rtk bun run build
|
|
12
|
-
rtk bun run test
|
|
12
|
+
rtk bun run test:ui
|
|
13
13
|
rtk bun run typecheck
|
|
14
14
|
rtk bun run coverage
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
Run these commands from the repository root. There is intentionally no nested UI package manifest.
|
|
18
|
+
|
|
17
19
|
Important:
|
|
18
20
|
|
|
19
|
-
- `
|
|
20
|
-
-
|
|
21
|
+
- `start`, `build`, and `ui:preview` run Vite through `bun --bun ...` on purpose. Do not switch them back to Node execution, because the server functions import Bun-only modules such as `bun:sqlite`.
|
|
22
|
+
- Those root-owned Vite scripts use `apps/ui` as their internal working directory because TanStack Start derives part of its dev-server plan from the process working directory.
|
|
23
|
+
- Keep UI runtime dependencies needed by the packaged `bunx spiracha` launcher in the root `dependencies`. Keep build and test tooling in the root `devDependencies`.
|
|
24
|
+
- Keep Vitest on its normal Node runtime; forcing it through `bun --bun` breaks test-environment module and global behavior.
|
|
21
25
|
|
|
22
26
|
## Routing
|
|
23
27
|
|
|
@@ -29,12 +33,13 @@ Important:
|
|
|
29
33
|
- Codex project inventory and project-thread search use route search params. `/codex` and `/codex/$project` use `q`.
|
|
30
34
|
- Codex analytics uses the `project` route search param so filtered analytics links can be bookmarked and reloaded.
|
|
31
35
|
- Claude Code session detail lives at `/claude-code-sessions/$sessionId`.
|
|
36
|
+
- Grok session detail lives at `/grok-sessions/$sessionId`.
|
|
32
37
|
- Kiro session detail lives at `/kiro-sessions/$sessionId`.
|
|
33
38
|
- Qoder session detail lives at `/qoder-sessions/$sessionId`.
|
|
34
39
|
- Cursor thread detail lives at `/cursor-threads/$composerId`.
|
|
35
40
|
- Antigravity conversation detail lives at `/antigravity-conversations/$conversationId`.
|
|
36
41
|
- OpenCode session detail lives at `/opencode-sessions/$sessionId`.
|
|
37
|
-
- Keep the Codex, Claude Code, Kiro, Qoder, Cursor, Antigravity, and OpenCode list/detail pages aligned around the same table-driven index/detail pattern when adding new source integrations.
|
|
42
|
+
- Keep the Codex, Claude Code, Grok, Kiro, Qoder, Cursor, Antigravity, and OpenCode list/detail pages aligned around the same table-driven index/detail pattern when adding new source integrations.
|
|
38
43
|
|
|
39
44
|
## Shared Data Layer
|
|
40
45
|
|
|
@@ -48,6 +53,9 @@ The UI depends on root-package helpers via `@spiracha/*` path aliases:
|
|
|
48
53
|
- `@spiracha/lib/claude-code-db`
|
|
49
54
|
- `@spiracha/lib/claude-code-transcript-phase`
|
|
50
55
|
- `@spiracha/lib/claude-code-transcript`
|
|
56
|
+
- `@spiracha/lib/grok-db`
|
|
57
|
+
- `@spiracha/lib/grok-transcript-phase`
|
|
58
|
+
- `@spiracha/lib/grok-transcript`
|
|
51
59
|
- `@spiracha/lib/kiro-db`
|
|
52
60
|
- `@spiracha/lib/kiro-transcript-phase`
|
|
53
61
|
- `@spiracha/lib/kiro-transcript`
|
|
@@ -73,10 +81,10 @@ The `*-transcript-phase` helpers are intentionally browser-safe and may be impor
|
|
|
73
81
|
|
|
74
82
|
Use the existing layers consistently:
|
|
75
83
|
|
|
76
|
-
- TanStack Start server functions in `src/lib/codex-server.ts`, `src/lib/claude-code-server.ts`, `src/lib/kiro-server.ts`, `src/lib/qoder-server.ts`, `src/lib/cursor-server.ts`, `src/lib/antigravity-server.ts`, and `src/lib/opencode-server.ts`
|
|
84
|
+
- TanStack Start server functions in `src/lib/codex-server.ts`, `src/lib/claude-code-server.ts`, `src/lib/grok-server.ts`, `src/lib/kiro-server.ts`, `src/lib/qoder-server.ts`, `src/lib/cursor-server.ts`, `src/lib/antigravity-server.ts`, and `src/lib/opencode-server.ts`
|
|
77
85
|
- Use for any browser-triggered read/write that needs Bun-only modules, DB access, filesystem access, Keychain access, or shared root-package helpers.
|
|
78
86
|
- Use `.validator(...)` for input validation. Do not add new `.inputValidator(...)` calls.
|
|
79
|
-
- TanStack Query query options in `src/lib/codex-queries.ts`, `src/lib/claude-code-queries.ts`, `src/lib/kiro-queries.ts`, `src/lib/qoder-queries.ts`, `src/lib/cursor-queries.ts`, `src/lib/antigravity-queries.ts`, and `src/lib/opencode-queries.ts`
|
|
87
|
+
- TanStack Query query options in `src/lib/codex-queries.ts`, `src/lib/claude-code-queries.ts`, `src/lib/grok-queries.ts`, `src/lib/kiro-queries.ts`, `src/lib/qoder-queries.ts`, `src/lib/cursor-queries.ts`, `src/lib/antigravity-queries.ts`, and `src/lib/opencode-queries.ts`
|
|
80
88
|
- Use for client-side fetching, caching, retries, and invalidation of server-function results.
|
|
81
89
|
- Shared root-package helpers under `@spiracha/lib/*`
|
|
82
90
|
- Extend these when the behavior should stay shared between the UI and the stable data API.
|
|
@@ -103,7 +111,7 @@ For URL-backed route state, use `src/lib/route-search.ts` instead of ad hoc pars
|
|
|
103
111
|
- UI component tests live under `src/**/*.vitest.tsx`.
|
|
104
112
|
- Source-specific transcript adapter tests live next to their adapter files under `src/lib/*.vitest.ts`.
|
|
105
113
|
- Route search parsing tests live next to the helper in `src/lib/route-search.vitest.ts`.
|
|
106
|
-
- The root package wraps this Vitest suite from `src/ui-
|
|
114
|
+
- The root package wraps this Vitest suite from `src/ui-suite.test.ts` so `rtk bun test` exercises both the Bun suite and the UI suite.
|
|
107
115
|
|
|
108
116
|
## Design
|
|
109
117
|
|
|
@@ -113,5 +121,5 @@ For URL-backed route state, use `src/lib/route-search.ts` instead of ad hoc pars
|
|
|
113
121
|
## Constraints
|
|
114
122
|
|
|
115
123
|
- Do not add a second database.
|
|
116
|
-
- Do not duplicate transcript parsing or export rendering in
|
|
124
|
+
- Do not duplicate transcript parsing or export rendering in the UI source tree.
|
|
117
125
|
- Use the shared root-package helpers instead.
|
package/apps/ui/README.md
CHANGED
|
@@ -17,8 +17,9 @@ The browser UI for browsing local Codex, Claude Code, Grok, Kiro, Qoder, Cursor,
|
|
|
17
17
|
|
|
18
18
|
- lists derived Codex projects from the Codex SQLite database
|
|
19
19
|
- lists Codex threads within a project in chronological order
|
|
20
|
-
- shows Codex thread timelines, tool
|
|
21
|
-
-
|
|
20
|
+
- shows Codex thread timelines, a dedicated tool activity and definition view, recorded goals, readable sandbox policy, metadata, and raw event context
|
|
21
|
+
- searches Codex projects from the app shell through the URL-backed `/codex?q=...` inventory filter
|
|
22
|
+
- exports Codex, Claude Code, Grok, Kiro, Qoder, Cursor, and OpenCode sessions or threads as Markdown, plain text, or optional zip archives with optional metadata, commentary, and tool-call inclusion; the last submitted choices persist while canceled drafts are discarded
|
|
22
23
|
- lists Claude Code workspaces and sessions from local `~/.claude/projects` JSONL files
|
|
23
24
|
- shows dedicated Claude Code session detail pages with reasoning, tool calls, token metadata, and export actions
|
|
24
25
|
- lists Grok workspaces and sessions from local Grok session archives
|
|
@@ -35,26 +36,26 @@ The browser UI for browsing local Codex, Claude Code, Grok, Kiro, Qoder, Cursor,
|
|
|
35
36
|
- unlocks Antigravity transcript export through macOS Keychain and exports conversations or artifacts as Markdown
|
|
36
37
|
- lists OpenCode workspaces and sessions from the local OpenCode SQLite database
|
|
37
38
|
- shows dedicated OpenCode session detail pages with reasoning, tool parts, MiniMax `<think>` blocks, token metadata, and export actions
|
|
38
|
-
- shows dashboard and
|
|
39
|
+
- shows dashboard and project-scoped Codex analytics for token totals, average and median thread size, archive counts, tool usage, model tokens, client sources, and reasoning effort
|
|
39
40
|
- keeps Codex inventory search and analytics project filters in URL search params for reloadable and shareable views
|
|
40
41
|
- keeps source-specific commentary hidden by default while preserving final answers, with matching export filtering for Claude Code, Kiro, Qoder, and OpenCode
|
|
41
42
|
|
|
42
43
|
## Commands
|
|
43
44
|
|
|
44
45
|
```bash
|
|
45
|
-
rtk bun
|
|
46
|
+
rtk bun start
|
|
46
47
|
rtk bun run build
|
|
47
|
-
rtk bun run test
|
|
48
|
+
rtk bun run test:ui
|
|
48
49
|
rtk bun run typecheck
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
## Runtime Note
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
Run these commands from the repository root. The UI is part of the root package and intentionally has no nested package manifest.
|
|
54
55
|
|
|
55
|
-
That is required because
|
|
56
|
+
Root-owned Vite commands use `apps/ui` as their internal working directory and run through `bun --bun`. That is required because TanStack Start derives part of its server plan from the application directory and the server functions use Bun-only modules such as `bun:sqlite`.
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
Vitest uses its normal Node runtime.
|
|
58
59
|
|
|
59
60
|
## Configuration
|
|
60
61
|
|
|
@@ -72,6 +73,8 @@ Runtime configuration is intentionally small:
|
|
|
72
73
|
- `SPIRACHA_TRANSCRIPT_LOAD_CONCURRENCY`
|
|
73
74
|
- Optional positive integer for detail-page transcript loading concurrency across sources.
|
|
74
75
|
- Defaults to `3` and is capped at `16` to protect the server from excessive parallel disk and database work.
|
|
76
|
+
- `SPIRACHA_TRANSCRIPT_LOAD_LOGS`
|
|
77
|
+
- Set to `1` to log transcript-loader queue and timing diagnostics. Disabled by default so library and CLI consumers stay quiet.
|
|
75
78
|
- `SPIRACHA_CLAUDE_CODE_PROJECTS_DIR`
|
|
76
79
|
- Optional absolute path to the Claude Code projects directory.
|
|
77
80
|
- If unset, Spiracha reads `${SPIRACHA_CLAUDE_CODE_DATA_DIR:-~/.claude}/projects`. `SPIRACHA_CLAUDE_CODE_DIR` and `SPIRACHA_CLAUDE_HOME` are also accepted aliases for the Claude Code data directory.
|
|
@@ -93,6 +96,8 @@ Runtime configuration is intentionally small:
|
|
|
93
96
|
- `SPIRACHA_OPENCODE_DB_CONCURRENCY`
|
|
94
97
|
- Optional positive integer for concurrent OpenCode database reads.
|
|
95
98
|
- Defaults to `2`.
|
|
99
|
+
- `SPIRACHA_OPENCODE_DB_LOGS`
|
|
100
|
+
- Set to `1` to log OpenCode database queue and timing diagnostics. Disabled by default.
|
|
96
101
|
- `SPIRACHA_CURSOR_USER_DIR`
|
|
97
102
|
- Optional absolute path to Cursor's `User` directory.
|
|
98
103
|
- If unset, Spiracha reads the platform default Cursor user-data directory.
|
|
@@ -121,6 +126,8 @@ Default source locations:
|
|
|
121
126
|
|
|
122
127
|
Codex analytics cache keys are based on Codex DB row metadata instead of statting every rollout file before cache hits. That keeps large histories responsive. The tradeoff is that manual JSONL edits outside Codex do not invalidate analytics unless DB row metadata changes or the temporary UI cache is cleared.
|
|
123
128
|
|
|
129
|
+
The temporary UI cache is pruned opportunistically at read/write boundaries, with cleanup scans throttled to once per minute. Entries expire after 24 hours and the retained cache is capped at 256 MiB; recently accessed entries refresh their age so hot data survives eviction.
|
|
130
|
+
|
|
124
131
|
Transcript detail pages expose the same display controls across sources: user messages, commentary, tool calls, extra events, and raw JSON. Claude Code assistant lead-ins are classified from `stop_reason`, Kiro assistant phases are classified per user turn from session and execution files, Qoder shows local prompt history plus checkpoint file operations, and OpenCode assistant phases are classified per assistant run after stripping MiniMax `<think>` blocks into commentary. OpenCode think-tag extraction preserves literal `<think>` examples inside Markdown code spans and fenced code blocks.
|
|
125
132
|
|
|
126
133
|
## Routes
|
|
@@ -178,11 +185,11 @@ Transcript detail pages expose the same display controls across sources: user me
|
|
|
178
185
|
- `/$threadId`
|
|
179
186
|
- shortcut redirect to the thread detail page for pasted Codex thread UUIDs
|
|
180
187
|
- `/analytics`
|
|
181
|
-
- Codex token
|
|
188
|
+
- project-scoped Codex token, archive, tool, model, client-source, and reasoning-effort analytics, with `project` as the route search param
|
|
182
189
|
|
|
183
190
|
## Testing
|
|
184
191
|
|
|
185
192
|
- UI component tests live under `src/**/*.vitest.tsx`.
|
|
186
193
|
- Source-specific transcript event adapter tests live under `src/lib/*.vitest.ts`.
|
|
187
194
|
- Route search parsing tests live in `src/lib/route-search.vitest.ts`.
|
|
188
|
-
- The repo root wraps this Vitest suite from `src/ui-
|
|
195
|
+
- The repo root wraps this Vitest suite from `src/ui-suite.test.ts`, so `rtk bun test` at the root covers both the Bun tests and the UI tests.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CodexAnalytics,
|
|
3
|
+
DistributionItem,
|
|
4
|
+
ModelTokenSummary,
|
|
5
|
+
ToolUsageSummary,
|
|
6
|
+
} from '@spiracha/lib/codex-browser-types';
|
|
7
|
+
import { createColumnHelper } from '@tanstack/react-table';
|
|
8
|
+
import { formatNumber, formatTokens } from '#/lib/formatters';
|
|
9
|
+
import { DataTable } from './data-table';
|
|
10
|
+
|
|
11
|
+
const toolUsageColumnHelper = createColumnHelper<ToolUsageSummary>();
|
|
12
|
+
const toolUsageColumns = [
|
|
13
|
+
toolUsageColumnHelper.accessor('name', {
|
|
14
|
+
cell: (info) => <span className="font-mono text-sm">{info.getValue()}</span>,
|
|
15
|
+
header: 'Tool',
|
|
16
|
+
}),
|
|
17
|
+
toolUsageColumnHelper.accessor('count', {
|
|
18
|
+
cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
|
|
19
|
+
header: 'Calls',
|
|
20
|
+
}),
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
const modelColumnHelper = createColumnHelper<ModelTokenSummary>();
|
|
24
|
+
const modelColumns = [
|
|
25
|
+
modelColumnHelper.accessor('model', {
|
|
26
|
+
cell: (info) => <span className="font-mono text-sm">{info.getValue()}</span>,
|
|
27
|
+
header: 'Model',
|
|
28
|
+
}),
|
|
29
|
+
modelColumnHelper.accessor('threadCount', {
|
|
30
|
+
cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
|
|
31
|
+
header: 'Threads',
|
|
32
|
+
}),
|
|
33
|
+
modelColumnHelper.accessor('totalTokens', {
|
|
34
|
+
cell: (info) => <span className="font-mono text-sm">{formatTokens(info.getValue())}</span>,
|
|
35
|
+
header: 'Tokens',
|
|
36
|
+
}),
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
const distributionColumnHelper = createColumnHelper<DistributionItem>();
|
|
40
|
+
const distributionColumns = [
|
|
41
|
+
distributionColumnHelper.accessor('label', {
|
|
42
|
+
cell: (info) => <span className="font-mono text-sm">{info.getValue()}</span>,
|
|
43
|
+
header: 'Value',
|
|
44
|
+
}),
|
|
45
|
+
distributionColumnHelper.accessor('count', {
|
|
46
|
+
cell: (info) => <span className="font-mono text-sm">{formatNumber(info.getValue())}</span>,
|
|
47
|
+
header: 'Threads',
|
|
48
|
+
}),
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
type AnalyticsBreakdownsProps = Pick<CodexAnalytics, 'modelsByTokens' | 'reasoningEfforts' | 'sources' | 'toolUsage'>;
|
|
52
|
+
|
|
53
|
+
export const AnalyticsBreakdowns = ({
|
|
54
|
+
modelsByTokens,
|
|
55
|
+
reasoningEfforts,
|
|
56
|
+
sources,
|
|
57
|
+
toolUsage,
|
|
58
|
+
}: AnalyticsBreakdownsProps) => {
|
|
59
|
+
return (
|
|
60
|
+
<div className="grid gap-6 xl:grid-cols-2">
|
|
61
|
+
<section className="space-y-4">
|
|
62
|
+
<div>
|
|
63
|
+
<h3 className="font-semibold text-sm">Most frequent tool calls</h3>
|
|
64
|
+
<p className="mt-1 text-[var(--muted-foreground)] text-sm">
|
|
65
|
+
Useful for future prompt and tool optimization work.
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
<DataTable columns={toolUsageColumns} data={toolUsage} emptyMessage="No tool calls recorded." />
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<section className="space-y-4">
|
|
72
|
+
<div>
|
|
73
|
+
<h3 className="font-semibold text-sm">Model token breakdown</h3>
|
|
74
|
+
<p className="mt-1 text-[var(--muted-foreground)] text-sm">
|
|
75
|
+
Compare model usage and token concentration within the current project scope.
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
<DataTable columns={modelColumns} data={modelsByTokens} emptyMessage="No model usage recorded." />
|
|
79
|
+
</section>
|
|
80
|
+
|
|
81
|
+
<section className="space-y-4">
|
|
82
|
+
<div>
|
|
83
|
+
<h3 className="font-semibold text-sm">Client source breakdown</h3>
|
|
84
|
+
<p className="mt-1 text-[var(--muted-foreground)] text-sm">
|
|
85
|
+
See which Codex clients created threads in the current scope.
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
<DataTable columns={distributionColumns} data={sources} emptyMessage="No client sources recorded." />
|
|
89
|
+
</section>
|
|
90
|
+
|
|
91
|
+
<section className="space-y-4">
|
|
92
|
+
<div>
|
|
93
|
+
<h3 className="font-semibold text-sm">Reasoning effort breakdown</h3>
|
|
94
|
+
<p className="mt-1 text-[var(--muted-foreground)] text-sm">
|
|
95
|
+
Compare configured reasoning effort across threads.
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
<DataTable
|
|
99
|
+
columns={distributionColumns}
|
|
100
|
+
data={reasoningEfforts}
|
|
101
|
+
emptyMessage="No reasoning effort recorded."
|
|
102
|
+
/>
|
|
103
|
+
</section>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Link, useRouterState } from '@tanstack/react-router';
|
|
1
|
+
import { Link, useNavigate, useRouterState } from '@tanstack/react-router';
|
|
2
2
|
import {
|
|
3
3
|
BarChart3,
|
|
4
4
|
Bot,
|
|
@@ -6,12 +6,13 @@ import {
|
|
|
6
6
|
Code2,
|
|
7
7
|
FolderOpen,
|
|
8
8
|
LayoutDashboard,
|
|
9
|
+
Search,
|
|
9
10
|
Settings2,
|
|
10
11
|
Sparkles,
|
|
11
12
|
SquareTerminal,
|
|
12
13
|
Workflow,
|
|
13
14
|
} from 'lucide-react';
|
|
14
|
-
import type
|
|
15
|
+
import { type FormEvent, type PropsWithChildren, useEffect, useState } from 'react';
|
|
15
16
|
import { packageMetadata } from '#/lib/package-metadata';
|
|
16
17
|
import { cn } from '#/lib/utils';
|
|
17
18
|
import { ThemeToggle } from './theme-toggle';
|
|
@@ -59,9 +60,33 @@ const GitHubIcon = ({ className }: { className?: string }) => (
|
|
|
59
60
|
);
|
|
60
61
|
|
|
61
62
|
export function AppShell({ children }: PropsWithChildren) {
|
|
63
|
+
const navigate = useNavigate();
|
|
62
64
|
const pathname = useRouterState({
|
|
63
65
|
select: (state) => state.location.pathname,
|
|
64
66
|
});
|
|
67
|
+
const routeProjectQuery = useRouterState({
|
|
68
|
+
select: (state) => {
|
|
69
|
+
if (state.location.pathname !== '/codex' && state.location.pathname !== '/codex/') {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
const query = (state.location.search as Record<string, unknown>).q;
|
|
73
|
+
return typeof query === 'string' ? query : '';
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const [projectQuery, setProjectQuery] = useState(routeProjectQuery);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setProjectQuery(routeProjectQuery);
|
|
80
|
+
}, [routeProjectQuery]);
|
|
81
|
+
|
|
82
|
+
const handleProjectSearch = (event: FormEvent<HTMLFormElement>) => {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
const query = projectQuery.trim();
|
|
85
|
+
void navigate({
|
|
86
|
+
search: query ? { q: query } : {},
|
|
87
|
+
to: '/codex',
|
|
88
|
+
});
|
|
89
|
+
};
|
|
65
90
|
|
|
66
91
|
return (
|
|
67
92
|
<div className="min-h-screen bg-[var(--background)] text-[var(--foreground)]">
|
|
@@ -92,7 +117,24 @@ export function AppShell({ children }: PropsWithChildren) {
|
|
|
92
117
|
<ThemeToggle />
|
|
93
118
|
</div>
|
|
94
119
|
|
|
95
|
-
<
|
|
120
|
+
<search aria-label="Global project search" className="mt-5">
|
|
121
|
+
<form className="relative" onSubmit={handleProjectSearch}>
|
|
122
|
+
<Search
|
|
123
|
+
aria-hidden="true"
|
|
124
|
+
className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-[var(--muted-foreground)]"
|
|
125
|
+
/>
|
|
126
|
+
<input
|
|
127
|
+
aria-label="Search Codex projects"
|
|
128
|
+
className="h-9 w-full rounded-lg border border-[var(--border)] bg-[var(--panel-secondary)] pr-3 pl-9 text-sm outline-none transition placeholder:text-[var(--muted-foreground)] focus:border-[var(--accent)] focus:ring-2 focus:ring-[var(--accent)]/20"
|
|
129
|
+
placeholder="Search Codex projects"
|
|
130
|
+
type="search"
|
|
131
|
+
value={projectQuery}
|
|
132
|
+
onChange={(event) => setProjectQuery(event.target.value)}
|
|
133
|
+
/>
|
|
134
|
+
</form>
|
|
135
|
+
</search>
|
|
136
|
+
|
|
137
|
+
<nav className="mt-3 grid gap-1">
|
|
96
138
|
{navItems.map((item) => {
|
|
97
139
|
const active = isNavItemActive(pathname, item);
|
|
98
140
|
const Icon = item.icon;
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
useReactTable,
|
|
10
10
|
} from '@tanstack/react-table';
|
|
11
11
|
import { ArrowDownUp } from 'lucide-react';
|
|
12
|
-
import { type ReactNode, useRef, useState } from 'react';
|
|
12
|
+
import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
|
|
13
13
|
import { Checkbox } from '#/components/ui/checkbox';
|
|
14
14
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '#/components/ui/table';
|
|
15
15
|
import { cn } from '#/lib/utils';
|
|
@@ -80,6 +80,22 @@ export function DataTable<TData>({
|
|
|
80
80
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
|
81
81
|
const lastSelectedRowIdRef = useRef<string | null>(null);
|
|
82
82
|
const pendingShiftSelectionRowIdRef = useRef<string | null>(null);
|
|
83
|
+
const currentRowIds = useMemo(
|
|
84
|
+
() => new Set(data.map((row, index) => (getRowId ? getRowId(row, index) : String(index)))),
|
|
85
|
+
[data, getRowId],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
setRowSelection((selection) => {
|
|
90
|
+
const next = Object.fromEntries(
|
|
91
|
+
Object.entries(selection).filter(([rowId, selected]) => selected && currentRowIds.has(rowId)),
|
|
92
|
+
);
|
|
93
|
+
return Object.keys(next).length === Object.keys(selection).length ? selection : next;
|
|
94
|
+
});
|
|
95
|
+
if (lastSelectedRowIdRef.current && !currentRowIds.has(lastSelectedRowIdRef.current)) {
|
|
96
|
+
lastSelectedRowIdRef.current = null;
|
|
97
|
+
}
|
|
98
|
+
}, [currentRowIds]);
|
|
83
99
|
|
|
84
100
|
const updateSelectionForRow = (rowId: string, checked: boolean, shiftKey: boolean) => {
|
|
85
101
|
const visibleRowIds = table.getRowModel().rows.map((row) => row.id);
|