Merge pull request #696 from getagentseal/fix/splash-compact

polish: compact first-run splash progress
This commit is contained in:
Resham Joshi 2026-07-16 11:00:03 -07:00 committed by GitHub
commit 7a91a86dcb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 57 additions and 55 deletions

View file

@ -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(<Splash hasData={false} hasError={false} />)
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)', () => {

View file

@ -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 (
<div className="splash-status">
<div className="splash-status-line">{line}</div>
{progress.order.length > 0 && (
<div className="splash-prov-strip">
{progress.order.map(id => (
<span key={id} className={`splash-prov ${progress.status[id] ?? 'pending'}`} title={providerLabel(id)}>
<ProviderLogo provider={id} size={15} />
</span>
))}
</div>
)}
<div className="splash-status-note">One-time scan · future launches are instant</div>
</div>
)
}
/**
* 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<Phase>('lit')
@ -140,31 +169,7 @@ export function Splash({ hasData, hasError }: { hasData: boolean; hasError: bool
)}
<div className="splash-word">CodeBurn</div>
<div className="splash-version">v{version}</div>
{showDetail && (
<div className="splash-status">
<div className="splash-status-line">
First run: indexing your usage history. This one-time scan can take a few minutes; future launches are instant.
</div>
{progress.order.length > 0 && (
<ul className="splash-providers">
{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 (
<li key={id} className={`splash-prov ${status}`}>
<ProviderLogo provider={id} size={16} />
<span className="splash-prov-name">{text}</span>
{status === 'done' && <span className="splash-prov-check"></span>}
</li>
)
})}
</ul>
)}
</div>
)}
{showDetail && <SplashStatus progress={progress} />}
</div>,
document.body,
)

View file

@ -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; }
}