From e875d73c0b6311cc414a33569b360465561b8f61 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Fri, 13 Mar 2026 01:05:39 -0400 Subject: [PATCH] docs: add Script Generator Phase 2 frontend design spec Co-Authored-By: Claude Sonnet 4.6 --- ...26-03-13-script-generator-phase2-design.md | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-13-script-generator-phase2-design.md diff --git a/docs/superpowers/specs/2026-03-13-script-generator-phase2-design.md b/docs/superpowers/specs/2026-03-13-script-generator-phase2-design.md new file mode 100644 index 00000000..7f45c1b5 --- /dev/null +++ b/docs/superpowers/specs/2026-03-13-script-generator-phase2-design.md @@ -0,0 +1,281 @@ +# Script Generator Phase 2 — Frontend Design + +**Date:** 2026-03-13 +**Status:** Approved +**Phase:** 2 of Script Generator feature +**Builds on:** Phase 1 backend (`feat/script-generator` PR #105) + +--- + +## Goal + +Build the Script Library frontend: a three-panel page where MSP engineers can browse PowerShell script templates by category, fill in parameters, get a live preview, and generate + copy/download the final script. + +--- + +## Architecture + +**Layout:** Top filter bar + two-column layout (C). Category tabs and search across the top, template list on the left, generator panel on the right. Follows the existing glassmorphism design system. + +**State:** Zustand store (`scriptGeneratorStore`) chosen over a local hook because script generation will soon be embeddable in session execution (Script Output Node). A store allows any component anywhere in the tree to read/write generation state without prop drilling. Survives navigation. + +**Preview:** Client-side lightweight substitution for live preview as the user types (`{{variable_name}}` replacement only). The real `POST /scripts/generate` endpoint is called on Generate — it applies filters (`as_secure_string`, `as_array`, `as_bool`), conditionals, and PowerShell-safe sanitization. + +--- + +## Zustand Store — `scriptGeneratorStore` + +**File:** `frontend/src/store/scriptGeneratorStore.ts` + +### State shape + +```typescript +interface ScriptGeneratorState { + // Template browsing + categories: ScriptCategoryResponse[]; + templates: ScriptTemplateListItem[]; + selectedTemplate: ScriptTemplateDetail | null; + searchQuery: string; + activeCategoryId: string | null; + isLoadingTemplates: boolean; + + // Form + paramValues: Record; // keyed by variable_name + formErrors: Record; // keyed by variable_name + + // Output + generatedScript: string | null; + generationId: string | null; + isGenerating: boolean; + generateError: string | null; + + // Actions + loadCategories: () => Promise; + loadTemplates: () => Promise; + selectTemplate: (id: string) => Promise; + setCategory: (id: string | null) => void; + setSearch: (query: string) => void; + setParamValue: (variableName: string, value: string) => void; + generate: (sessionId?: string) => Promise; + clearOutput: () => void; + reset: () => void; +} +``` + +### Behaviour notes + +- `setCategory` and `setSearch` both call `loadTemplates()` after updating state — they compose (category + search both applied to the API call) +- `selectTemplate` fetches the full `ScriptTemplateDetail` (including `parameters_schema` and `script_template`), then calls `reset()` to clear previous form/output state +- `reset()` clears `paramValues`, `formErrors`, `generatedScript`, `generationId`, `generateError` — does not clear template selection or browsing state +- `generate()` validates required params client-side first (populates `formErrors`), then calls `POST /scripts/generate` + +--- + +## Types + +Added to `frontend/src/types/index.ts`: + +```typescript +export interface ScriptCategoryResponse { + id: string; + name: string; + slug: string; + description: string | null; + template_count: number; +} + +export interface ScriptTemplateListItem { + id: string; + category_id: string; + name: string; + description: string | null; + script_complexity: 'simple' | 'moderate' | 'complex'; + usage_count: number; + tags: string[]; +} + +export interface ScriptParameter { + variable_name: string; + label: string; + field_type: 'text' | 'password' | 'select' | 'multiselect' | 'checkbox' | 'number'; + required: boolean; + default_value?: string; + options?: string[]; + is_sensitive: boolean; + display_order: number; + help_text?: string; +} + +export interface ScriptParametersSchema { + parameters: ScriptParameter[]; +} + +export interface ScriptTemplateDetail extends ScriptTemplateListItem { + parameters_schema: ScriptParametersSchema; + script_template: string; +} + +export interface ScriptGenerateRequest { + template_id: string; + parameters: Record; + session_id?: string; +} + +export interface ScriptGenerateResponse { + generation_id: string; + generated_script: string; + template_name: string; +} +``` + +--- + +## API Client + +**File:** `frontend/src/api/scripts.ts` + +```typescript +getCategories(): Promise +getTemplates(params?: { category_id?: string; search?: string }): Promise +getTemplateDetail(id: string): Promise +generate(req: ScriptGenerateRequest): Promise +getGenerations(): Promise +``` + +All methods use the existing `apiClient` (base URL `/api/v1`, auth interceptor handles token refresh). + +Exported from `frontend/src/api/index.ts` as `scriptsApi`. + +--- + +## Component Tree + +``` +ScriptLibraryPage pages/ScriptLibraryPage.tsx +├── ScriptFilterBar components/scripts/ScriptFilterBar.tsx +├── ScriptTemplateList components/scripts/ScriptTemplateList.tsx +│ └── TemplateCard components/scripts/TemplateCard.tsx +└── ScriptGeneratorPanel components/scripts/ScriptGeneratorPanel.tsx + ├── ScriptParameterForm components/scripts/ScriptParameterForm.tsx + │ └── ScriptParameterField components/scripts/ScriptParameterField.tsx + └── ScriptPreview components/scripts/ScriptPreview.tsx + └── PowerShellHighlighter components/scripts/PowerShellHighlighter.tsx +``` + +### Component responsibilities + +**`ScriptLibraryPage`** +- Bootstraps store on mount: calls `loadCategories()` then `loadTemplates()` +- Renders two-column layout (template list left, generator panel right) +- Renders `ScriptFilterBar` above the two columns +- No business logic — pure layout + bootstrap + +**`ScriptFilterBar`** +- Category tabs (pill style, `bg-primary/10` + cyan accent on active) +- Search input (debounced 300ms) — searches names, descriptions, and tags (backend handles it) +- Reads `categories`, `activeCategoryId`, `searchQuery` from store +- Calls `setCategory` and `setSearch` + +**`ScriptTemplateList`** +- Scrollable list of `TemplateCard` components +- Reads `templates`, `isLoadingTemplates`, `selectedTemplate` from store +- Shows skeleton loaders while loading +- Shows empty state when no templates match + +**`TemplateCard`** +- Displays: name, description (truncated to 2 lines), complexity badge, usage count, tags +- Active state: `bg-primary/10` background + 3px left cyan accent bar (matches existing nav active pattern) +- Calls `selectTemplate(template.id)` on click +- Complexity badge colors: simple → emerald-400, moderate → amber-400, complex → rose-500 + +**`ScriptGeneratorPanel`** +- Shows placeholder ("Select a template to get started") when `selectedTemplate` is null +- When template selected: renders template name/description header, `ScriptParameterForm`, `ScriptPreview`, and action bar +- Action bar: Generate button (`bg-gradient-brand`) + Download `.ps1` button +- Download triggers `new Blob([generatedScript], { type: 'text/plain' })` → anchor click +- Shows `generateError` inline below Generate button +- Viewers see Generate button disabled with tooltip "Engineer access required" (checked via `usePermissions()`) + +**`ScriptParameterForm`** +- Iterates `selectedTemplate.parameters_schema.parameters` sorted by `display_order` +- Renders a `ScriptParameterField` per parameter +- Client-side required validation on Generate (marks `formErrors` in store) +- Disabled entirely for viewers + +**`ScriptParameterField`** +- Renders input by `field_type`: text → ``, password → ``, select → ``, number → `` +- Shows `help_text` as a small muted line below the field +- Shows `formErrors[variable_name]` as an inline error +- Password fields show a show/hide toggle +- Calls `setParamValue(variable_name, value)` on change + +**`ScriptPreview`** +- Two modes: + - **Draft mode** (before Generate): client-side substitution of `{{variable_name}}` in `selectedTemplate.script_template` using current `paramValues`. Unfilled params render as `{{variable_name}}` placeholders (visually dimmed). + - **Generated mode** (after Generate): shows `generatedScript` from store +- Copy icon (Lucide `Copy`) in top-right corner of code block — calls `navigator.clipboard.writeText()`; shows a brief "Copied!" tooltip on success +- Passes script string to `PowerShellHighlighter` + +**`PowerShellHighlighter`** +- Pure component: `({ script: string }) => JSX.Element` +- Regex-based syntax highlighting (no external library): + - Comments (`#...`) → `text-[#8b949e]` + - Cmdlets (`Verb-Noun` pattern) → `text-[#22d3ee]` (cyan) + - String literals (`"..."`, `'...'`) → `text-[#a5d6ff]` + - Variables (`$VarName`) → `text-[#79c0ff]` + - Parameters (`-ParamName`) → `text-[#d2a8ff]` (purple) + - Keywords (`if`, `foreach`, `function`, etc.) → `text-[#ff7b72]` + - Unfilled placeholders (`{{variable_name}}`) → `text-amber-400` with dashed underline +- Renders as `
` with `font-label` (JetBrains Mono), `bg-card`, rounded corners
+
+---
+
+## Routing & Navigation
+
+- Route: `/scripts` added to `frontend/src/router.tsx` inside the `ProtectedRoute`/`AppLayout` children
+- Sidebar nav entry: "Scripts" with a `Terminal` icon (Lucide), grouped under the main nav
+- No sub-routes needed for Phase 2
+
+---
+
+## Permissions
+
+| Action | Minimum role |
+|--------|-------------|
+| View Script Library page | Any authenticated user |
+| Browse templates, see preview | Any authenticated user |
+| Fill form, generate, copy, download | Engineer or above |
+
+Implemented via `usePermissions()` hook. Viewer-blocking applied to: `ScriptParameterForm` (disabled), Generate button (disabled + tooltip), Download button (disabled + tooltip).
+
+---
+
+## Search Behaviour
+
+- Search query sent as `?search=` param to `GET /scripts/templates`
+- Backend searches name, description, and tags (already implemented in Phase 1)
+- Debounced 300ms in `ScriptFilterBar` before calling `setSearch`
+- Category filter and search compose: both params sent simultaneously
+
+---
+
+## Empty & Loading States
+
+| Scenario | Treatment |
+|----------|-----------|
+| Templates loading | Skeleton cards (3 placeholder TemplateCards) |
+| No templates in category | Illustration + "No templates found" message |
+| No search results | "No templates match your search" with clear button |
+| No template selected | Right panel: centered placeholder with Terminal icon + "Select a template to get started" |
+| Generating | Generate button shows spinner, disabled |
+| Generate error | Inline error text below Generate button in rose-500 |
+
+---
+
+## Out of Scope (Phase 2)
+
+- Session-embedded script generation (Script Output Node) — Phase 3
+- Template creation/editing UI — admin-only, deferred
+- Generation history page — deferred
+- Admin template management — deferred
+- Script execution / RMM integration — long-term roadmap