docs: fix spec issues from round 2 review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,14 +7,27 @@
|
||||
|
||||
## 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.
|
||||
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
|
||||
- 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
|
||||
|
||||
@@ -22,62 +35,108 @@ The Script Library left pane gains two distinct modes. In **Browse mode** the us
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No changes to the right pane (`ScriptPreview`, `ScriptGeneratorPanel` output section)
|
||||
- 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 must be implemented as a new button in `ScriptConfigurePane` that copies `generatedScript` (or `displayScript` from `ScriptPreview`'s computation). Since `ScriptPreview` owns the substitution logic, the simplest approach is for the action-bar Copy to copy `generatedScript` from the store when available, and fall back to the draft preview via a shared utility or by co-locating the substitution logic. Implementation note: the action-bar Copy can duplicate `ScriptPreview`'s `handleCopy` behavior using `store.generatedScript` for the generated state; the draft substitution case is handled by `ScriptPreview`'s own overlay copy button.
|
||||
|
||||
---
|
||||
|
||||
## Left Pane — Two Modes
|
||||
|
||||
### Browse Mode
|
||||
|
||||
Rendered when `selectedTemplate === null` OR when the user has returned via "Back to library".
|
||||
Rendered when `paneMode === 'browse'`.
|
||||
|
||||
Layout (top to bottom, full pane height):
|
||||
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.
|
||||
|
||||
1. **Filter bar** (`ScriptFilterBar`) — category pills + search input, same as current
|
||||
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:**
|
||||
- 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)
|
||||
- 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`
|
||||
- On click: calls `selectTemplate(template.id)` then transitions pane to Configure mode
|
||||
- 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 `selectedTemplate !== null` (or `isLoadingDetail === true`).
|
||||
Rendered when `paneMode === 'configure'`.
|
||||
|
||||
The entire left pane (including filter bar area) is replaced by the Configure view. No filter bar visible.
|
||||
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** — top of pane
|
||||
- `← Back to library`
|
||||
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 `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
|
||||
- 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**
|
||||
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: complexity badge + category name + template tags (first 3, overflow as `+N`) — same badge styles as current `TemplateCard`
|
||||
- `ShieldAlert` elevation icon if `requires_elevation`
|
||||
- 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)
|
||||
3. **`ScriptParameterForm`** — existing component, `canGenerate` prop unchanged
|
||||
|
||||
4. **Action bar**
|
||||
- **Generate** button: full-width, `bg-gradient-brand`, same disabled/loading behavior as current
|
||||
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 in the action bar copies `generatedScript` (or the draft display if not yet generated). `ScriptPreview`'s existing copy overlay is **retained as-is** — two copy entry-points is acceptable.
|
||||
- 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.
|
||||
**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-y-auto 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -90,54 +149,68 @@ 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.
|
||||
- `'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 — 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
|
||||
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'`. If this is the first selection (`selectedTemplate === null`), the configure pane will show neither a spinner nor template content — it will render nothing. `ScriptConfigurePane` must handle this: if `!isLoadingDetail && !selectedTemplate`, render an error state ("Failed to load template. ← Back to library").
|
||||
|
||||
**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
|
||||
|
||||
### `ScriptLibraryPage.tsx` — Modified
|
||||
| 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` |
|
||||
| `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 |
|
||||
|
||||
- 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
|
||||
No store changes. No API changes. No routing changes.
|
||||
|
||||
### `TemplateCard.tsx` — Modified
|
||||
---
|
||||
|
||||
- Remove `onClick` on the card root button; change root element to `<div>`
|
||||
- Remove `isActive` / active border-l styling
|
||||
- Add "Configure →" button
|
||||
## Visual Spec
|
||||
|
||||
### `ScriptTemplateList.tsx` — Modified
|
||||
### Page layout (Configure mode active)
|
||||
|
||||
- Accept `onConfigure: (id: string) => void` prop
|
||||
- Pass it to each `TemplateCard`
|
||||
```
|
||||
┌─ 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 ] │ │
|
||||
└────────────────────────────────┴──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### `ScriptConfigurePane.tsx` — New Component
|
||||
### TemplateCard — Browse mode
|
||||
|
||||
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
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ Restart Windows Service 🛡 [Beginner] │
|
||||
│ Stops and restarts a named service │
|
||||
│ 4× used [services] [windows] +1 [Configure →] │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### `ScriptConfigurePane` props
|
||||
|
||||
Props:
|
||||
```tsx
|
||||
interface Props {
|
||||
canGenerate: boolean
|
||||
@@ -145,46 +218,4 @@ interface Props {
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
All other data read from Zustand store directly.
|
||||
|
||||
Reference in New Issue
Block a user