diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx index b32b6e52..211ddb74 100644 --- a/frontend/src/app/components/Layout/AppShell.tsx +++ b/frontend/src/app/components/Layout/AppShell.tsx @@ -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', diff --git a/frontend/src/app/components/OnboardingV3/useOnboardingV3Pipeline.ts b/frontend/src/app/components/OnboardingV3/useOnboardingV3Pipeline.ts index 5edc2c0c..5c8139a9 100644 --- a/frontend/src/app/components/OnboardingV3/useOnboardingV3Pipeline.ts +++ b/frontend/src/app/components/OnboardingV3/useOnboardingV3Pipeline.ts @@ -34,11 +34,6 @@ function cadenceToSchedule(cadence: string): Record { 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]); diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx index a04f548c..de84e70d 100644 --- a/frontend/src/app/pages/AgentChat/AgentChat.tsx +++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx @@ -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 ( @@ -1779,7 +1770,6 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose )} diff --git a/frontend/src/app/pages/AgentChat/parsing/toolLabels.ts b/frontend/src/app/pages/AgentChat/parsing/toolLabels.ts index 720dfd60..a28b7ca2 100644 --- a/frontend/src/app/pages/AgentChat/parsing/toolLabels.ts +++ b/frontend/src/app/pages/AgentChat/parsing/toolLabels.ts @@ -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(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 = { { 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 = { ], mcplist: [ { present: 'Listing tools', past: 'Listed tools' }, - { present: 'Browsing the toolbox', past: 'Browsed the toolbox' }, ], settingsread: [ { present: 'Checking your settings', past: 'Checked your settings' },