feat(core-ui): Claude Code icon in harness dropdown, drop credentials badge

- ConfigPicker harness dropdown rows now render the Claude Code mark
  as a leading icon (PickerRow gained an optional `icon` prop).
- Removed the Anthropic credentials status pill from the composer
  chrome per request. Deleted the AnthropicStatusBadge component and
  its now-dead plumbing: the anthropicState/anthropicStatus props
  threaded ConfigPicker ← SessionView ← AppShell, and AppShell's
  useAnthropicStatus consumption. The hook + anthropic-status lib stay
  (still used by the harness settings page).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Eliot MAURICE 2026-05-14 19:18:24 +00:00
parent 2d64e526f7
commit bde7a06d6d
3 changed files with 13 additions and 104 deletions

View file

@ -21,7 +21,6 @@ import { Sidebar } from '../Sidebar/Sidebar';
import { SessionView } from '../SessionView/SessionView';
import { useSessions } from '../../lib/store';
import { useDeployStatus } from '../../lib/deploy-status';
import { useAnthropicStatus } from '../../lib/anthropic-status';
import type { SessionUser } from '../../lib/types';
interface AppShellProps {
@ -75,7 +74,6 @@ export function AppShell({
});
const deploy = useDeployStatus(client);
const anthropic = useAnthropicStatus(client);
const [sidebarOpen, setSidebarOpen] = useState(true);
return (
@ -135,8 +133,6 @@ export function AppShell({
onCancel={cancelStream}
harnessVersions={deploy.version?.harnesses ?? null}
onAnswerQuestion={answerQuestion}
anthropicState={anthropic.state}
anthropicStatus={anthropic.status}
/>
</div>
</div>

View file

@ -16,7 +16,6 @@ import { useEffect, useMemo, useRef, type ReactNode } from 'react';
import { AlertTriangle, Check, ChevronDown, Cpu, Gauge } from 'lucide-react';
import clsx from 'clsx';
import type { HarnessVersionInfo, RateLimitInfo } from '@cial/sdk';
import type { AnthropicConnectionState, AnthropicStatus } from '../../lib/anthropic-status';
import {
EFFORT_OPTIONS,
MODEL_OPTIONS,
@ -68,16 +67,9 @@ interface ConfigPickerProps {
/** Live version per registered harness (server-broadcast). Replaces the
* static `version` field in HARNESS_OPTIONS when present. */
readonly harnessVersions?: ReadonlyArray<HarnessVersionInfo> | null;
/** Connection state of the calling user's Anthropic credential.
* Renders as a small pill next to the harness pill `'unknown'`
* while the first /status fetch is in flight. */
readonly anthropicState?: AnthropicConnectionState;
/** Full status payload lets the pill show the actual Anthropic
* account email instead of "Your account". */
readonly anthropicStatus?: AnthropicStatus | null;
/** Most recent rate-limit verdict for the active session's account.
* When throttled or out of overage credits, a small amber pill appears
* next to the Anthropic badge. */
* next to the harness pill. */
readonly rateLimit?: RateLimitInfo | null;
}
@ -91,8 +83,6 @@ export function ConfigPicker({
disabled,
harnessLocked,
harnessVersions,
anthropicState,
anthropicStatus,
rateLimit,
}: ConfigPickerProps) {
const liveVersions = useMemo(() => {
@ -124,6 +114,7 @@ export function ConfigPicker({
key={opt.id}
label={opt.label}
subtitle={opt.available ? v : `${v} · coming soon`}
icon={<ClaudeCodeIcon className="h-3.5 w-3.5" />}
selected={opt.id === harness}
disabled={!opt.available || lockedOut}
onSelect={() => onHarnessChange(opt.id)}
@ -131,9 +122,6 @@ export function ConfigPicker({
);
})}
</PickerMenu>
{anthropicState ? (
<AnthropicStatusBadge state={anthropicState} status={anthropicStatus ?? null} />
) : null}
<RateLimitBadge info={rateLimit ?? null} />
<PickerMenu
icon={<Cpu className="h-3 w-3" aria-hidden />}
@ -234,12 +222,14 @@ function PickerMenu({ icon, label, meta, ariaLabel, disabled, title, children }:
interface PickerRowProps {
readonly label: string;
readonly subtitle?: string;
/** Optional leading icon (e.g. the Claude Code mark on harness rows). */
readonly icon?: ReactNode;
readonly selected: boolean;
readonly disabled?: boolean;
readonly onSelect: () => void;
}
function PickerRow({ label, subtitle, selected, disabled, onSelect }: PickerRowProps) {
function PickerRow({ label, subtitle, icon, selected, disabled, onSelect }: PickerRowProps) {
return (
<button
type="button"
@ -261,11 +251,14 @@ function PickerRow({ label, subtitle, selected, disabled, onSelect }: PickerRowP
: 'text-[var(--t-text-primary)] hover:bg-[var(--t-surface-hover)]',
)}
>
<span className="flex flex-col">
<span className="font-medium leading-tight">{label}</span>
{subtitle ? (
<span className="text-[10.5px] text-[var(--t-text-faint)]">{subtitle}</span>
) : null}
<span className="flex items-center gap-2">
{icon ? <span className="shrink-0">{icon}</span> : null}
<span className="flex flex-col">
<span className="font-medium leading-tight">{label}</span>
{subtitle ? (
<span className="text-[10.5px] text-[var(--t-text-faint)]">{subtitle}</span>
) : null}
</span>
</span>
<Check
className={clsx(
@ -278,73 +271,6 @@ function PickerRow({ label, subtitle, selected, disabled, onSelect }: PickerRowP
);
}
// ─────────────────────────── status pill ───────────────────────────
/**
* Compact status pill showing whether the user has Anthropic credentials
* connected. Click opens the harness settings page in a new tab so
* the user can connect / disconnect without losing the chat.
*
* When the active credential is OAuth, the label becomes the actual
* account email much more informative than "Your account".
*/
function AnthropicStatusBadge({
state,
status,
}: {
state: AnthropicConnectionState;
status: AnthropicStatus | null;
}) {
if (state === 'unknown') return null;
const active =
state === 'user' ? status?.user ?? null
: state === 'instance' ? status?.instance ?? null
: null;
// Prefer the email when it's available (OAuth records carry one).
// Fall back to the masked hint, then a generic label.
const dynamicLabel =
active?.email
|| (active && active.kind === 'api_key' ? active.maskedHint : null);
const palette = {
user: {
dot: 'bg-emerald-400 shadow-[0_0_4px_rgba(52,211,153,0.6)]',
label: dynamicLabel ?? 'Your account',
title: active?.email
? `Connected as ${active.email} — used by your sessions.`
: 'Connected via your Anthropic account — used by your sessions.',
},
instance: {
dot: 'bg-emerald-400/70 shadow-[0_0_4px_rgba(52,211,153,0.45)]',
label: dynamicLabel
? `${dynamicLabel} (instance)`
: 'Instance',
title: active?.email
? `Falling back to the instance default (${active.email}) — connect your own to bill yourself.`
: 'Falling back to the instance default — connect your own to bill yourself.',
},
disconnected: {
dot: 'bg-rose-400 shadow-[0_0_4px_rgba(251,113,133,0.55)]',
label: 'Connect',
title: 'No Anthropic account connected — click to set one up.',
},
} as const;
const c = palette[state];
return (
<a
href="/.cial/settings/harnesses/anthropic"
target="_blank"
rel="noopener noreferrer"
title={c.title}
className="flex max-w-[16rem] cursor-pointer items-center gap-1 rounded-full border border-[var(--t-border)] bg-[var(--t-input-bg)] px-2.5 py-1 text-[11px] tabular-nums text-[var(--t-text-muted)] transition-colors hover:border-[var(--t-border-medium)] hover:text-[var(--t-text-primary)]"
>
<span className={`inline-flex h-1.5 w-1.5 rounded-full ${c.dot}`} aria-hidden />
<span className="truncate font-medium">{c.label}</span>
</a>
);
}
// ─────────────────────────── rate-limit pill ───────────────────────────
/**

View file

@ -12,7 +12,6 @@ import type { ChatSession } from '../../lib/types';
import type { SendOptions, NewSessionOptions } from '../../lib/store';
import type { DeployStage } from '../../lib/deploy-status';
import type { HarnessVersionInfo } from '@cial/sdk';
import type { AnthropicConnectionState, AnthropicStatus } from '../../lib/anthropic-status';
import { DEFAULT_EFFORT, DEFAULT_MODEL, type Effort } from '../../lib/models';
import { DEFAULT_HARNESS, type SessionProvider } from '../../lib/harnesses';
import { MessageList } from '../MessageList/MessageList';
@ -39,14 +38,6 @@ interface SessionViewProps {
readonly harnessVersions: ReadonlyArray<HarnessVersionInfo> | null;
/** Reply to the active session's pending harness question. */
readonly onAnswerQuestion: (content: string, questionId?: string) => void;
/** Whether the calling user has Anthropic credentials connected (and
* via what scope). Drives a small status pill next to the harness
* picker so the user can see at a glance whether their next message
* will succeed. */
readonly anthropicState: AnthropicConnectionState;
/** Full Anthropic status payload lets the pill show the actual
* account email when available instead of a generic "Your account". */
readonly anthropicStatus: AnthropicStatus | null;
}
export function SessionView({
@ -60,8 +51,6 @@ export function SessionView({
onCancel,
harnessVersions,
onAnswerQuestion,
anthropicState,
anthropicStatus,
}: SessionViewProps) {
const [draft, setDraft] = useState('');
const [harness, setHarness] = useState<SessionProvider>(DEFAULT_HARNESS);
@ -216,8 +205,6 @@ export function SessionView({
disabled={busy}
harnessLocked={!!active}
harnessVersions={harnessVersions}
anthropicState={anthropicState}
anthropicStatus={anthropicStatus}
rateLimit={active?.rateLimit ?? null}
/>
</motion.div>