- 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
84 lines
2.7 KiB
TypeScript
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>
|
|
);
|
|
}
|