[eric] onboarding+chrome: clear pill/tool labels (keep thinking-bubble whimsy), parallel reveal launches, overlay sidebar hugs window edges

This commit is contained in:
ciregenz
2026-07-19 23:50:16 -07:00
parent 8d3782a078
commit cc36847b9d
4 changed files with 25 additions and 62 deletions
@@ -853,9 +853,13 @@ const AppShell: React.FC = () => {
// Zen/Arc compact mode: a detached, rounded panel that SLIDES in and out from the left edge
// (sidePeek drives the transform both ways; it stays mounted so leaving glides it away, not vanish).
...(sideOverlay ? {
position: 'fixed', top: 10, left: 10, bottom: 10, zIndex: 1000002,
borderRadius: '14px', overflow: 'hidden',
boxShadow: '0 16px 48px rgba(0,0,0,0.34)', border: `1px solid ${c.border.medium}`,
// Hug the window's top/left/bottom edges (same footprint as the docked sidebar) so the
// dashboard's corner tint can't peek in the margins; only the dashboard-facing right corners
// stay rounded. The OS clips the square outer corners to the window's own rounding, so the
// window edge (the "last border") stays intact.
position: 'fixed', top: 0, left: 0, bottom: 0, zIndex: 1000002,
borderRadius: '0 14px 14px 0', overflow: 'hidden',
boxShadow: '8px 0 32px rgba(0,0,0,0.28)', borderRight: `1px solid ${c.border.medium}`,
transform: sidePeek ? 'translateX(0)' : 'translateX(-118%)',
transition: 'transform 240ms cubic-bezier(0.22,1,0.36,1)',
pointerEvents: sidePeek ? 'auto' : 'none',
@@ -34,11 +34,6 @@ function cadenceToSchedule(cadence: string): Record<string, unknown> {
return { ...base, repeat_unit: 'week', on_days: [1] };
}
// Gap between the demo launches. Right after connect the provider's OAuth token can still be mid-refresh,
// and four simultaneous cold turns raced that refresh and all "hit a snag"; the first launch warms the
// token, the rest ride it warm. Fires during the curtain beats, so the lead time is hidden.
const LAUNCH_STAGGER_MS = 1200;
// How long finish() will wait for prep before staging the reveal. Prep is kicked early (theme beat) so it
// has usually resolved; this only bites a user who outran it, and a brief wait for a COHERENT reveal beats
// an instant one showing a previous run's stale greeting. Capped so it's never an open-ended spinner.
@@ -153,28 +148,16 @@ export function useOnboardingV3Pipeline() {
if (launchedRef.current || !prep || !prep.greeting || !launchCtxRef.current.connected) return;
launchedRef.current = true;
// The four auto-run showcase jobs, one per capability: build an app, dig the web, drive a real
// browser, and set up a scheduled task. The lame file-audit is gone; the browser task shows the
// agent controlling a real site live. Fired STAGGERED so the first turn warms the just-connected
// provider token before the rest hit it (see LAUNCH_STAGGER_MS).
const launches: Array<() => void> = [];
if (prep.app_title && prep.app_prompt) {
const t = prep.app_title, p = prep.app_prompt, r = prep.app_reason ?? '';
launches.push(() => launchJob(t, p, 'app', r));
}
if (prep.research_title && prep.research_prompt) {
const t = prep.research_title, p = prep.research_prompt, r = prep.research_reason ?? '';
launches.push(() => launchJob(t, p, 'research', r));
}
if (prep.browser_title && prep.browser_prompt) {
const t = prep.browser_title, p = prep.browser_prompt, r = prep.browser_reason ?? '';
launches.push(() => launchJob(t, p, 'browser', r));
}
// browser, and set up a scheduled task. Each fires INDEPENDENTLY (own draft session, own async
// launch, own error handling), so none waits on another and one failing never blocks the rest; a
// real first-run's provider token is fresh (just connected), so parallel launch is safe.
if (prep.app_title && prep.app_prompt) launchJob(prep.app_title, prep.app_prompt, 'app', prep.app_reason ?? '');
if (prep.research_title && prep.research_prompt) launchJob(prep.research_title, prep.research_prompt, 'research', prep.research_reason ?? '');
if (prep.browser_title && prep.browser_prompt) launchJob(prep.browser_title, prep.browser_prompt, 'browser', prep.browser_reason ?? '');
// The scheduled task is a first-class part of the reveal (the "it automates for me" capability), so
// guarantee one: use the model's automation when it emitted one (it sometimes drops the last JSON
// field), else fall back to a safe, universally-useful weekly roundup.
const auto = prep.automations[0] ?? SCHEDULE_FALLBACK;
launches.push(() => createScheduledJob(auto));
launches.forEach((fn, idx) => { if (idx === 0) fn(); else window.setTimeout(fn, idx * LAUNCH_STAGGER_MS); });
createScheduledJob(prep.automations[0] ?? SCHEDULE_FALLBACK);
});
}, [launchJob, createScheduledJob]);
+4 -14
View File
@@ -155,22 +155,13 @@ const thinkingShimmerKeyframes = `
}
`;
// Pick a label deterministically per session-turn so the pill has variety without flickering between renders. Shared list with MessageBubble.
function streamingLabelFor(seedKey: string | undefined): string {
if (!seedKey) return THINKING_LABELS[0].live;
let h = 0;
for (let i = 0; i < seedKey.length; i++) {
h = ((h << 5) - h + seedKey.charCodeAt(i)) | 0;
}
return THINKING_LABELS[Math.abs(h) % THINKING_LABELS.length].live;
}
const ThinkingBubble: React.FC<{ label?: string | null; seedKey?: string }> = ({ label, seedKey }) => {
const ThinkingBubble: React.FC<{ label?: string | null }> = ({ label }) => {
const c = useClaudeTokens();
const shimmerBase = c.text.tertiary;
const shimmerHighlight = c.text.primary;
// Aux-LLM label wins; otherwise pick a quirky verb keyed off seedKey so different sessions / turns show different verbs without flicker.
const display = label ? `${label}` : `${streamingLabelFor(seedKey)}`;
// Aux-LLM label wins; otherwise the pill stays plain "Thinking". The whimsical verbs read as personality
// in the per-message thinking bubble (MessageBubble), but as a vague, confusing status on a working card.
const display = label ? `${label}` : `${THINKING_LABELS[0].live}`;
return (
<Box sx={{ display: 'flex', justifyContent: 'flex-start', my: 0.75 }}>
<style>{thinkingShimmerKeyframes}</style>
@@ -1779,7 +1770,6 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
<Box sx={{ overflowAnchor: 'none' }}>
<ThinkingBubble
label={preSendActivityLabel || session.turn_label?.label}
seedKey={`${session.id}:${session.messages?.length ?? 0}`}
/>
</Box>
)}
@@ -3,18 +3,12 @@ export interface ToolLabel {
past: string;
}
// djb2 hash; same seed always picks the same variant so rows don't flicker.
function _stableIndex(seed: string | undefined, n: number): number {
if (n <= 1 || !seed) return 0;
let h = 5381;
for (let i = 0; i < seed.length; i++) {
h = ((h << 5) + h + seed.charCodeAt(i)) | 0;
}
return Math.abs(h) % n;
}
// The clearest label of each verb pool. We used to seed-pick a random synonym for personality, but the
// whimsical spread ("Rummaging the toolbox", "Eyeballing") read as vague status noise on a working card;
// that variety lives on in the per-message thinking bubble instead. Index 0 is the plain literal by design.
function _pick<T>(variants: T[], seed?: string): T {
return variants[_stableIndex(seed, variants.length)];
void seed;
return variants[0];
}
// Index 0 is the safe default; single-entry pools mean no seeded variation.
@@ -142,17 +136,10 @@ const VARIANTS: Record<string, ToolLabel[]> = {
{ present: 'Pulling up the task', past: 'Pulled up the task' },
],
toolsearch: [
{ present: 'Looking through the toolbox', past: 'Looked through the toolbox' },
{ present: 'Hunting for the right tool', past: 'Found a tool' },
{ present: 'Browsing the toolbox', past: 'Browsed the toolbox' },
{ present: 'Rummaging the toolbox', past: 'Rummaged the toolbox' },
{ present: 'Digging through the toolbox', past: 'Dug through the toolbox' },
{ present: 'Finding the right tool', past: 'Found the right tool' },
],
mcpsearch: [
{ present: 'Looking through the toolbox', past: 'Looked through the toolbox' },
{ present: 'Hunting for the right tool', past: 'Found a tool' },
{ present: 'Browsing the toolbox', past: 'Browsed the toolbox' },
{ present: 'Rummaging the toolbox', past: 'Rummaged the toolbox' },
{ present: 'Finding the right tool', past: 'Found the right tool' },
],
// getToolLabelWithInput has the brand-aware version; this is the seedless fallback.
mcpactivate: [
@@ -164,7 +151,6 @@ const VARIANTS: Record<string, ToolLabel[]> = {
],
mcplist: [
{ present: 'Listing tools', past: 'Listed tools' },
{ present: 'Browsing the toolbox', past: 'Browsed the toolbox' },
],
settingsread: [
{ present: 'Checking your settings', past: 'Checked your settings' },