core/workbench-ui/src/app/TopBar.tsx
Shay cdead696ed
feat(W-027): Workbench frontend shell — five-region grid + ten empty routes + live StatusFooter (ADR-0160 / ADR-0162) (#299)
- Five-region CSS grid shell (TopBar, LeftNav, Main, StatusFooter; Inspector collapsed)
- Ten placeholder routes with EmptyState cli/string variants and ApiErrorBoundary
- Extended EmptyState API: nextAction accepts string (button) | { kind: "cli", command }
- Updated CommandPalette: real fuzzy search over three commands (Chat, Proposals, Evals)
  with ↑/↓/Enter keyboard nav and useInRouterContext degradation for Branch 1 preview
- TanStack Query hooks for all W-026 endpoints; 30s polling on runtime/status
- TypeScript mirror of workbench/schemas.py at src/types/api.ts
- StatusFooter: mutation_mode badge, git_revision (copy on click), checkpoint_revision
  with amber warning + ADR-0157/ADR-0158 expansion note
- TopBar: CORE Workbench wordmark, ⌘K palette trigger, connection pill
- ApiErrorBoundary (class component) catches WorkbenchApiError → ErrorState
- scripts/dump-api-schemas.py: AST-based Python dataclass field extractor
- workbench-ui/api-schema-snapshot.json: checked-in drift sentinel
- 51 tests across 10 files (0 failures); enum-coverage 4/4; clean vite build
2026-05-26 12:09:12 -07:00

84 lines
2.7 KiB
TypeScript

import { useState } from "react";
import { CommandPalette } from "../design/components/primitives/CommandPalette";
import { useRuntimeStatus } from "../api/queries";
export function TopBar() {
const [paletteOpen, setPaletteOpen] = useState(false);
const { isLoading, isError } = useRuntimeStatus();
function openPalette() {
setPaletteOpen(true);
}
// ⌘K global shortcut
function handleKeyDown(e: React.KeyboardEvent<HTMLButtonElement>) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
openPalette();
}
}
const connectionPill = (() => {
if (isLoading) {
return (
<span
className="rounded-full border border-[var(--color-border-subtle)] px-3 py-1 text-xs text-[var(--color-text-secondary)]"
aria-live="polite"
>
Connecting
</span>
);
}
if (isError) {
return (
<span
className="rounded-full border border-[var(--color-state-danger-border)] bg-[var(--color-state-danger-bg)] px-3 py-1 text-xs text-[var(--color-state-danger-text)]"
aria-live="polite"
>
API: unreachable
</span>
);
}
return (
<span
className="rounded-full border border-[var(--color-state-success-border)] bg-[var(--color-state-success-bg)] px-3 py-1 text-xs text-[var(--color-state-success-text)]"
aria-live="polite"
>
API: connected
</span>
);
})();
return (
<header
data-region="topbar"
className="flex items-center gap-4 border-b border-[var(--color-border-subtle)] bg-[var(--color-surface-base)] px-4 py-2"
>
{/* Wordmark */}
<span className="shrink-0 font-mono text-sm font-semibold text-[var(--color-text-primary)]">
CORE Workbench
</span>
{/* Search / Command Palette trigger */}
<div className="flex flex-1 justify-center">
<button
type="button"
className="flex items-center gap-2 rounded border border-[var(--color-border-subtle)] bg-[var(--color-surface-sunken)] px-3 py-1 text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--color-focus-ring)]"
onClick={openPalette}
onKeyDown={handleKeyDown}
aria-label="Open command palette (⌘K)"
>
<span>Search commands</span>
<kbd className="rounded border border-[var(--color-border-subtle)] px-1 font-mono text-xs">
K
</kbd>
</button>
</div>
{/* Connection pill */}
<div className="shrink-0">{connectionPill}</div>
<CommandPalette open={paletteOpen} onOpenChange={setPaletteOpen} />
</header>
);
}