Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19492,6 +19492,7 @@ function App(): React.JSX.Element {
geminiAuthStatus={geminiAuthStatus}
cursorProviderAvailable={cursorProviderAvailable}
grokProviderAvailable={grokProviderAvailable}
usageSummary={usageSummary}
themeAppearance={appearance.themeAppearance || 'system'}
composerStyle={appearance.composerStyle || 'default'}
userBubbleColor={appearance.userBubbleColor || 'system'}
Expand Down
142 changes: 142 additions & 0 deletions src/renderer/src/assets/css/08-theme-picker-overrides.css
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,12 @@
border-color: color-mix(in srgb, #f85149 28%, transparent);
background: color-mix(in srgb, #f85149 4%, rgba(255, 255, 255, 0.02));
}
/* Signed in but rate-limited at ~100%. Reads hotter than "CLI not
* found" (deeper red fill) so a quota wall is unmistakable. */
.first-launch-sheet-provider-card-out-of-usage {
border-color: color-mix(in srgb, #f85149 48%, transparent);
background: color-mix(in srgb, #f85149 8%, rgba(255, 255, 255, 0.02));
}
.first-launch-sheet-provider-card-deemphasised {
opacity: 0.65;
}
Expand Down Expand Up @@ -1680,6 +1686,136 @@
background: #f85149;
box-shadow: 0 0 0 3px color-mix(in srgb, #f85149 22%, transparent);
}
.first-launch-sheet-provider-status-dot-out-of-usage {
background: #f85149;
box-shadow: 0 0 0 3px color-mix(in srgb, #f85149 30%, transparent);
}
/* §1 status-dot legend — decodes the dots so a red dot isn't misread
* as one single failure (onboarding-review 3-facet ask). */
.first-launch-sheet-status-legend {
list-style: none;
margin: 8px 0 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 6px 16px;
font-size: 0.78em;
color: var(--text-tertiary);
}
.first-launch-sheet-status-legend li {
display: flex;
align-items: center;
gap: 6px;
}
/* Quota bar shown inside an "out of usage" provider card. */
.first-launch-sheet-provider-card-usage {
margin: 2px 0 0;
}
/* §4 "You stay in control" — safety mock (approval options + presets). */
.first-launch-sheet-safety {
display: flex;
flex-direction: column;
gap: 10px;
margin: 4px 0 2px;
}
.first-launch-sheet-safety-block {
display: flex;
flex-direction: column;
gap: 6px;
}
.first-launch-sheet-safety-label {
font-size: 0.74em;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-tertiary);
}
.first-launch-sheet-safety-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.first-launch-sheet-safety-chip {
font-size: 0.8em;
padding: 3px 10px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
color: var(--text-secondary);
}
.first-launch-sheet-safety-chip.danger {
border-color: color-mix(in srgb, #f85149 38%, transparent);
color: color-mix(in srgb, #f85149 82%, var(--text-primary));
}
/* §5 "Track your usage & spend" — illustrative usage mock. */
.first-launch-sheet-usage-mock {
display: flex;
flex-direction: column;
gap: 8px;
margin: 6px 0 4px;
padding: 10px 12px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.02);
}
.first-launch-sheet-usage-mock-row {
display: grid;
grid-template-columns: 54px 1fr 38px;
align-items: center;
gap: 10px;
}
.first-launch-sheet-usage-mock-label {
font-size: 0.82em;
color: var(--text-secondary);
}
.first-launch-sheet-usage-mock-pct {
font-size: 0.78em;
color: var(--text-tertiary);
text-align: right;
font-variant-numeric: tabular-nums;
}
/* Official CLI install commands — onboarding §1 + Settings → Providers. */
.provider-install-commands {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 8px;
}
.provider-install-row {
display: grid;
grid-template-columns: 60px minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
}
.provider-install-label {
font-size: 0.82em;
font-weight: 600;
color: var(--text-secondary);
}
.provider-install-cmd {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.76em;
padding: 4px 8px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
color: var(--text-primary);
overflow-x: auto;
white-space: nowrap;
}
.provider-install-copy {
flex-shrink: 0;
}
.first-launch-sheet-install,
.settings-provider-install {
margin-top: 10px;
}
.first-launch-sheet-install > summary,
.settings-provider-install > summary {
cursor: pointer;
font-size: 0.84em;
color: var(--text-secondary);
user-select: none;
}
.first-launch-sheet-provider-card-description {
margin: 0;
font-size: 0.85em;
Expand Down Expand Up @@ -1892,6 +2028,12 @@
.first-launch-sheet-ensemble-chip[data-provider='kimi'] {
border-color: color-mix(in srgb, var(--provider-kimi-color) 46%, transparent);
}
.first-launch-sheet-ensemble-chip[data-provider='cursor'] {
border-color: color-mix(in srgb, var(--provider-cursor-color) 46%, transparent);
}
.first-launch-sheet-ensemble-chip[data-provider='grok'] {
border-color: color-mix(in srgb, var(--provider-grok-color) 46%, transparent);
}
.first-launch-sheet-ensemble-arrow {
color: var(--text-tertiary);
font-size: 0.9em;
Expand Down
90 changes: 88 additions & 2 deletions src/renderer/src/components/FirstLaunchSheet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
import { renderToStaticMarkup } from 'react-dom/server'
import { FirstLaunchSheet } from './FirstLaunchSheet'
import type { GeminiAuthStatus, ProviderApiKeyStatus } from '../../../main/store/types'
import type { ModelUsageAggregate } from '../App'

/**
* Server-rendered smoke tests for FirstLaunchSheet. The component
Expand Down Expand Up @@ -91,8 +92,10 @@ describe('FirstLaunchSheet', () => {
expect(html).toContain('1. Sign in to your providers')
expect(html).toContain('2. Add your first workspace')
expect(html).toContain('3. Choose your starting look')
expect(html).toContain('4. Try Ensemble chats')
expect(html).toContain('5. Power-user shortcuts')
expect(html).toContain('4. You stay in control')
expect(html).toContain('5. Track your usage')
expect(html).toContain('6. Try Ensemble chats')
expect(html).toContain('7. Power-user shortcuts')
})

it('renders the Appearance preference controls and preview surfaces', () => {
Expand Down Expand Up @@ -136,6 +139,9 @@ describe('FirstLaunchSheet', () => {
expect(html).toContain('data-provider="claude"')
expect(html).toContain('data-provider="gemini"')
expect(html).toContain('data-provider="kimi"')
// Cursor + Grok complete the 6-provider roster (the chips use <em>).
expect(html).toContain('<em>Cursor</em>')
expect(html).toContain('<em>Grok</em>')
expect(html).toContain('Turn / Continuous in the composer')
})

Expand Down Expand Up @@ -253,6 +259,68 @@ describe('FirstLaunchSheet', () => {
expect(html).toContain('Usage credential missing')
})

it('flips a signed-in provider to "out of usage" when its quota window is maxed', () => {
const usageSummary = [
{
provider: 'codex',
model: 'usage limits',
windows: [
{
id: 'weekly',
label: 'Weekly',
limitLabel: 'Weekly limit',
usedPercent: 100,
resetAt: '2999-01-01T09:30:00.000Z'
}
]
}
] as unknown as ModelUsageAggregate[]
const html = renderToStaticMarkup(
<FirstLaunchSheet
open={true}
onDismiss={() => {}}
onOpenSettings={() => {}}
codexStatus={{ available: true, codexUsage: { planType: 'pro', userId: 'u1' } }}
claudeAuthStatus={null}
kimiAuthStatus={null}
geminiAuthStatus={null}
usageSummary={usageSummary}
/>
)
// Codex was "signed in (pro)" but the maxed window flips it to the
// explicit out-of-usage state: status text + card variant + quota bar.
// (Assert the CARD class, not the dot class — the §1 legend always
// renders an out-of-usage dot, so the dot class is not card-specific.)
expect(html).toContain('100% used')
expect(html).toContain('first-launch-sheet-provider-card-out-of-usage')
expect(html).toContain('quota-progress-bar')
})

it('keeps a signed-in provider signed-in when usage is below 100%', () => {
const usageSummary = [
{
provider: 'codex',
model: 'usage limits',
windows: [{ id: 'weekly', label: 'Weekly', limitLabel: 'Weekly limit', usedPercent: 40 }]
}
] as unknown as ModelUsageAggregate[]
const html = renderToStaticMarkup(
<FirstLaunchSheet
open={true}
onDismiss={() => {}}
onOpenSettings={() => {}}
codexStatus={{ available: true, codexUsage: { planType: 'pro', userId: 'u1' } }}
claudeAuthStatus={null}
kimiAuthStatus={null}
geminiAuthStatus={null}
usageSummary={usageSummary}
/>
)
expect(html).toContain('Signed in (pro)')
// No CARD should be out-of-usage at 40% (the legend's dot doesn't count).
expect(html).not.toContain('first-launch-sheet-provider-card-out-of-usage')
})

it('Claude card surfaces "signed in" when apiKeyConfigured is true', () => {
const html = renderToStaticMarkup(
<FirstLaunchSheet
Expand Down Expand Up @@ -354,4 +422,22 @@ describe('FirstLaunchSheet', () => {
expect(html).toContain('Skip for now')
expect(html).toContain('Got it')
})

it('renders official CLI install commands for newcomers', () => {
const html = renderToStaticMarkup(
<FirstLaunchSheet
open={true}
onDismiss={() => {}}
onOpenSettings={() => {}}
codexStatus={null}
claudeAuthStatus={null}
kimiAuthStatus={null}
geminiAuthStatus={null}
/>
)
expect(html).toContain('npm i -g @openai/codex')
expect(html).toContain('https://claude.ai/install.sh')
expect(html).toContain('https://code.kimi.com/install.sh')
expect(html).toContain('Official install commands')
})
})
Loading
Loading