diff --git a/app/renderer/components/Splash.test.tsx b/app/renderer/components/Splash.test.tsx
index 31b2310..00f253b 100644
--- a/app/renderer/components/Splash.test.tsx
+++ b/app/renderer/components/Splash.test.tsx
@@ -80,7 +80,7 @@ describe('Splash', () => {
expect(splashEl()).not.toBeInTheDocument()
})
- it('reveals the per-provider indexing list on real cold-scan progress', () => {
+ it('reveals the compact indexing status on real cold-scan progress', () => {
render()
expect(splashEl()).toBeInTheDocument()
// No detail before any progress arrives.
@@ -95,15 +95,17 @@ describe('Splash', () => {
const status = document.querySelector('.splash-status')
expect(status).toBeInTheDocument()
- expect(status?.textContent).toContain('First run: indexing your usage history')
- expect(status?.textContent).toContain('Ingesting Claude…')
- expect(status?.textContent).toContain('120/480')
- // Both detected providers render a row; claude is active, codex pending.
+ // One compact line: active provider + live counter. No per-provider text rows.
+ expect(status?.querySelector('.splash-status-line')?.textContent).toBe('Indexing Claude · 120/480')
+ expect(status?.textContent).toContain('One-time scan')
+ // Both detected providers render as small logos in the strip; claude active.
expect(document.querySelectorAll('.splash-prov').length).toBe(2)
- expect(document.querySelector('.splash-prov.active')?.textContent).toContain('Claude')
+ expect(document.querySelector('.splash-prov.active')?.getAttribute('title')).toBe('Claude')
act(() => { progressCb?.({ kind: 'provider', provider: 'claude', state: 'done' }) })
- expect(document.querySelector('.splash-prov.done')?.textContent).toContain('Claude')
+ expect(document.querySelector('.splash-prov.done')?.getAttribute('title')).toBe('Claude')
+ // No active provider left: the line falls back to the generic copy.
+ expect(document.querySelector('.splash-status-line')?.textContent).toBe('Indexing your usage history…')
})
it('swaps instantly under reduced motion (no fade, no min-time)', () => {
diff --git a/app/renderer/components/Splash.tsx b/app/renderer/components/Splash.tsx
index a27dfb9..5af7f7e 100644
--- a/app/renderer/components/Splash.tsx
+++ b/app/renderer/components/Splash.tsx
@@ -58,6 +58,35 @@ function providerLabel(id: string): string {
return id.split(/[-\s]+/).filter(Boolean).map(part => part.charAt(0).toUpperCase() + part.slice(1)).join(' ')
}
+/**
+ * Compact first-run indexing status: one line of copy (with a live counter when
+ * Claude is parsing), a small horizontal strip of provider logos (dim = pending,
+ * lit = active, settled = done), and one muted note. Deliberately no per-provider
+ * text rows -- a machine with 12 providers must not turn the splash into a list.
+ */
+function SplashStatus({ progress }: { progress: Progress }) {
+ const active = progress.order.find(id => progress.status[id] === 'active') ?? null
+ const counter = active === 'claude' && progress.claudeTotal > 0
+ ? ` · ${progress.claudeDone.toLocaleString('en-US')}/${progress.claudeTotal.toLocaleString('en-US')}`
+ : ''
+ const line = active ? `Indexing ${providerLabel(active)}${counter}` : 'Indexing your usage history…'
+ return (
+
+
{line}
+ {progress.order.length > 0 && (
+
+ {progress.order.map(id => (
+
+
+
+ ))}
+
+ )}
+
One-time scan · future launches are instant
+
+ )
+}
+
/**
* Full-window branded startup loader -- the same scanning moment as the menubar
* app's ignite-while-loading flame. Mounts once with the app and stays up while
@@ -70,9 +99,9 @@ function providerLabel(id: string): string {
*
* On a genuinely cold first run the overview warmup streams per-provider scan
* progress (main.ts forwards the CLI's stderr). Once real parse work is detected
- * (or the scan simply outlasts REVEAL_FALLBACK_MS), the splash reveals a "first
- * run: indexing" line and a per-provider ingest list. A warm launch resolves
- * before that threshold and never shows it.
+ * (or the scan simply outlasts REVEAL_FALLBACK_MS), the splash reveals a compact
+ * status block (see SplashStatus). A warm launch resolves before that threshold
+ * and never shows it.
*/
export function Splash({ hasData, hasError }: { hasData: boolean; hasError: boolean }) {
const [phase, setPhase] = useState('lit')
@@ -140,31 +169,7 @@ export function Splash({ hasData, hasError }: { hasData: boolean; hasError: bool
)}
CodeBurn
v{version}
- {showDetail && (
-
-
- First run: indexing your usage history. This one-time scan can take a few minutes; future launches are instant.
-
- {progress.order.length > 0 && (
-
- {progress.order.map(id => {
- const status = progress.status[id] ?? 'pending'
- const count = id === 'claude' && status === 'active' && progress.claudeTotal > 0
- ? ` ${progress.claudeDone.toLocaleString('en-US')}/${progress.claudeTotal.toLocaleString('en-US')}`
- : ''
- const text = status === 'active' ? `Ingesting ${providerLabel(id)}…${count}` : providerLabel(id)
- return (
- -
-
- {text}
- {status === 'done' && ✓}
-
- )
- })}
-
- )}
-
- )}
+ {showDetail && }
,
document.body,
)
diff --git a/app/renderer/styles/plain.css b/app/renderer/styles/plain.css
index 6f58111..23cf360 100644
--- a/app/renderer/styles/plain.css
+++ b/app/renderer/styles/plain.css
@@ -900,35 +900,30 @@ td:first-child { font-size: var(--fs-body); font-weight: var(--fw-body); }
to { opacity: 1; transform: none; }
}
-/* First-run indexing detail: framing line + per-provider ingest list. Colors are
- fixed to the dark splash canvas (like .splash-word/.splash-version), not the theme. */
+/* First-run indexing status: one line + a small provider-logo strip + one note.
+ Colors are fixed to the dark splash canvas (like .splash-word/.splash-version),
+ not the theme. Tabular numerals keep the live counter from jittering. */
.splash-status {
- margin-top: 8px; display: flex; flex-direction: column; align-items: center; gap: 16px;
- max-width: 420px; text-align: center;
+ margin-top: 4px; display: flex; flex-direction: column; align-items: center; gap: 10px;
+ max-width: 340px; text-align: center;
animation: splash-word-in 400ms ease-out both;
}
-.splash-status-line { font-size: var(--fs-label); line-height: 1.5; color: #b3a595; }
-.splash-providers {
- list-style: none; margin: 0; padding: 0; width: 100%;
- display: flex; flex-direction: column; gap: 7px;
+.splash-status-line {
+ font-size: var(--fs-label); color: #f4f1ec; font-variant-numeric: tabular-nums;
}
-.splash-prov {
- display: flex; align-items: center; gap: 9px;
- font-size: var(--fs-label); color: #6f665c; padding: 0 6px;
+.splash-prov-strip {
+ display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 9px;
}
-.splash-prov .provider-logo { opacity: 0.4; flex: none; }
-.splash-prov-name { flex: 1; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-.splash-prov.active { color: #f4f1ec; }
-.splash-prov.active .provider-logo { opacity: 1; animation: splash-prov-pulse 1.4s ease-in-out infinite; }
-.splash-prov.done { color: #b3a595; }
-.splash-prov.done .provider-logo { opacity: 0.85; }
-.splash-prov-check { color: #7ec98f; font-size: 0.85em; flex: none; }
-@keyframes splash-prov-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
+.splash-prov { display: inline-flex; opacity: 0.28; transition: opacity 200ms ease; }
+.splash-prov.done { opacity: 0.8; }
+.splash-prov.active { opacity: 1; animation: splash-prov-pulse 1.4s ease-in-out infinite; }
+.splash-status-note { font-size: var(--fs-label); color: #6f665c; }
+@keyframes splash-prov-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
@media (prefers-reduced-motion: reduce) {
.fm-flicker,
.splash-lit .splash-mark, .splash-lit .splash-mark::before, .splash-lit .splash-word { animation: none; }
.splash { transition: none; }
.splash-status { animation: none; }
- .splash-prov.active .provider-logo { animation: none; }
+ .splash-prov.active { animation: none; }
}