From ec165518b507a361c2fecfcb692d87bf258e079d Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 29 Sep 2026 22:57:01 -0700 Subject: [PATCH] fix(web): new workbench, onboarding, worker page, and theme polish match the mockups (CL-9589) --- .env.example | 3 + apps/web/src/onboarding/onboarding-layout.tsx | 4 +- .../src/onboarding/provider-connect-step.tsx | 32 ++++++--- apps/web/src/onboarding/provider-rows.css | 34 +++++++++ apps/web/src/pages/new-workbench-picker.tsx | 29 +++----- apps/web/src/pages/new-workbench.css | 24 ++----- apps/web/src/pages/onboarding-page.tsx | 23 +++++- apps/web/src/pages/worker-page.tsx | 72 ++++++++++++++----- apps/web/src/settings/account-section.tsx | 21 +----- apps/web/src/settings/styles.css | 18 +++++ apps/web/src/theme-tokens.css | 4 +- apps/web/src/voice/voice-overlay.css | 5 ++ apps/web/src/voice/voice-overlay.tsx | 1 + docs/local-dev.md | 6 ++ 14 files changed, 188 insertions(+), 88 deletions(-) create mode 100644 apps/web/src/onboarding/provider-rows.css create mode 100644 apps/web/src/voice/voice-overlay.css diff --git a/.env.example b/.env.example index 81f55dde1..3f6ac84bb 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,9 @@ DATABASE_URL=postgres://localhost:5432/workbench # Hub origin the web dev server proxies /api to. BASE_URL=http://localhost:3000 # PORT=3000 +# Origin sign-in trusts. Set it to the hub origin whenever the hub is not on +# :3000, or sign-up fails with "Invalid origin". +# BETTER_AUTH_BASE_URL=http://localhost:3000 # Durable hub state (git repos and assets). The default sits inside this # work tree, which needs the opt-in below. Tests never use this path. diff --git a/apps/web/src/onboarding/onboarding-layout.tsx b/apps/web/src/onboarding/onboarding-layout.tsx index 40f036ffc..212593b9f 100644 --- a/apps/web/src/onboarding/onboarding-layout.tsx +++ b/apps/web/src/onboarding/onboarding-layout.tsx @@ -5,13 +5,13 @@ const STEP_LABELS = ["Workspace", "Model", "Myra"] as const; /** * Single centered column with a step progress bar, matching the onboarding - * mockup. `step` is the index of the current step (0-2). + * mockup. `step` is the index of the current step (0-2; 3 = all done). */ export function OnboardingLayout({ step, children, }: { - readonly step: 0 | 1 | 2; + readonly step: 0 | 1 | 2 | 3; readonly children: ReactNode; }) { return ( diff --git a/apps/web/src/onboarding/provider-connect-step.tsx b/apps/web/src/onboarding/provider-connect-step.tsx index cb34a2484..f91afe014 100644 --- a/apps/web/src/onboarding/provider-connect-step.tsx +++ b/apps/web/src/onboarding/provider-connect-step.tsx @@ -2,6 +2,7 @@ // derives the single offering it mints; a tenant that already resolves an // offering skips this step (see `resolveExistingOffering`). import { Button, Input, RadioGroup, RadioOption, Select } from "@corbits/react-ui"; +import { Cpu, Key, LinkSimple } from "@/lib/icons"; import { cancelProviderLogin, credentialNameFor, @@ -19,6 +20,7 @@ import type { FormEvent } from "react"; import type { ModelProviderPlugin } from "@intx/types"; import { fetchOllamaTags } from "./ollama-tags"; +import "./provider-rows.css"; export type ProviderOption = { /** Stable option id: two OAuth providers share one plugin, so the plugin @@ -346,14 +348,28 @@ export function ProviderConnectStep({ value={selected ?? ""} onValueChange={selectOption} > - {PROVIDER_OPTIONS.map((candidate) => ( - - ))} + {PROVIDER_OPTIONS.map((candidate) => { + const rowId = `provider-${candidate.id}`; + const RowIcon = candidate.local + ? Cpu + : candidate.oauthProvider !== undefined + ? LinkSimple + : Key; + return ( +
+ + + +
+ ); + })} {option === undefined ? null : oauthProvider !== undefined ? (

diff --git a/apps/web/src/onboarding/provider-rows.css b/apps/web/src/onboarding/provider-rows.css new file mode 100644 index 000000000..75c3c691f --- /dev/null +++ b/apps/web/src/onboarding/provider-rows.css @@ -0,0 +1,34 @@ +.onboarding-provider-row { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.625rem 0; +} + +.onboarding-provider-icon { + display: grid; + place-items: center; + flex: none; + width: 2rem; + height: 2rem; + border-radius: 0.5rem; + background: var(--muted); + color: var(--muted-foreground); +} + +.onboarding-provider-text { + display: flex; + flex-direction: column; + gap: 0.125rem; + cursor: pointer; +} + +.onboarding-provider-name { + font-size: 0.875rem; + font-weight: 500; +} + +.onboarding-provider-desc { + font-size: 0.75rem; + color: var(--muted-foreground); +} diff --git a/apps/web/src/pages/new-workbench-picker.tsx b/apps/web/src/pages/new-workbench-picker.tsx index 303bc085d..73f4150ab 100644 --- a/apps/web/src/pages/new-workbench-picker.tsx +++ b/apps/web/src/pages/new-workbench-picker.tsx @@ -2,8 +2,7 @@ // posts the message so the worker starts. import { Button, toast } from "@corbits/react-ui"; -import { PaperPlaneRight } from "@/lib/icons"; -import { CHAT_STRINGS, WorkbenchLoadingState } from "@/chat"; +import { WorkbenchLoadingState } from "@/chat"; import { useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { reportError } from "@corbits/error-sink"; @@ -30,12 +29,6 @@ export function describeWorkbenchCreateFailure(cause: unknown, refId?: string): return refId === undefined ? message : `${message} Reference: ${refId}`; } -const SUGGESTIONS = [ - "Review every pull request on a repository", - "Vet a vendor before we sign", - "Summarize what shipped each Friday", -]; - /** A workbench's name is its opening ask, trimmed. */ function workbenchName(prompt: string): string { const trimmed = prompt.trim(); @@ -92,7 +85,7 @@ export function NewWorkbenchPickerRoute() { return (

- +
{create.isPending ? ( // `delayMs={0}`: a genuine wait the instant the person sends. @@ -100,7 +93,7 @@ export function NewWorkbenchPickerRoute() { ) : ( <> -

What should this workbench do?

+

What should we work on?

{ @@ -110,8 +103,8 @@ export function NewWorkbenchPickerRoute() { >