# Script Library — Left Pane Takeover Design Spec

> **Created:** 2026-03-13
> **Feature:** Redesign Script Library left pane to have two distinct modes: Browse and Configure

---

## Overview

The Script Library left pane gains two distinct modes. In **Browse mode** the user sees the full template list with filter bar and a "Configure →" button on each card. Clicking "Configure →" transitions the entire left pane (including the filter bar) to **Configure mode**, which replaces the list with a full-height view of the selected template's header, parameter form, and action buttons (Generate, Download, Copy). The right pane becomes a **read-only preview** (`ScriptPreview` only — no form or buttons). "Back to library" returns to Browse mode with filter/search state preserved.

---

## Structural Change Summary

This is a cross-column relocation of the Generate/Download/Copy controls:

| Before | After |
|--------|-------|
| Left pane: template list + filter bar | Left pane: Browse mode (list + filter) OR Configure mode (form + actions) |
| Right pane: param form + action buttons + preview | Right pane: `ScriptPreview` only (read-only display) |

`ScriptGeneratorPanel` (which currently owns the right column's form, actions, and preview) is **deleted**. The right pane becomes `ScriptPreview` in isolation. The new `ScriptConfigurePane` component owns the left pane in Configure mode and contains the form + all action buttons.

---

## Goals

- Make template selection intentional (no accidental click-to-configure)
- Give the parameter form more vertical space by using the full left pane height
- Keep the output preview always visible on the right
- Preserve filter/search state across Browse ↔ Configure transitions

---

## Non-Goals

- No changes to `ScriptPreview` internals — it moves to the right pane as-is, including its existing copy overlay button
- No changes to the Zustand store shape or actions
- No changes to the filter/search debounce logic
- No changes to routing

---

## New Work (not pre-existing)

The **Copy button in the action bar** is new — it does not exist in the current `ScriptGeneratorPanel`. It copies `generatedScript` from the store. It is **disabled** when `generatedScript === null` (i.e., before the user has clicked Generate). The draft preview's copy needs are handled by `ScriptPreview`'s existing overlay copy button. No draft-substitution logic needs to be duplicated in `ScriptConfigurePane`.

---

## Left Pane — Two Modes

### Browse Mode

Rendered when `paneMode === 'browse'`.

The page header (`h1` "Script Library" + subtitle) stays at the top of `ScriptLibraryPage` above the two-column grid, visible in both pane modes — it is not affected by this change.

The current `ScriptLibraryPage` renders `ScriptFilterBar` at the page level above the two-column grid. In this redesign, the filter bar moves **inside the left pane column** so it can be hidden in Configure mode. `inputValue`/`setInputValue` remain owned by `ScriptLibraryPage` (not inside the left pane sub-tree) so the search text survives the unmount when the pane switches to Configure mode.

Layout (top to bottom, fills left pane height):

1. **Filter bar** (`ScriptFilterBar`) — category pills + search input
2. **Template list** (`ScriptTemplateList`) — scrollable, fills remaining height

**TemplateCard changes:**
- Root element changes from `<button>` to `<div>` — the card itself is no longer clickable
- Remove active/selected visual state (`bg-primary/10 border-l-[3px]` etc.) — no card is "selected" in browse mode
- Add **"Configure →"** button, right-aligned in the bottom row
  - Style: `bg-primary/10 border border-primary/20 text-primary text-xs px-2.5 py-1 rounded-md hover:bg-primary/20 transition-colors`
  - Uses unicode `→` (not a Lucide icon)
  - On click: calls `onConfigure(template.id)` prop
  - The rest of the card (name, description, tags, complexity badge) is non-interactive

**Updated `TemplateCard` props:**
```tsx
interface Props {
  template: ScriptTemplateListItem
  onConfigure: (id: string) => void
}
```

`TemplateCard` also removes its `useScriptGeneratorStore` import entirely — it no longer reads `selectedTemplate` or `selectTemplate` from the store.

### Configure Mode

Rendered when `paneMode === 'configure'`.

The entire left pane (including filter bar) is replaced by the Configure view.

Layout (top to bottom, full pane height, `overflow-y-auto`):

1. **Back button**
   - Label: `← Back to library`
   - Style: `flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors mb-4`
   - On click: calls `onBack` prop → `ScriptLibraryPage` calls `store.clearOutput()` then `setPaneMode('browse')`
   - Does NOT clear `selectedTemplate` in the store (no such action exists — `selectTemplate` only accepts a string ID). `selectedTemplate` remains set in the store after returning to Browse mode. The right pane (`ScriptPreview`) continues showing the last preview while browsing — this is intentional.

2. **Template header** (visible when `isLoadingDetail === false`)
   - Name: `text-base font-semibold font-heading text-foreground`
   - Description (if present): `text-sm text-muted-foreground mt-0.5`
   - Tag row (left-to-right): `ShieldAlert` icon if `requires_elevation`, complexity badge, category name (resolved from `store.categories.find(c => c.id === selectedTemplate.category_id)?.name`), template tags (first 3, overflow as `+N`) — same badge/tag styles as current `TemplateCard`. If no matching category is found, omit the category name chip.
   - Separator: `border-t border-border mt-3 pt-3`

3. **`ScriptParameterForm`** — existing component, `canGenerate` prop unchanged

4. **Warnings callout** — shown above Generate when `generationWarnings.length > 0` (same amber box as current `ScriptGeneratorPanel`)

5. **Action bar**
   - **Generate** button: full-width, `bg-gradient-brand`, disabled/loading behavior same as current
   - **Download .ps1** + **Copy** buttons: side by side below Generate, each `flex-1`
     - The Copy button copies `store.generatedScript`. It is disabled when `generatedScript === null`. Draft copy is handled by `ScriptPreview`'s existing overlay — two copy entry-points is acceptable.
   - Error text below if `generateError` is set

**Loading state:** When `isLoadingDetail === true`, shows a centered `<Loader2 size={28} className="text-primary animate-spin" />` filling the pane instead of the template content.

---

## Right Pane

After the redesign, the right pane contains **only `ScriptPreview`**, wrapped in a `glass-card-static h-full` container.

Two sub-states:
- **No template ever selected** (`selectedTemplate === null`): show empty state — Terminal icon + "Select a template to get started" text. **Important:** `ScriptPreview` returns `null` when `selectedTemplate` is null, so the empty state must be rendered by the right-pane wrapper in `ScriptLibraryPage`, not by `ScriptPreview` itself. Pattern:
  ```tsx
  {selectedTemplate === null ? (
    <div className="glass-card-static h-full flex flex-col items-center justify-center gap-3 text-center p-8">
      <Terminal size={40} className="text-muted-foreground/40" />
      <p className="text-sm text-muted-foreground">Select a template to get started</p>
    </div>
  ) : (
    <div className="glass-card-static h-full overflow-hidden p-4">
      <ScriptPreview />
    </div>
  )}
  ```
- **Template selected** (in either pane mode): show `ScriptPreview`

The right pane is always read-only — no form, no Generate/Download buttons.

**Layout note:** `ScriptPreview` renders a `<div className="relative">` at its root; the copy overlay button is `position: absolute, top-3, right-3` inside it. The right-pane wrapper must **not** use `overflow-y-auto` — if it did, the absolute copy button would scroll out of view on long scripts. Instead, the wrapper is `overflow-hidden` and `ScriptPreview`'s inner `<pre>` (inside `PowerShellHighlighter`) provides its own `overflow-x-auto` for wide scripts. The right pane itself does not need to scroll vertically — `PowerShellHighlighter` already handles horizontal overflow. No changes to `ScriptPreview` are needed.

The correct right-pane wrapper pattern:
```tsx
<div className="glass-card-static h-full overflow-hidden p-4">
  <ScriptPreview />
</div>
```

**Right pane during initial detail load:** When the user clicks "Configure →" for the first time (`selectedTemplate === null`, `isLoadingDetail === true`), the right pane still shows the empty state (Terminal icon). The left pane's configure view shows the loading spinner. This is acceptable — the right pane updating when `isLoadingDetail` resolves is sufficient. No right-pane loading state is needed.

---

## State — Pane Mode

Pane mode is **local React state** in `ScriptLibraryPage`, not the Zustand store.

```tsx
const [paneMode, setPaneMode] = useState<'browse' | 'configure'>('browse')
```

Transitions:
- `'browse' → 'configure'`: `onConfigure(id)` — calls `store.selectTemplate(id)` then `setPaneMode('configure')`
- `'configure' → 'browse'`: `onBack()` — calls `store.clearOutput()` then `setPaneMode('browse')`

**`isLoadingDetail` drives configure pane content, not pane mode.** When the user clicks "Configure →":
1. `selectTemplate(id)` is called (sets `isLoadingDetail: true` in store)
2. `setPaneMode('configure')` is called immediately — user sees the loading spinner in configure mode

**`selectTemplate` failure case:** If `selectTemplate` throws (network error), the store resets `isLoadingDetail: false` but leaves `selectedTemplate` unchanged. The pane mode remains `'configure'`.

- **First-selection failure** (`selectedTemplate === null` before the call): `ScriptConfigurePane` must handle `!isLoadingDetail && !selectedTemplate` — render an error state: "Failed to load template." with a "← Back to library" link.
- **Subsequent-selection failure** (`selectedTemplate` is still set from the previous template): the configure pane silently shows the previous template's form with stale data. This is an accepted edge case — network errors mid-session are rare and the user can press Back to recover. No special handling required.

**`paramValues` and `formErrors` on Back:** `clearOutput()` does not reset `paramValues` or `formErrors`. This is intentional — if the user returns to browse mode and then clicks "Configure →" on the same template again, `selectTemplate` will repopulate `paramValues` from defaults, discarding any edits. If they configure a different template, `selectTemplate` again repopulates from that template's defaults. There is no scenario where stale param values from a prior template persist into a new template's form. No additional cleanup is needed on Back.

**Filter/search preservation:** `inputValue` remains owned by `ScriptLibraryPage` at the page level. The filter bar unmounts in configure mode and remounts in browse mode with the same `inputValue`. Store's `activeCategoryId` and `searchQuery` are never cleared by pane transitions.

---

## Component Changes

| File | Change |
|------|--------|
| `frontend/src/pages/ScriptLibraryPage.tsx` | Add `paneMode` state; add `usePermissions` import for `canGenerate`; move `ScriptFilterBar` into left pane column; add `onConfigure`/`onBack` callbacks; render `ScriptConfigurePane` or browse content in left pane conditionally; render right pane as `ScriptPreview`-only with empty state |
| `frontend/src/components/scripts/TemplateCard.tsx` | Root `<button>` → `<div>`; remove `onClick`/active-state; add `onConfigure: (id: string) => void` prop; add "Configure →" button |
| `frontend/src/components/scripts/ScriptTemplateList.tsx` | Accept `onConfigure: (id: string) => void` prop; pass to each `TemplateCard`. `inputValue: string` and `onClearSearch: () => void` props are unchanged. |
| `frontend/src/components/scripts/ScriptConfigurePane.tsx` | **New** — configure mode layout (back button, template header, `ScriptParameterForm`, warnings, action bar with Generate + Download + Copy) |
| `frontend/src/components/scripts/ScriptGeneratorPanel.tsx` | **Delete** — superseded by `ScriptConfigurePane` and right-pane simplification |

No store changes. No API changes. No routing changes.

---

## Visual Spec

### Page layout (Configure mode active)

```
┌─ left pane (320px) ────────────┬─ right pane (1fr) ──────────────────┐
│ ← Back to library              │                                      │
│                                │  [ScriptPreview — always visible]    │
│ Restart Windows Service        │                                      │
│ Stops and restarts a service   │  # Restart Windows Service           │
│ 🛡 [Beginner] [Services] [win] │  param(                              │
│ ─────────────────────────────  │    $ServiceName = "{{service_name}}" │
│ Service Name *                 │  )                 [copy overlay]    │
│ [________________]             │                                      │
│ Target Computer                │                                      │
│ [localhost______]              │                                      │
│ Verify after restart [✓]       │                                      │
│                                │                                      │
│ [    Generate Script         ] │                                      │
│ [ Download .ps1 ] [  Copy   ]  │                                      │
└────────────────────────────────┴──────────────────────────────────────┘
```

### TemplateCard — Browse mode

```
┌──────────────────────────────────────────────────────┐
│ Restart Windows Service         🛡 [Beginner]         │
│ Stops and restarts a named service                   │
│ 4× used  [services] [windows] +1     [Configure →]   │
└──────────────────────────────────────────────────────┘
```

### `ScriptConfigurePane` props

```tsx
interface Props {
  canGenerate: boolean
  onBack: () => void
}
```

All other data read from Zustand store directly. `ScriptConfigurePane` derives `canGenerate` from props only — it does NOT call `usePermissions` internally. `ScriptLibraryPage` is the sole caller of `usePermissions` for this feature.

**Download filename:** `${selectedTemplate.slug}.ps1` — consistent with the current `ScriptGeneratorPanel` behavior.
