docs: add script library pane takeover design spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-13 11:49:14 -04:00
parent 844b792bc2
commit e7a3ed6982

View File

@@ -0,0 +1,190 @@
# 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 to **Configure mode**, which replaces the list with a full-height view of the selected template's header, parameter form, and action buttons. The right pane (preview/output) is always visible. "Back to library" returns to Browse mode with filter/search state preserved.
---
## Goals
- Make template selection intentional (no accidental click-to-configure)
- Give the parameter form more vertical space
- Keep the output preview always visible on the right
- Preserve filter/search state across Browse ↔ Configure transitions
---
## Non-Goals
- No changes to the right pane (`ScriptPreview`, `ScriptGeneratorPanel` output section)
- No changes to the Zustand store shape or actions
- No changes to the filter/search debounce logic
- No changes to routing
---
## Left Pane — Two Modes
### Browse Mode
Rendered when `selectedTemplate === null` OR when the user has returned via "Back to library".
Layout (top to bottom, full pane height):
1. **Filter bar** (`ScriptFilterBar`) — category pills + search input, same as current
2. **Template list** (`ScriptTemplateList`) — scrollable, fills remaining height
**TemplateCard changes:**
- Remove the full-card `onClick` that calls `selectTemplate()`
- Remove the active/selected visual state (`bg-primary/10 border-l-[3px]` etc.) — cards are never "selected" in browse mode, they are only "configured"
- Add a **"Configure →"** button to each card (right-aligned, replaces the old click-anywhere behavior)
- 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`
- On click: calls `selectTemplate(template.id)` then transitions pane to Configure mode
- The rest of the card (name, description, tags, complexity badge) is non-interactive
### Configure Mode
Rendered when `selectedTemplate !== null` (or `isLoadingDetail === true`).
The entire left pane (including filter bar area) is replaced by the Configure view. No filter bar visible.
Layout (top to bottom, full pane height, `overflow-y-auto`):
1. **Back button** — top of pane
- `← 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 `store.clearOutput()` then sets pane mode back to Browse (see State section)
- Does NOT call `selectTemplate(null)` or clear `selectedTemplate` in the store — the store selection is preserved; only the pane view changes
2. **Template header**
- Name: `text-base font-semibold font-heading text-foreground`
- Description (if present): `text-sm text-muted-foreground mt-0.5`
- Tag row: complexity badge + category name + template tags (first 3, overflow as `+N`) — same badge styles as current `TemplateCard`
- `ShieldAlert` elevation icon if `requires_elevation`
- Separator: `border-t border-border mt-3 pt-3`
3. **`ScriptParameterForm`** (existing component, `canGenerate` prop unchanged)
4. **Action bar**
- **Generate** button: full-width, `bg-gradient-brand`, same disabled/loading behavior as current
- **Download .ps1** + **Copy** buttons: side by side below Generate, each `flex-1`
- Error text below if `generateError` is set
- Warnings callout above Generate if `generationWarnings.length > 0` (same amber box as current)
**Loading state:** When `isLoadingDetail === true`, the Configure pane shows a centered `<Loader2 size={28} className="text-primary animate-spin" />` instead of the template content. Same as current panel behavior.
---
## 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'`: triggered by "Configure →" button click (after `selectTemplate()` call)
- `'configure' → 'browse'`: triggered by "Back to library" button click
**Filter/search preservation:** Filter bar state (`inputValue`, `setInputValue`, store's `activeCategoryId`, `searchQuery`) is untouched by pane mode transitions. The filter bar simply unmounts in configure mode and remounts with the same state in browse mode.
**`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 — the user sees the loading spinner in configure mode, not a flash of browse mode
3. When `isLoadingDetail` becomes false, the full configure view renders
---
## Component Changes
### `ScriptLibraryPage.tsx` — Modified
- Add `paneMode` state (`'browse' | 'configure'`)
- Pass `onConfigure` callback to `ScriptTemplateList` (called with `template.id` on "Configure →" click)
- `onConfigure` calls `selectTemplate(id)` then `setPaneMode('configure')`
- Pass `onBack` callback to the configure pane
- `onBack` calls `clearOutput()` then `setPaneMode('browse')`
- Render left pane conditionally:
- `paneMode === 'browse'`: `ScriptFilterBar` + `ScriptTemplateList`
- `paneMode === 'configure'`: new `ScriptConfigurePane` component
### `TemplateCard.tsx` — Modified
- Remove `onClick` on the card root button; change root element to `<div>`
- Remove `isActive` / active border-l styling
- Add "Configure →" button
### `ScriptTemplateList.tsx` — Modified
- Accept `onConfigure: (id: string) => void` prop
- Pass it to each `TemplateCard`
### `ScriptConfigurePane.tsx` — New Component
Encapsulates the configure mode layout:
- Back button → calls `onBack` prop
- Template header (name, description, tags, complexity, elevation)
- Loading spinner when `isLoadingDetail`
- `ScriptParameterForm` (with `canGenerate`)
- Warnings callout
- Action bar: Generate (full-width), Download .ps1 + Copy (side-by-side below)
- Error text
Props:
```tsx
interface Props {
canGenerate: boolean
onBack: () => void
}
```
All data read directly from Zustand store (`selectedTemplate`, `isLoadingDetail`, `generatedScript`, etc.).
### `ScriptGeneratorPanel.tsx` — Removed (or repurposed)
The current `ScriptGeneratorPanel` is superseded by `ScriptConfigurePane`. It should be deleted; its logic is absorbed into `ScriptConfigurePane`.
---
## Visual Spec
### Configure Pane — action bar
```
┌─────────────────────────────────┐
│ [Generate Script] │ ← full-width, bg-gradient-brand
│ [Download .ps1] [Copy] │ ← side-by-side, flex-1 each
│ <error text if any> │
└─────────────────────────────────┘
```
### "Configure →" button on TemplateCard
```
┌──────────────────────────────────────────────────┐
│ Restart Windows Service [Beginner] 🛡 │
│ Stops and restarts a named service │
│ services windows +1 [Configure →]│
└──────────────────────────────────────────────────┘
```
---
## Affected Files
| File | Change |
|------|--------|
| `frontend/src/pages/ScriptLibraryPage.tsx` | Add `paneMode` state, `onConfigure`/`onBack` callbacks, conditional left-pane render |
| `frontend/src/components/scripts/TemplateCard.tsx` | Remove full-card click, add "Configure →" button |
| `frontend/src/components/scripts/ScriptTemplateList.tsx` | Accept + thread `onConfigure` prop |
| `frontend/src/components/scripts/ScriptConfigurePane.tsx` | New — configure mode layout |
| `frontend/src/components/scripts/ScriptGeneratorPanel.tsx` | Delete — superseded by `ScriptConfigurePane` |
No store changes. No API changes. No routing changes.