# Earl Web Playground Design

## Overview

A documentation-forward web playground for earl that presents all loaded commands and provides an interactive space to execute them. Serves both template authors and AI agent operators. Launched via `earl web` and served by the existing Axum backend.

## Layout

Three resizable columns using `react-resizable-panels`:

```
┌──────────────────────────────────────────────────────────────────┐
│  earl                                     🔍 Cmd+K    ⚙         │
├─────────────┬──────────────────────────┬─────────────────────────┤
│  SIDEBAR    │  COMMAND DETAIL          │  TRY IT                 │
│  (dense)    │  (spacious, doc-style)   │  (dense, tool-style)    │
│  ~250px     │  flexible center         │  ~380px                 │
└─────────────┴──────────────────────────┴─────────────────────────┘
```

### Header

Minimal: earl wordmark left-aligned, Cmd+K search trigger center-right, settings icon right.

### Sidebar (~250px, resizable)

- **Search-first**: Cmd+K command palette (via `cmdk`) is the primary navigation. Works globally from anywhere, not just when sidebar is focused.
- **Sidebar search input**: Secondary, filters the tree inline.
- **Provider tree**: Collapsible groups. Each command shows name + tiny colored dot (6px circle: green for read, amber for write) right-aligned via `SidebarMenuBadge`.
- **Category filter**: Filterable dropdown (not chips -- there can be 50+ categories). Cross-cuts the provider tree.
- **Virtualization**: The tree must use `@tanstack/react-virtual` to handle 1,000+ commands without jank. Expanding a provider with hundreds of commands must remain smooth.
- **Active item**: 150ms ease-out background transition. 2px left accent border on selected item.

### Command Detail (center, scrollable)

Generous whitespace (`px-8`, `py-6` section spacing). Content sits directly on `--background` surface -- no wrapping card.

**Hero zone** (top, no card, `pt-8 pb-6 px-8`):
- Title: `text-2xl font-semibold tracking-tight`
- Command key below: `text-sm font-mono text-muted-foreground`
- Badges inline (third line, `gap-2`): protocol (HTTP/GraphQL/gRPC/CLI), mode (read/write with semantic color), scope (local/global)
- Summary: single paragraph, `text-sm text-muted-foreground leading-relaxed`

**Sections** separated by `border-t border-border` hairline dividers with `py-6` spacing. No nested cards except the Operation card.

1. **Description**: Rendered as Markdown via `react-markdown` with `remark-gfm`. Code blocks within descriptions use the signature code block component. Links are clickable.

2. **Parameters table**: The centerpiece.
   - Proper `<table>` at `text-[13px]`
   - Header: `text-[11px] uppercase tracking-wider text-muted-foreground font-medium`, bottom border
   - Columns: Name, Type (color-coded badge), Required (small filled 4px accent-colored dot), Default (inline code span: `bg-muted px-1 py-0.5 rounded-sm font-mono text-[12px]`), Description
   - Subtle alternate row shading: `bg-muted/30` on even rows

3. **Operation card**: This one element IS a card (`Card size="sm"`).
   - HTTP method as bold monospace badge: GET green-tinted, POST blue-tinted, PUT amber-tinted, DELETE red-tinted
   - URL template in `font-mono text-sm`, template variables (`{{ args.query }}`) highlighted in accent color
   - Headers listed
   - Auth type displayed
   - Required secrets listed with availability status

4. **Example expression**: Signature code block component (see Visual Design section). Copyable.

5. **Source footer**: Small text showing template file path and scope.

### Try-It Panel (right, ~380px, sticky as center scrolls)

Dense, tool-like styling.

**Mode toggle**: Tabs at top -- "Form" and "Expression". These are two views of the same underlying state (a `Map<string, Value>` of bound arguments + command key). Editing the form recomputes the expression. Editing the expression parses and updates form fields when valid; on parse failure, shows inline error without clobbering form state.

**Form mode**:
- Auto-generated from `params[]`
- Input types: text for strings, number for integers, checkbox for booleans, textarea for arrays/objects (JSON)
- Required fields: red asterisk
- Default values pre-filled
- Param descriptions as help text below each input

**Expression mode**:
- Monospace textarea using the signature code block styling
- Pre-filled with `example_expression` from API
- **Inline validation**: Debounced (300ms) calls to `POST /api/validate` showing parse status, resolved command, and whether all required params are provided. Errors displayed inline below textarea with character position indicators.

**Secrets warning banner**: When a command requires secrets (from `annotations.secrets`) that aren't configured, show a prominent warning banner above the form with the missing secret names and `earl secrets set <key>` instructions.

**Execute button**:
- Primary button, accent-colored (`oklch(0.55 0.20 260)`)
- Keyboard shortcut: Cmd+Enter / Ctrl+Enter from anywhere in the panel
- For `write` mode commands: confirmation dialog before executing
- Loading state: spinner replaces button text during execution
- Success flash: brief green (200ms) before returning to default

**Response viewer** (below form):
- Status badge: `200 OK` in green, `4xx`/`5xx` in red, with response time in ms
- Tabs: "Human Output" (`human_output` field), "JSON" (`result`), "Decoded" (`decoded`)
- JSON rendered with syntax highlighting, collapsible nodes, and truncation for large responses (virtualized JSON viewer for 100KB+ payloads)
- Copy-to-clipboard on each tab: icon transitions from Copy to Check with 150ms crossfade, reverts after 2s, check icon uses `--success` color

**History drawer** (collapsible bottom section):
- Vertical slide animation (`max-height 200ms ease-out`)
- Staggered fade-in for entries (50ms stagger, 150ms duration)
- Each entry: timestamp, command key, status code, truncated expression
- Click to reload response into viewer
- "Re-run" button to re-execute with same params
- Session-only (client-side state, not persisted)

## Visual Design

### Color System

Single accent color in blue-indigo range. Used surgically in exactly 4-5 places.

```css
:root {
  --accent: oklch(0.55 0.20 260);
  --accent-foreground: oklch(0.985 0 0);
  --success: oklch(0.65 0.17 145);
  --success-foreground: oklch(0.985 0 0);
  --ring: oklch(0.55 0.12 260);
}

.dark {
  --accent: oklch(0.65 0.18 260);
  --accent-foreground: oklch(0.145 0 0);
  --success: oklch(0.72 0.15 145);
  --success-foreground: oklch(0.145 0 0);
  --ring: oklch(0.50 0.10 260);
}
```

Accent used in: Execute button fill, active sidebar item indicator, focused input rings, success status badges.

**Read/Write semantic colors**:
- Read: green (`oklch(0.65 0.17 145)`) -- safe, observational
- Write: amber (`oklch(0.70 0.15 70)`) -- caution, mutating
- Sidebar: 6px colored dots (no text)
- Detail panel: full badges with text and colored left border

### Signature Code Block Component

The visual identity of the tool.

- **Background**: Cool-tinted surface. Light: `oklch(0.975 0.005 260)`. Dark: `oklch(0.18 0.008 260)`.
- **Header bar**: 28px, language label left (`text-[10px] uppercase tracking-widest text-muted-foreground`), copy button right (ghost variant).
- **Font**: `"Noto Sans Mono", "SF Mono", "Fira Code", ui-monospace, monospace` at 13px, line-height 1.6.
- **Border**: `1px solid var(--border)`, `border-radius: var(--radius-lg)`. No shadow.
- **Copy feedback**: Icon crossfades Copy → Check (150ms), reverts after 2s, check uses `--success` color.

The expression textarea in the Try-It Panel shares this exact styling.

### Typography

- **Primary**: Noto Sans Variable (already chosen). Use `tracking-tight` on headings, `leading-relaxed` on body text.
- **Monospace**: `"Noto Sans Mono", "SF Mono", "Fira Code", ui-monospace, monospace`
- **Type scale**: `text-2xl` for command titles, `text-sm` for body/descriptions, `text-[13px]` for table content, `text-[11px]` for table headers and labels, `text-[10px]` for code block headers.

### Empty States

**No command selected** (center panel): Centered composition:
- "earl" in `text-4xl font-semibold tracking-tight text-foreground/20` (large, faint)
- Instruction: "Select a command from the sidebar, or press" + inline `Kbd` component showing `Cmd K`
- 3-4 quick-start ghost buttons linking to interesting commands
- No illustrations, no mascots. Restraint is the aesthetic.

**Loading** (sidebar): `SidebarMenuSkeleton`. Center panel: layout-preserving skeletons matching the command detail structure (title skeleton wider, key skeleton narrower, badge skeletons, paragraph blocks).

**Try-It Panel empty**: "Select a command to try it" centered in muted text. After command selected but before execution: dashed border container (`border border-dashed border-border rounded-lg`) with centered "Response will appear here."

### Micro-interactions

1. **Sidebar selection**: 150ms ease-out background transition
2. **Panel resize handles**: 1px hairline, 4px handle area on hover with subtle background tint
3. **Execute button**: Spinner during request, green flash (200ms) on completion
4. **Code block copy**: 150ms crossfade Copy → Check icon
5. **History drawer**: Vertical slide 200ms ease-out, staggered entry fade-in

## Backend Changes

### Add `secrets` to ToolSummaryResponse

Add `secrets: Vec<String>` field populated from `entry.template.annotations.secrets`. Frontend needs this to show the secrets warning banner.

### Add `POST /api/validate` endpoint

Runs `parse_expression` + `bind_arguments` without executing. Returns structured result:

```json
{
  "valid": true,
  "command_key": "github.search_issues",
  "bound_params": ["query", "per_page"],
  "missing_required": []
}
```

Or on failure:

```json
{
  "valid": false,
  "error": {
    "code": "parse_error",
    "message": "expected `.` at position 6",
    "offset": 6
  }
}
```

### Add `GET /api/secrets/status` endpoint

Checks whether required secrets are configured in the keychain. Returns:

```json
{
  "secrets": {
    "github.token": { "configured": true },
    "slack.token": { "configured": false }
  }
}
```

### Fix CSS bug

In `web/src/index.css`, the global rules at lines 46-55 use `hsl(var(--border))` but tokens are defined in oklch format. Change to bare `var(--border)` (and same for `--foreground`, `--background`).

## State Management

No external state library. Use `useReducer` with a typed action union for coordinated state:

- Selected command key
- Form values (Map<string, Value>)
- Expression text
- Form/expression sync direction
- Execution history (array)
- Loading states (catalog, detail, execution, validation)
- Search query and category filter
- Panel sizes

Hash-based URL routing (`#/provider/command-name`) for bookmarkability. Split provider and command in the URL for parseability.

## Component Architecture

```
App
├── Header
│   ├── Logo
│   ├── SearchTrigger (opens CommandPalette)
│   └── SettingsButton
├── CommandPalette (cmdk, global Cmd+K)
├── ResizablePanelGroup (horizontal)
│   ├── Sidebar
│   │   ├── SidebarSearch (inline filter)
│   │   ├── CategoryDropdown (filterable)
│   │   └── ProviderTree (virtualized via @tanstack/react-virtual)
│   │       └── CommandItem (name + read/write dot)
│   ├── CommandDetail
│   │   ├── HeroZone (title, key, badges, summary)
│   │   ├── DescriptionSection (markdown rendered)
│   │   ├── ParamsTable
│   │   ├── OperationCard
│   │   ├── ExampleExpression (CodeBlock component)
│   │   └── SourceFooter
│   └── TryItPanel
│       ├── SecretsWarning (conditional banner)
│       ├── ModeToggle (Form | Expression tabs)
│       ├── ParamForm (auto-generated inputs)
│       ├── ExpressionEditor (CodeBlock-styled textarea + inline validation)
│       ├── ExecuteButton (with write confirmation AlertDialog)
│       ├── ResponseViewer
│       │   ├── StatusBadge
│       │   └── OutputTabs (Human | JSON | Decoded)
│       └── HistoryDrawer (collapsible)
└── Toaster (sonner)
```

## Dependencies to Add

- `react-markdown` + `remark-gfm` -- Markdown rendering for descriptions
- `@tanstack/react-virtual` -- Virtualized sidebar list for 1,000+ commands

No other new dependencies needed. Everything else is covered by the existing shadcn component set.
