()({
],
}),
loader: () => getInitialSettingsFn(),
+ notFoundComponent: RootNotFoundComponent,
});
function RootComponent() {
diff --git a/apps/ui/src/routes/analytics.tsx b/src/ui/routes/analytics.tsx
similarity index 100%
rename from apps/ui/src/routes/analytics.tsx
rename to src/ui/routes/analytics.tsx
diff --git a/apps/ui/src/routes/antigravity-conversations.$conversationId.tsx b/src/ui/routes/antigravity-conversations.$conversationId.tsx
similarity index 100%
rename from apps/ui/src/routes/antigravity-conversations.$conversationId.tsx
rename to src/ui/routes/antigravity-conversations.$conversationId.tsx
diff --git a/apps/ui/src/routes/antigravity.$workspaceKey.tsx b/src/ui/routes/antigravity.$workspaceKey.tsx
similarity index 100%
rename from apps/ui/src/routes/antigravity.$workspaceKey.tsx
rename to src/ui/routes/antigravity.$workspaceKey.tsx
diff --git a/apps/ui/src/routes/antigravity.index.tsx b/src/ui/routes/antigravity.index.tsx
similarity index 100%
rename from apps/ui/src/routes/antigravity.index.tsx
rename to src/ui/routes/antigravity.index.tsx
diff --git a/apps/ui/src/routes/api.v1.codex.threads.events.ts b/src/ui/routes/api.v1.codex.threads.events.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.codex.threads.events.ts
rename to src/ui/routes/api.v1.codex.threads.events.ts
diff --git a/apps/ui/src/routes/api.v1.conversation-query.ts b/src/ui/routes/api.v1.conversation-query.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.conversation-query.ts
rename to src/ui/routes/api.v1.conversation-query.ts
diff --git a/apps/ui/src/routes/api.v1.conversations.$source.$id.evidence.ts b/src/ui/routes/api.v1.conversations.$source.$id.evidence.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.conversations.$source.$id.evidence.ts
rename to src/ui/routes/api.v1.conversations.$source.$id.evidence.ts
diff --git a/apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts b/src/ui/routes/api.v1.conversations.$source.$id.export.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.conversations.$source.$id.export.ts
rename to src/ui/routes/api.v1.conversations.$source.$id.export.ts
diff --git a/apps/ui/src/routes/api.v1.conversations.$source.$id.ts b/src/ui/routes/api.v1.conversations.$source.$id.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.conversations.$source.$id.ts
rename to src/ui/routes/api.v1.conversations.$source.$id.ts
diff --git a/apps/ui/src/routes/api.v1.conversations.delete.ts b/src/ui/routes/api.v1.conversations.delete.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.conversations.delete.ts
rename to src/ui/routes/api.v1.conversations.delete.ts
diff --git a/apps/ui/src/routes/api.v1.conversations.export.ts b/src/ui/routes/api.v1.conversations.export.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.conversations.export.ts
rename to src/ui/routes/api.v1.conversations.export.ts
diff --git a/apps/ui/src/routes/api.v1.conversations.ts b/src/ui/routes/api.v1.conversations.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.conversations.ts
rename to src/ui/routes/api.v1.conversations.ts
diff --git a/apps/ui/src/routes/api.v1.resolve.ts b/src/ui/routes/api.v1.resolve.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.resolve.ts
rename to src/ui/routes/api.v1.resolve.ts
diff --git a/apps/ui/src/routes/api.v1.sources.ts b/src/ui/routes/api.v1.sources.ts
similarity index 100%
rename from apps/ui/src/routes/api.v1.sources.ts
rename to src/ui/routes/api.v1.sources.ts
diff --git a/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx b/src/ui/routes/claude-code-sessions.$sessionId.tsx
similarity index 98%
rename from apps/ui/src/routes/claude-code-sessions.$sessionId.tsx
rename to src/ui/routes/claude-code-sessions.$sessionId.tsx
index 8f8d720..d2a2668 100644
--- a/apps/ui/src/routes/claude-code-sessions.$sessionId.tsx
+++ b/src/ui/routes/claude-code-sessions.$sessionId.tsx
@@ -35,7 +35,7 @@ import {
} from '#/lib/claude-code-transcript-events';
import { downloadTextFile, downloadUrlFile } from '#/lib/download';
import type { ExportDialogOptions } from '#/lib/export-options';
-import { formatDateTime, formatList, formatNumber, formatTokens } from '#/lib/formatters';
+import { formatDateTime, formatList, formatModelLabel, formatNumber, formatTokens } from '#/lib/formatters';
import { getMutationErrorMessage } from '#/lib/mutation-error';
import { applyPathTransforms } from '#/lib/path-utils';
import {
@@ -90,7 +90,10 @@ const buildSessionMetadata = (detail: ClaudeCodeSessionTranscript) => [
},
{ label: 'Worktree', value: detail.session.worktree },
{ label: 'CWD', value: detail.session.cwd },
- { label: 'Model', value: detail.session.model ?? 'unknown' },
+ {
+ label: 'Model',
+ value: detail.session.model ? formatModelLabel(detail.session.model) : 'unknown',
+ },
{ label: 'Version', value: detail.session.version ?? 'unknown' },
{ label: 'Git branch', value: detail.session.gitBranch ?? 'unknown' },
{ label: 'Created', value: {formatDateTime(detail.session.createdAtMs)} },
diff --git a/apps/ui/src/routes/claude-code.$workspaceKey.tsx b/src/ui/routes/claude-code.$workspaceKey.tsx
similarity index 100%
rename from apps/ui/src/routes/claude-code.$workspaceKey.tsx
rename to src/ui/routes/claude-code.$workspaceKey.tsx
diff --git a/apps/ui/src/routes/claude-code.index.tsx b/src/ui/routes/claude-code.index.tsx
similarity index 89%
rename from apps/ui/src/routes/claude-code.index.tsx
rename to src/ui/routes/claude-code.index.tsx
index deb565a..7e64bf2 100644
--- a/apps/ui/src/routes/claude-code.index.tsx
+++ b/src/ui/routes/claude-code.index.tsx
@@ -3,6 +3,7 @@ import { createFileRoute } from '@tanstack/react-router';
import { useDeferredValue, useState } from 'react';
import { ClaudeCodeWorkspacesTable } from '#/components/claude-code-workspaces-table';
import { ListSearchInput } from '#/components/list-search-input';
+import { LoadingPanel } from '#/components/loading-panel';
import { PageHeader } from '#/components/page-header';
import { RouteErrorPanel } from '#/components/route-error-panel';
import { claudeCodeWorkspacesQueryOptions } from '#/lib/claude-code-queries';
@@ -12,6 +13,10 @@ export const Route = createFileRoute('/claude-code/')({
component: ClaudeCodePage,
errorComponent: ClaudeCodeErrorComponent,
loader: ({ context }) => context.queryClient.ensureQueryData(claudeCodeWorkspacesQueryOptions()),
+ pendingComponent: () => (
+
+ ),
+ pendingMs: 0,
});
function ClaudeCodeErrorComponent({ error }: { error: Error }) {
diff --git a/apps/ui/src/routes/cline-tasks.$taskId.tsx b/src/ui/routes/cline-tasks.$taskId.tsx
similarity index 100%
rename from apps/ui/src/routes/cline-tasks.$taskId.tsx
rename to src/ui/routes/cline-tasks.$taskId.tsx
diff --git a/apps/ui/src/routes/cline.$workspaceKey.tsx b/src/ui/routes/cline.$workspaceKey.tsx
similarity index 100%
rename from apps/ui/src/routes/cline.$workspaceKey.tsx
rename to src/ui/routes/cline.$workspaceKey.tsx
diff --git a/apps/ui/src/routes/cline.index.tsx b/src/ui/routes/cline.index.tsx
similarity index 100%
rename from apps/ui/src/routes/cline.index.tsx
rename to src/ui/routes/cline.index.tsx
diff --git a/apps/ui/src/routes/codex.$project.tsx b/src/ui/routes/codex.$project.tsx
similarity index 100%
rename from apps/ui/src/routes/codex.$project.tsx
rename to src/ui/routes/codex.$project.tsx
diff --git a/apps/ui/src/routes/codex.index.tsx b/src/ui/routes/codex.index.tsx
similarity index 100%
rename from apps/ui/src/routes/codex.index.tsx
rename to src/ui/routes/codex.index.tsx
diff --git a/apps/ui/src/routes/cursor-threads.$composerId.tsx b/src/ui/routes/cursor-threads.$composerId.tsx
similarity index 96%
rename from apps/ui/src/routes/cursor-threads.$composerId.tsx
rename to src/ui/routes/cursor-threads.$composerId.tsx
index 97a6e01..1e722c4 100644
--- a/apps/ui/src/routes/cursor-threads.$composerId.tsx
+++ b/src/ui/routes/cursor-threads.$composerId.tsx
@@ -21,7 +21,7 @@ import { deleteCursorThreadsFn, exportCursorThreadFn, type getCursorThreadDetail
import { cursorTranscriptToThreadEvents, getCursorThreadTranscriptStats } from '#/lib/cursor-transcript-events';
import { downloadTextFile, downloadUrlFile } from '#/lib/download';
import type { ExportDialogOptions } from '#/lib/export-options';
-import { formatBytes, formatDateTime, formatList, formatNumber } from '#/lib/formatters';
+import { formatBytes, formatDateTime, formatList, formatModelLabel, formatNumber } from '#/lib/formatters';
import {
getTranscriptDisplayState,
parseThreadTranscriptSearch,
@@ -50,6 +50,8 @@ const buildCursorThreadMetadata = (detail: CursorThreadDetail) => {
},
{ label: 'Workspace key', value: {detail.thread.workspaceKey} },
{ label: 'Mode', value: detail.thread.mode ?? 'unknown' },
+ { label: 'Model', value: detail.thread.model ? formatModelLabel(detail.thread.model) : 'unknown' },
+ { label: 'Reasoning', value: detail.thread.reasoningEffort ?? 'unknown' },
{
label: 'Created',
value: {formatDateTime(detail.thread.createdAtMs)},
@@ -252,7 +254,11 @@ const CursorThreadDetailPage = () => {
-
+
{
/>
{detail.transcript && transcriptEvents.length > 0 ? (
{
return ;
};
+const CursorPendingComponent = () => (
+
+);
+
const CursorPage = () => {
const queryClient = useQueryClient();
const workspaces = useSuspenseQuery(cursorWorkspacesQueryOptions()).data;
@@ -120,4 +125,6 @@ export const Route = createFileRoute('/cursor/')({
component: CursorPage,
errorComponent: CursorErrorComponent,
loader: ({ context }) => context.queryClient.ensureQueryData(cursorWorkspacesQueryOptions()),
+ pendingComponent: CursorPendingComponent,
+ pendingMs: 0,
});
diff --git a/apps/ui/src/routes/grok-sessions.$sessionId.tsx b/src/ui/routes/grok-sessions.$sessionId.tsx
similarity index 100%
rename from apps/ui/src/routes/grok-sessions.$sessionId.tsx
rename to src/ui/routes/grok-sessions.$sessionId.tsx
diff --git a/apps/ui/src/routes/grok.$workspaceKey.tsx b/src/ui/routes/grok.$workspaceKey.tsx
similarity index 100%
rename from apps/ui/src/routes/grok.$workspaceKey.tsx
rename to src/ui/routes/grok.$workspaceKey.tsx
diff --git a/apps/ui/src/routes/grok.index.tsx b/src/ui/routes/grok.index.tsx
similarity index 88%
rename from apps/ui/src/routes/grok.index.tsx
rename to src/ui/routes/grok.index.tsx
index dbb533a..54a476f 100644
--- a/apps/ui/src/routes/grok.index.tsx
+++ b/src/ui/routes/grok.index.tsx
@@ -3,6 +3,7 @@ import { createFileRoute } from '@tanstack/react-router';
import { useDeferredValue, useState } from 'react';
import { GrokWorkspacesTable } from '#/components/grok-workspaces-table';
import { ListSearchInput } from '#/components/list-search-input';
+import { LoadingPanel } from '#/components/loading-panel';
import { PageHeader } from '#/components/page-header';
import { RouteErrorPanel } from '#/components/route-error-panel';
import { grokWorkspacesQueryOptions } from '#/lib/grok-queries';
@@ -12,6 +13,10 @@ const GrokErrorComponent = ({ error }: { error: Error }) => {
return ;
};
+const GrokPendingComponent = () => (
+
+);
+
const GrokPage = () => {
const workspaces = useSuspenseQuery(grokWorkspacesQueryOptions()).data;
const [searchInput, setSearchInput] = useState('');
@@ -45,4 +50,6 @@ export const Route = createFileRoute('/grok/')({
component: GrokPage,
errorComponent: GrokErrorComponent,
loader: ({ context }) => context.queryClient.ensureQueryData(grokWorkspacesQueryOptions()),
+ pendingComponent: GrokPendingComponent,
+ pendingMs: 0,
});
diff --git a/apps/ui/src/routes/index.tsx b/src/ui/routes/index.tsx
similarity index 100%
rename from apps/ui/src/routes/index.tsx
rename to src/ui/routes/index.tsx
diff --git a/apps/ui/src/routes/kiro-sessions.$sessionId.tsx b/src/ui/routes/kiro-sessions.$sessionId.tsx
similarity index 100%
rename from apps/ui/src/routes/kiro-sessions.$sessionId.tsx
rename to src/ui/routes/kiro-sessions.$sessionId.tsx
diff --git a/apps/ui/src/routes/kiro.$workspaceKey.tsx b/src/ui/routes/kiro.$workspaceKey.tsx
similarity index 100%
rename from apps/ui/src/routes/kiro.$workspaceKey.tsx
rename to src/ui/routes/kiro.$workspaceKey.tsx
diff --git a/apps/ui/src/routes/kiro.index.tsx b/src/ui/routes/kiro.index.tsx
similarity index 89%
rename from apps/ui/src/routes/kiro.index.tsx
rename to src/ui/routes/kiro.index.tsx
index 781b199..d730087 100644
--- a/apps/ui/src/routes/kiro.index.tsx
+++ b/src/ui/routes/kiro.index.tsx
@@ -3,6 +3,7 @@ import { createFileRoute } from '@tanstack/react-router';
import { useDeferredValue, useState } from 'react';
import { KiroWorkspacesTable } from '#/components/kiro-workspaces-table';
import { ListSearchInput } from '#/components/list-search-input';
+import { LoadingPanel } from '#/components/loading-panel';
import { PageHeader } from '#/components/page-header';
import { RouteErrorPanel } from '#/components/route-error-panel';
import { kiroWorkspacesQueryOptions } from '#/lib/kiro-queries';
@@ -45,4 +46,8 @@ export const Route = createFileRoute('/kiro/')({
component: KiroPage,
errorComponent: KiroErrorComponent,
loader: ({ context }) => context.queryClient.ensureQueryData(kiroWorkspacesQueryOptions()),
+ pendingComponent: () => (
+
+ ),
+ pendingMs: 0,
});
diff --git a/apps/ui/src/routes/minimax-code-sessions.$sessionId.tsx b/src/ui/routes/minimax-code-sessions.$sessionId.tsx
similarity index 100%
rename from apps/ui/src/routes/minimax-code-sessions.$sessionId.tsx
rename to src/ui/routes/minimax-code-sessions.$sessionId.tsx
diff --git a/apps/ui/src/routes/minimax-code.$workspaceKey.tsx b/src/ui/routes/minimax-code.$workspaceKey.tsx
similarity index 100%
rename from apps/ui/src/routes/minimax-code.$workspaceKey.tsx
rename to src/ui/routes/minimax-code.$workspaceKey.tsx
diff --git a/apps/ui/src/routes/minimax-code.index.tsx b/src/ui/routes/minimax-code.index.tsx
similarity index 99%
rename from apps/ui/src/routes/minimax-code.index.tsx
rename to src/ui/routes/minimax-code.index.tsx
index ba18361..09af6f5 100644
--- a/apps/ui/src/routes/minimax-code.index.tsx
+++ b/src/ui/routes/minimax-code.index.tsx
@@ -47,4 +47,5 @@ export const Route = createFileRoute('/minimax-code/')({
pendingComponent: () => (
),
+ pendingMs: 0,
});
diff --git a/apps/ui/src/routes/opencode-sessions.$sessionId.tsx b/src/ui/routes/opencode-sessions.$sessionId.tsx
similarity index 100%
rename from apps/ui/src/routes/opencode-sessions.$sessionId.tsx
rename to src/ui/routes/opencode-sessions.$sessionId.tsx
diff --git a/apps/ui/src/routes/opencode.$workspaceKey.tsx b/src/ui/routes/opencode.$workspaceKey.tsx
similarity index 100%
rename from apps/ui/src/routes/opencode.$workspaceKey.tsx
rename to src/ui/routes/opencode.$workspaceKey.tsx
diff --git a/apps/ui/src/routes/opencode.index.tsx b/src/ui/routes/opencode.index.tsx
similarity index 100%
rename from apps/ui/src/routes/opencode.index.tsx
rename to src/ui/routes/opencode.index.tsx
diff --git a/apps/ui/src/routes/qoder-sessions.$sessionId.tsx b/src/ui/routes/qoder-sessions.$sessionId.tsx
similarity index 100%
rename from apps/ui/src/routes/qoder-sessions.$sessionId.tsx
rename to src/ui/routes/qoder-sessions.$sessionId.tsx
diff --git a/apps/ui/src/routes/qoder.$workspaceKey.tsx b/src/ui/routes/qoder.$workspaceKey.tsx
similarity index 100%
rename from apps/ui/src/routes/qoder.$workspaceKey.tsx
rename to src/ui/routes/qoder.$workspaceKey.tsx
diff --git a/apps/ui/src/routes/qoder.index.tsx b/src/ui/routes/qoder.index.tsx
similarity index 100%
rename from apps/ui/src/routes/qoder.index.tsx
rename to src/ui/routes/qoder.index.tsx
diff --git a/apps/ui/src/routes/settings.tsx b/src/ui/routes/settings.tsx
similarity index 100%
rename from apps/ui/src/routes/settings.tsx
rename to src/ui/routes/settings.tsx
diff --git a/apps/ui/src/routes/threads.$threadId.tsx b/src/ui/routes/threads.$threadId.tsx
similarity index 100%
rename from apps/ui/src/routes/threads.$threadId.tsx
rename to src/ui/routes/threads.$threadId.tsx
diff --git a/apps/ui/src/styles.css b/src/ui/styles.css
similarity index 100%
rename from apps/ui/src/styles.css
rename to src/ui/styles.css
diff --git a/apps/ui/vitest.setup.ts b/src/ui/vitest.setup.ts
similarity index 100%
rename from apps/ui/vitest.setup.ts
rename to src/ui/vitest.setup.ts
diff --git a/tsconfig.json b/tsconfig.json
index ab5edf9..4b5e41b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,5 +1,6 @@
{
"compilerOptions": {
+ "allowImportingTsExtensions": true,
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
@@ -9,7 +10,8 @@
"forceConsistentCasingInFileNames": true,
"importHelpers": false,
"isolatedModules": true,
- "lib": ["ESNext", "DOM"],
+ "jsx": "react-jsx",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleDetection": "force",
"moduleResolution": "Bundler",
@@ -20,7 +22,9 @@
"noUnusedParameters": true,
"outDir": "./dist",
"paths": {
- "@/*": ["./src/*"]
+ "@/*": ["./src/*"],
+ "@spiracha/*": ["./src/*"],
+ "#/*": ["./src/ui/*"]
},
"removeComments": false,
"rootDir": ".",
@@ -29,7 +33,7 @@
"sourceMap": true,
"strict": true,
"target": "ESNext",
- "types": ["bun", "node"],
+ "types": ["bun", "node", "vite/client"],
"verbatimModuleSyntax": true
},
"exclude": ["node_modules", "dist"],
diff --git a/apps/ui/vite.config.ts b/vite.config.ts
similarity index 89%
rename from apps/ui/vite.config.ts
rename to vite.config.ts
index 246ba2d..16a57f8 100644
--- a/apps/ui/vite.config.ts
+++ b/vite.config.ts
@@ -1,5 +1,6 @@
import { createReadStream } from 'node:fs';
import path from 'node:path';
+import { fileURLToPath } from 'node:url';
import tailwindcss from '@tailwindcss/vite';
import { devtools } from '@tanstack/devtools-vite';
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
@@ -9,9 +10,9 @@ import {
buildUiExportContentDisposition,
resolveReadableUiExportFileFromRequestPath,
UI_EXPORT_URL_PREFIX,
-} from '../../src/lib/ui-export-files';
+} from './src/lib/ui-export-files.ts';
-const uiRoot = __dirname;
+const appRoot = path.dirname(fileURLToPath(import.meta.url));
const getExportContentType = (filePath: string) => {
if (filePath.endsWith('.zip')) {
@@ -76,17 +77,17 @@ const spirachaExportFiles = (): Plugin => {
};
const config = defineConfig({
- plugins: [spirachaExportFiles(), devtools(), tailwindcss(), tanstackStart(), viteReact()],
+ plugins: [spirachaExportFiles(), devtools(), tailwindcss(), tanstackStart({ srcDirectory: 'src/ui' }), viteReact()],
resolve: {
alias: {
- '@spiracha': path.resolve(uiRoot, '..', '..', 'src'),
+ '@spiracha': path.resolve(appRoot, 'src'),
},
tsconfigPaths: true,
},
- root: uiRoot,
+ root: appRoot,
server: {
fs: {
- allow: [path.resolve(uiRoot, '..', '..')],
+ allow: [appRoot],
},
},
});
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..d9d6104
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,29 @@
+import path from 'node:path';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ resolve: {
+ alias: {
+ '@spiracha': path.resolve(__dirname, 'src'),
+ '#': path.resolve(__dirname, 'src/ui'),
+ },
+ },
+ root: __dirname,
+ test: {
+ coverage: {
+ exclude: ['src/ui/routeTree.gen.ts', 'src/ui/integrations/**', 'src/ui/router.tsx', 'src/ui/routes/**'],
+ include: ['src/ui/components/**/*.tsx', 'src/ui/lib/**/*.ts', 'src/ui/lib/**/*.tsx'],
+ provider: 'v8',
+ reporter: ['text', 'lcov'],
+ reportsDirectory: 'coverage/ui',
+ },
+ environment: 'jsdom',
+ environmentOptions: {
+ jsdom: {
+ url: 'http://localhost',
+ },
+ },
+ include: ['src/ui/**/*.vitest.ts', 'src/ui/**/*.vitest.tsx'],
+ setupFiles: ['src/ui/vitest.setup.ts'],
+ },
+});