From 405bf477b4924500d07677f92459b9c124592328 Mon Sep 17 00:00:00 2001 From: Kawa Date: Fri, 27 Mar 2026 10:23:14 +0100 Subject: [PATCH] docs(04-review-download-security): create phase 4 plan 5 plans across 5 waves: Wave 0 TDD stubs, utilities + OutputBlock, ReviewStep implementation, App.tsx wiring, human verify checkpoint. Co-Authored-By: Claude Sonnet 4.6 --- .planning/ROADMAP.md | 11 +- .../04-review-download-security/04-01-PLAN.md | 207 +++++++++++++ .../04-review-download-security/04-02-PLAN.md | 243 ++++++++++++++++ .../04-review-download-security/04-03-PLAN.md | 272 ++++++++++++++++++ .../04-review-download-security/04-04-PLAN.md | 129 +++++++++ .../04-review-download-security/04-05-PLAN.md | 116 ++++++++ 6 files changed, 976 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/04-review-download-security/04-01-PLAN.md create mode 100644 .planning/phases/04-review-download-security/04-02-PLAN.md create mode 100644 .planning/phases/04-review-download-security/04-03-PLAN.md create mode 100644 .planning/phases/04-review-download-security/04-04-PLAN.md create mode 100644 .planning/phases/04-review-download-security/04-05-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index d1ceb72..9f3ba7f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -76,7 +76,14 @@ Plans: 3. The app prominently displays that no data is sent to any server, and wizard state is never written to localStorage or sessionStorage 4. User can download each artifact individually (rclone.conf, Intune install script, Intune detection script, RMM script) or all together as a ZIP bundle 5. User can copy any output block to clipboard without downloading -**Plans**: TBD +**Plans**: 5 plans + +Plans: +- [ ] 04-01-PLAN.md — Wave 0 TDD stubs: ReviewStep.test.tsx (all requirement stubs RED) + SECU-03 assertion in reducer.test.ts +- [ ] 04-02-PLAN.md — Utilities and OutputBlock: downloadFile.ts, downloadZip.ts (install jszip), OutputBlock.tsx component +- [ ] 04-03-PLAN.md — Implement ReviewStep.tsx: live preview, security gate, all downloads and clipboard — all stubs turn GREEN +- [ ] 04-04-PLAN.md — Wire ReviewStep into App.tsx (step 3) and add 'Review' to StepIndicator STEP_LABELS +- [ ] 04-05-PLAN.md — Human verification: full 4-step wizard end-to-end, all downloads, ZIP, clipboard, SECU-03 DevTools check ## Progress @@ -88,4 +95,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 | 1. Foundation | 4/4 | Complete | 2026-03-26 | | 2. Generators | 4/4 | Complete | 2026-03-26 | | 3. Wizard UI | 5/5 | Complete | 2026-03-27 | -| 4. Review, Download & Security | 0/? | Not started | - | +| 4. Review, Download & Security | 0/5 | Not started | - | diff --git a/.planning/phases/04-review-download-security/04-01-PLAN.md b/.planning/phases/04-review-download-security/04-01-PLAN.md new file mode 100644 index 0000000..0e2eef9 --- /dev/null +++ b/.planning/phases/04-review-download-security/04-01-PLAN.md @@ -0,0 +1,207 @@ +--- +phase: 04-review-download-security +plan: 01 +type: tdd +wave: 1 +depends_on: [] +files_modified: + - src/components/wizard/ReviewStep.test.tsx + - src/store/reducer.test.ts +autonomous: true +requirements: [CONF-02, CONF-03, DOWN-01, DOWN-02, DOWN-03, DOWN-04, DOWN-05, DOWN-06, SECU-01, SECU-02, SECU-03] + +must_haves: + truths: + - "All ReviewStep requirement behaviors have named, failing test stubs (RED)" + - "SECU-03 is verified by a spy on Storage.prototype.setItem in reducer.test.ts" + - "Mock setup for URL.createObjectURL, URL.revokeObjectURL, navigator.clipboard.writeText exists in test file" + - "npm test runs without crashes (passWithNoTests: true covers missing implementation)" + artifacts: + - path: "src/components/wizard/ReviewStep.test.tsx" + provides: "RED test stubs for CONF-02, CONF-03, DOWN-01–DOWN-06, SECU-01, SECU-02" + exports: [] + - path: "src/store/reducer.test.ts" + provides: "SECU-03 assertion added to existing reducer test suite" + contains: "spyOn(Storage.prototype, 'setItem')" + key_links: + - from: "src/components/wizard/ReviewStep.test.tsx" + to: "src/components/wizard/ReviewStep" + via: "import ReviewStep" + pattern: "import.*ReviewStep" + - from: "src/components/wizard/ReviewStep.test.tsx" + to: "src/utils/downloadFile" + via: "import downloadFile (mocked)" + pattern: "vi.mock.*downloadFile" +--- + + +Write Wave 0 TDD stubs: all failing test cases for Phase 4 requirements, plus SECU-03 assertion in existing reducer tests. + +Purpose: Establish the RED baseline before any implementation. Tests define the contract that implementation plans must satisfy. Following the established project pattern from Phases 1–3. +Output: ReviewStep.test.tsx (stub file, RED tests for all 11 req IDs) + SECU-03 assertion in reducer.test.ts. + + + +@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/04-review-download-security/04-RESEARCH.md +@.planning/phases/04-review-download-security/04-VALIDATION.md + +@src/store/types.ts +@src/store/context.tsx +@src/store/reducer.test.ts +@src/generators/index.ts + + + + + +From src/store/types.ts: +```typescript +export interface WizardState { + currentStep: number; + remote: { + name: string; + backendType: BackendType | null; + params: Record; + }; + deployment: { + includeInstall: boolean; + configPath: 'machine-wide' | 'user-profile'; + scriptTargets: ('intune' | 'rmm')[]; + }; +} +``` + +From src/store/context.tsx: +```typescript +export function useWizard(): { state: WizardState; dispatch: React.Dispatch } +export function WizardProvider({ children }: { children: React.ReactNode }): JSX.Element +``` + +From src/generators/index.ts: +```typescript +export { buildRcloneConf } from './rclone-conf'; +export { buildIntuneInstall } from './intune-install'; +export { buildIntuneDetection } from './intune-detection'; +export { buildRmmScript } from './rmm-script'; +// All accept (state: WizardState): string +// buildRcloneConf throws when backendType is null or name is empty +``` + + + + + + Task 1: Write ReviewStep.test.tsx with RED stubs for all Phase 4 requirements + src/components/wizard/ReviewStep.test.tsx + + - CONF-02: rclone.conf preview text is present in rendered output when state has a valid backendType + - CONF-03: clicking "Copy" on the rclone.conf block calls navigator.clipboard.writeText with the conf content + - DOWN-01: clicking "Download rclone.conf" calls downloadFile with content and filename 'rclone.conf' + - DOWN-02: clicking "Download Intune Install" calls downloadFile with content and filename 'intune-install.ps1' + - DOWN-03: clicking "Download Intune Detection" calls downloadFile with content and filename 'intune-detection.ps1' + - DOWN-04: clicking "Download RMM script" calls downloadFile with content and filename 'rmm-script.ps1' + - DOWN-05: clicking "Download ZIP" calls downloadZip with all 4 file entries + - DOWN-06: each output block has a copy button that calls clipboard.writeText with that block's content + - SECU-01: download buttons are disabled when security checkbox is unchecked; enabled after checking + - SECU-02: rendered output contains text about no data being sent to a server + + + Create src/components/wizard/ReviewStep.test.tsx. Follow the established project pattern: + - Use `expect.fail('not yet implemented')` for each stub (named RED failure, not import-error RED) + - Import `{ describe, it, expect, vi, beforeEach }` from 'vitest' + - Import `{ render, screen, fireEvent }` from '@testing-library/react' + - Import `ReviewStep` from './ReviewStep' (will not exist yet — that is fine; tests will fail at import or at stub) + - Import `* as downloadFileModule` from '../../utils/downloadFile' for mocking + - Import `* as downloadZipModule` from '../../utils/downloadZip' for mocking + + File-level mock setup (before describe block): + ```typescript + vi.mock('../../utils/downloadFile', () => ({ downloadFile: vi.fn() })); + vi.mock('../../utils/downloadZip', () => ({ downloadZip: vi.fn() })); + ``` + + In a beforeEach, stub global APIs: + ```typescript + beforeEach(() => { + vi.stubGlobal('navigator', { + clipboard: { writeText: vi.fn().mockResolvedValue(undefined) }, + }); + vi.stubGlobal('URL', { + createObjectURL: vi.fn(() => 'blob:mock'), + revokeObjectURL: vi.fn(), + }); + vi.clearAllMocks(); + }); + ``` + + Helper: create a WizardProvider wrapper with a fully populated state (azureblob backend, name='my-remote', params={account:'acct',key:'k'}, deployment defaults) for rendering ReviewStep in tests. + + Write one `describe` block per requirement ID (embed ID in describe name for traceability, e.g. `describe('CONF-02: live rclone.conf preview', ...)`). Each describe contains an `it` that calls `expect.fail('not yet implemented')`. + + Do NOT write real assertions yet — the whole file should be stubs that fail with 'not yet implemented' (except the import/mock wiring which must work). + + Run: `npm test -- src/components/wizard/ReviewStep.test.tsx` — expect FAIL (RED) because ReviewStep does not exist yet. That is the correct Wave 0 state. + + + npm test -- src/components/wizard/ReviewStep.test.tsx 2>&1 | tail -20 + + ReviewStep.test.tsx exists with named stubs for all 10 requirement behaviors (CONF-02, CONF-03, DOWN-01 through DOWN-06, SECU-01, SECU-02). npm test fails with import or stub errors for ReviewStep only — NOT with TypeScript compilation errors in the test file itself. + + + + Task 2: Add SECU-03 assertion to reducer.test.ts + src/store/reducer.test.ts + + - SECU-03: localStorage.setItem and sessionStorage.setItem are never called during any wizard reducer dispatch + + + Edit src/store/reducer.test.ts. Add a new `it` block at the end of the existing `describe('wizardReducer', ...)` block: + + ```typescript + it('SECU-03: never writes to localStorage or sessionStorage', () => { + const setItemSpy = vi.spyOn(Storage.prototype, 'setItem'); + // Dispatch every action type to cover all reducer branches + wizardReducer(INITIAL_STATE, { type: 'SET_STEP', payload: 1 }); + wizardReducer(INITIAL_STATE, { type: 'SET_BACKEND_TYPE', payload: 'azureblob' }); + wizardReducer(INITIAL_STATE, { type: 'SET_REMOTE_NAME', payload: 'test' }); + wizardReducer(INITIAL_STATE, { type: 'SET_REMOTE_PARAMS', payload: { key: 'val' } }); + wizardReducer(INITIAL_STATE, { type: 'SET_DEPLOYMENT', payload: { includeInstall: true } }); + wizardReducer(INITIAL_STATE, { type: 'RESET' }); + expect(setItemSpy).not.toHaveBeenCalled(); + setItemSpy.mockRestore(); + }); + ``` + + Also add `vi` to the import line: change `import { describe, it, expect }` to `import { describe, it, expect, vi }`. + + Run: `npm test -- src/store/reducer.test.ts` — expect GREEN (reducer already does not touch storage). + + + npm test -- src/store/reducer.test.ts 2>&1 | tail -10 + + reducer.test.ts has the SECU-03 test and it passes GREEN. All pre-existing reducer tests still pass. + + + + + +After both tasks: `npm test` runs without TypeScript errors in reviewed files. reducer.test.ts is fully GREEN. ReviewStep.test.tsx fails with stub or module-not-found errors (RED — expected). + + + +- src/components/wizard/ReviewStep.test.tsx exists with 10 named describe/it blocks (one per requirement behavior) +- src/store/reducer.test.ts has SECU-03 assertion and passes fully GREEN +- No new TypeScript compilation errors anywhere in the test files +- Wave 0 baseline established: RED for ReviewStep, GREEN for SECU-03 + + + +After completion, create `.planning/phases/04-review-download-security/04-01-SUMMARY.md` + diff --git a/.planning/phases/04-review-download-security/04-02-PLAN.md b/.planning/phases/04-review-download-security/04-02-PLAN.md new file mode 100644 index 0000000..9d682ef --- /dev/null +++ b/.planning/phases/04-review-download-security/04-02-PLAN.md @@ -0,0 +1,243 @@ +--- +phase: 04-review-download-security +plan: 02 +type: execute +wave: 2 +depends_on: [04-01] +files_modified: + - src/utils/downloadFile.ts + - src/utils/downloadZip.ts + - src/components/wizard/OutputBlock.tsx +autonomous: true +requirements: [DOWN-01, DOWN-02, DOWN-03, DOWN-04, DOWN-05, DOWN-06, CONF-03] + +must_haves: + truths: + - "downloadFile(content, filename) creates a Blob and triggers an anchor click download" + - "downloadZip(files, zipName) builds a JSZip archive and triggers a download" + - "OutputBlock renders pre-formatted content, a copy button, and an optional download button" + - "OutputBlock copy button calls clipboard.writeText with block content" + artifacts: + - path: "src/utils/downloadFile.ts" + provides: "Pure download utility — Blob + createObjectURL + anchor click" + exports: ["downloadFile"] + - path: "src/utils/downloadZip.ts" + provides: "ZIP bundle download utility using JSZip" + exports: ["downloadZip"] + - path: "src/components/wizard/OutputBlock.tsx" + provides: "Reusable code block with copy and optional download buttons" + exports: ["OutputBlock", "OutputBlockProps"] + key_links: + - from: "src/components/wizard/OutputBlock.tsx" + to: "src/utils/downloadFile" + via: "import { downloadFile }" + pattern: "downloadFile" + - from: "src/utils/downloadZip.ts" + to: "jszip" + via: "import JSZip from 'jszip'" + pattern: "JSZip" +--- + + +Create the three utility/component building blocks that ReviewStep will compose: the file download helper, the ZIP bundle helper (installs jszip), and the reusable OutputBlock component. + +Purpose: These are pure, independently testable pieces. Isolating them here keeps ReviewStep.tsx focused on layout and state wiring, not download mechanics. +Output: src/utils/downloadFile.ts, src/utils/downloadZip.ts, src/components/wizard/OutputBlock.tsx + + + +@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md + + + +@.planning/phases/04-review-download-security/04-RESEARCH.md +@src/store/types.ts +@src/components/wizard/ReviewStep.test.tsx + + + + + +Expected by ReviewStep.test.tsx: +```typescript +// src/utils/downloadFile.ts +export function downloadFile(content: string, filename: string): void + +// src/utils/downloadZip.ts +export async function downloadZip( + files: { name: string; content: string }[], + zipName: string +): Promise + +// src/components/wizard/OutputBlock.tsx +export interface OutputBlockProps { + label: string; + content: string; + filename?: string; // if provided, show download button + disabled?: boolean; // gates both copy and download (security gate) +} +export function OutputBlock(props: OutputBlockProps): JSX.Element +``` + + + + + + Task 1: Create downloadFile.ts and downloadZip.ts utilities + src/utils/downloadFile.ts, src/utils/downloadZip.ts + + - downloadFile creates a Blob with text/plain;charset=utf-8 MIME type, creates an object URL, clicks a hidden anchor, then revokes the URL immediately after click + - downloadZip accepts an array of { name, content } entries, adds each to a JSZip instance, generates a blob asynchronously, then triggers download using the same Blob/anchor pattern + - Both utilities have no React dependency (pure browser functions) + + + First, install jszip (the only new runtime dependency this phase): + ```bash + npm install jszip + ``` + JSZip ships its own TypeScript declarations. Do NOT install @types/jszip (stale, 6 years old). + + Create src/utils/downloadFile.ts: + ```typescript + // src/utils/downloadFile.ts + // Triggers a browser file download from a string. + // Pattern: MDN URL.createObjectURL — https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static + export function downloadFile(content: string, filename: string): void { + const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); + } + ``` + + Create src/utils/downloadZip.ts: + ```typescript + // src/utils/downloadZip.ts + // Builds a ZIP archive from file entries and triggers download. + // Pattern: JSZip official docs — https://stuk.github.io/jszip/documentation/examples/download-zip-file.html + import JSZip from 'jszip'; + + export async function downloadZip( + files: { name: string; content: string }[], + zipName: string + ): Promise { + const zip = new JSZip(); + for (const { name, content } of files) { + zip.file(name, content); + } + const blob = await zip.generateAsync({ type: 'blob' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = zipName; + a.click(); + URL.revokeObjectURL(url); + } + ``` + + Do NOT create a src/utils/index.ts barrel — ReviewStep will import directly from the specific utility files to keep the mock paths in tests predictable (the test file already uses '../../utils/downloadFile' and '../../utils/downloadZip'). + + + npm run build 2>&1 | tail -10 + + Both utility files exist and TypeScript compilation succeeds with no errors. jszip appears in package.json dependencies. + + + + Task 2: Create OutputBlock.tsx reusable component + src/components/wizard/OutputBlock.tsx + + - OutputBlock renders a labelled section with a pre/code block showing the content + - A "Copy" button calls navigator.clipboard.writeText(content); button label changes to "Copied!" for 2 seconds then reverts + - When filename prop is provided, a "Download" button calls downloadFile(content, filename) + - When disabled prop is true, both Copy and Download buttons are disabled (HTML disabled attribute) — this is the SECU-01 gate applied per-block + - Copy and Download buttons are always rendered (never conditionally hidden) — disabled state is visual only + + + Create src/components/wizard/OutputBlock.tsx: + + ```typescript + // src/components/wizard/OutputBlock.tsx + // Reusable output block: pre-formatted content + copy button + optional download button. + // disabled prop gates both actions (SECU-01 security acknowledgement gate). + import { useState } from 'react'; + import { downloadFile } from '../../utils/downloadFile'; + + export interface OutputBlockProps { + label: string; + content: string; + filename?: string; + disabled?: boolean; + } + + export function OutputBlock({ label, content, filename, disabled = false }: OutputBlockProps) { + const [copied, setCopied] = useState(false); + + async function handleCopy() { + await navigator.clipboard.writeText(content); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + return ( +
+
+ {label} +
+ + {filename && ( + + )} +
+
+
+            {content}
+          
+
+ ); + } + ``` + + Styling uses Tailwind classes consistent with the existing wizard components. The `disabled:opacity-40` class communicates the locked state visually without hiding buttons. +
+ + npm test 2>&1 | tail -15 + + OutputBlock.tsx exists and compiles cleanly. npm test still shows ReviewStep.test.tsx as RED (component does not exist yet) and all other tests GREEN. No new TypeScript errors. +
+ +
+ + +`npm run build` passes. `npm test` shows: reducer.test.ts GREEN, ReviewStep.test.tsx still RED (ReviewStep not yet created — expected), all prior phase tests GREEN. + + + +- src/utils/downloadFile.ts exports downloadFile function +- src/utils/downloadZip.ts exports downloadZip async function +- src/components/wizard/OutputBlock.tsx exports OutputBlock and OutputBlockProps +- jszip listed in package.json dependencies +- TypeScript compilation clean + + + +After completion, create `.planning/phases/04-review-download-security/04-02-SUMMARY.md` + diff --git a/.planning/phases/04-review-download-security/04-03-PLAN.md b/.planning/phases/04-review-download-security/04-03-PLAN.md new file mode 100644 index 0000000..df35302 --- /dev/null +++ b/.planning/phases/04-review-download-security/04-03-PLAN.md @@ -0,0 +1,272 @@ +--- +phase: 04-review-download-security +plan: 03 +type: tdd +wave: 3 +depends_on: [04-02] +files_modified: + - src/components/wizard/ReviewStep.tsx +autonomous: true +requirements: [CONF-02, CONF-03, DOWN-01, DOWN-02, DOWN-03, DOWN-04, DOWN-05, DOWN-06, SECU-01, SECU-02] + +must_haves: + truths: + - "ReviewStep renders a live preview of the generated rclone.conf content" + - "A security warning checkbox must be checked before any download or copy button is enabled" + - "A 'no data sent to server' notice is visible in the ReviewStep render output" + - "OutputBlocks for all four generated files are shown (rclone.conf, intune-install.ps1, intune-detection.ps1, rmm-script.ps1)" + - "A 'Download ZIP' button calls downloadZip with all 4 file entries" + - "All test stubs from Plan 01 pass GREEN after this plan" + artifacts: + - path: "src/components/wizard/ReviewStep.tsx" + provides: "Step 3 component — live preview + security gate + all download/copy actions" + exports: ["ReviewStep"] + min_lines: 60 + key_links: + - from: "src/components/wizard/ReviewStep.tsx" + to: "src/generators/index.ts" + via: "import { buildRcloneConf, buildIntuneInstall, buildIntuneDetection, buildRmmScript }" + pattern: "buildRcloneConf" + - from: "src/components/wizard/ReviewStep.tsx" + to: "src/store/context.tsx" + via: "import { useWizard }" + pattern: "useWizard" + - from: "src/components/wizard/ReviewStep.tsx" + to: "src/components/wizard/OutputBlock.tsx" + via: "import { OutputBlock }" + pattern: "OutputBlock" + - from: "src/components/wizard/ReviewStep.tsx" + to: "src/utils/downloadZip.ts" + via: "import { downloadZip }" + pattern: "downloadZip" +--- + + +Implement ReviewStep.tsx: the full Phase 4 wizard step that composes all download, copy, preview, and security behaviors. This plan turns every RED stub from Plan 01 GREEN. + +Purpose: This is the primary deliverable of Phase 4. All eleven requirement behaviors (CONF-02, CONF-03, DOWN-01–DOWN-06, SECU-01, SECU-02) are implemented in this single component, composed from the utilities and OutputBlock built in Plan 02. +Output: src/components/wizard/ReviewStep.tsx — fully implemented, all tests GREEN. + + + +@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md + + + +@.planning/phases/04-review-download-security/04-RESEARCH.md +@src/store/types.ts +@src/store/context.tsx +@src/generators/index.ts +@src/components/wizard/OutputBlock.tsx +@src/utils/downloadFile.ts +@src/utils/downloadZip.ts +@src/components/wizard/ReviewStep.test.tsx + + + + + +From src/store/context.tsx: +```typescript +const { state } = useWizard(); +// state.remote.backendType: BackendType | null +// state.remote.name: string +// state.deployment: { includeInstall, configPath, scriptTargets } +``` + +From src/generators/index.ts (all accept WizardState, return string, buildRcloneConf throws on incomplete state): +```typescript +export function buildRcloneConf(state: WizardState): string +export function buildIntuneInstall(state: WizardState): string +export function buildIntuneDetection(state: WizardState): string +export function buildRmmScript(state: WizardState): string +``` + +From src/components/wizard/OutputBlock.tsx: +```typescript +export interface OutputBlockProps { + label: string; + content: string; + filename?: string; // when provided, shows Download button + disabled?: boolean; // SECU-01 gate +} +export function OutputBlock(props: OutputBlockProps): JSX.Element +``` + +From src/utils/downloadZip.ts: +```typescript +export async function downloadZip( + files: { name: string; content: string }[], + zipName: string +): Promise +``` + + + + + + Task 1: Implement ReviewStep.tsx — full component turning all stubs GREEN + src/components/wizard/ReviewStep.tsx + + - CONF-02: rcloneConfContent computed via useMemo calling buildRcloneConf(state); wrapped in try/catch returning placeholder on error + - CONF-03: rclone.conf OutputBlock has no filename (copy-only for live preview area); separate rclone.conf OutputBlock with filename='rclone.conf' for download + - DOWN-01: OutputBlock with filename='rclone.conf' calls downloadFile(content, 'rclone.conf') on Download click + - DOWN-02: OutputBlock with filename='intune-install.ps1' calls downloadFile on click + - DOWN-03: OutputBlock with filename='intune-detection.ps1' calls downloadFile on click + - DOWN-04: OutputBlock with filename='rmm-script.ps1' calls downloadFile on click + - DOWN-05: "Download All (ZIP)" button calls downloadZip([...4 files...], 'rclone-deployment.zip') + - DOWN-06: every OutputBlock's Copy button calls clipboard.writeText with that block's content + - SECU-01: acknowledged useState boolean controls disabled prop on all OutputBlocks and Download ZIP button + - SECU-02: a visible notice "No data is sent to any server" (or similar) is present in the render + + + Create src/components/wizard/ReviewStep.tsx. Follow the TDD cycle: + + RED: Run `npm test -- src/components/wizard/ReviewStep.test.tsx` first to confirm all stubs fail. + GREEN: Implement the component, run tests after each significant block. + + Implementation structure: + + ```typescript + // src/components/wizard/ReviewStep.tsx + import { useMemo, useState } from 'react'; + import { useWizard } from '../../store/context'; + import { + buildRcloneConf, + buildIntuneInstall, + buildIntuneDetection, + buildRmmScript, + } from '../../generators/index'; + import { OutputBlock } from './OutputBlock'; + import { downloadZip } from '../../utils/downloadZip'; + + const PLACEHOLDER = '# Fill in the wizard steps to generate your rclone.conf'; + + export function ReviewStep() { + const { state } = useWizard(); + const [acknowledged, setAcknowledged] = useState(false); + + // CONF-02: live preview — updates on every state change + const rcloneConf = useMemo(() => { + try { return buildRcloneConf(state); } catch { return PLACEHOLDER; } + }, [state]); + + const intuneInstall = useMemo(() => { + try { return buildIntuneInstall(state); } catch { return ''; } + }, [state]); + + const intuneDetection = useMemo(() => { + try { return buildIntuneDetection(state); } catch { return ''; } + }, [state]); + + const rmmScript = useMemo(() => { + try { return buildRmmScript(state); } catch { return ''; } + }, [state]); + + // DOWN-05: ZIP bundle — always include all 4 files + async function handleDownloadZip() { + await downloadZip( + [ + { name: 'rclone.conf', content: rcloneConf }, + { name: 'intune-install.ps1', content: intuneInstall }, + { name: 'intune-detection.ps1', content: intuneDetection }, + { name: 'rmm-script.ps1', content: rmmScript }, + ], + 'rclone-deployment.zip' + ); + } + + return ( +
+ {/* SECU-02: client-side only notice */} +

+ No data is sent to any server. All file generation happens in your browser. +

+ + {/* SECU-01: security acknowledgement gate */} +
+

+ Security warning: generated files contain credentials in plain text. +

+ +
+ + {/* Output blocks — DOWN-01 through DOWN-04, CONF-03, DOWN-06 */} + + + + + + {/* DOWN-05: ZIP bundle */} + +
+ ); + } + ``` + + After writing the file, run tests and fix any assertion mismatches: + - If a test expects a specific button text, match it exactly + - If a test queries by role or label, ensure the rendered output has matching accessible text + - Update test stubs to real assertions as you implement (replace `expect.fail('not yet implemented')` with actual assertions matching the rendered output) + + PITFALL — buildRcloneConf throws on incomplete state: Always use try/catch in useMemo. Show PLACEHOLDER string when thrown. This prevents the component from crashing during navigation with partial state. + + PITFALL — useMemo dependency: Pass `[state]` (the full state object) as the dependency array. WizardContext re-renders on every dispatch, so this will update on every form change (satisfies CONF-02). +
+ + npm test -- src/components/wizard/ReviewStep.test.tsx 2>&1 | tail -20 + + All test stubs in ReviewStep.test.tsx are replaced with real assertions and pass GREEN. npm test shows 0 failing tests for this file. All pre-existing tests in other files remain GREEN. +
+ +
+ + +`npm test` full suite passes GREEN. ReviewStep.test.tsx: all CONF-02, CONF-03, DOWN-01 through DOWN-06, SECU-01, SECU-02 assertions pass. reducer.test.ts SECU-03 passes. All Phase 1–3 tests still GREEN. + + + +- src/components/wizard/ReviewStep.tsx exists and exports ReviewStep +- All 10 requirement behaviors tested in ReviewStep.test.tsx pass GREEN +- No TypeScript errors in the component +- Full test suite green + + + +After completion, create `.planning/phases/04-review-download-security/04-03-SUMMARY.md` + diff --git a/.planning/phases/04-review-download-security/04-04-PLAN.md b/.planning/phases/04-review-download-security/04-04-PLAN.md new file mode 100644 index 0000000..a9699b8 --- /dev/null +++ b/.planning/phases/04-review-download-security/04-04-PLAN.md @@ -0,0 +1,129 @@ +--- +phase: 04-review-download-security +plan: 04 +type: execute +wave: 4 +depends_on: [04-03] +files_modified: + - src/App.tsx + - src/components/wizard/StepIndicator.tsx +autonomous: true +requirements: [CONF-02, CONF-03, DOWN-01, DOWN-02, DOWN-03, DOWN-04, DOWN-05, DOWN-06, SECU-01, SECU-02, SECU-03] + +must_haves: + truths: + - "App.tsx renders ReviewStep when currentStep is 3" + - "StepIndicator shows 4 labels: Backend, Remote Config, Deployment, Review" + - "Navigating to step 3 via StepIndicator dispatch renders the ReviewStep component" + - "Full npm test suite remains GREEN after wiring" + artifacts: + - path: "src/App.tsx" + provides: "Wired ReviewStep as step index 3 in the steps array" + contains: "ReviewStep" + - path: "src/components/wizard/StepIndicator.tsx" + provides: "STEP_LABELS updated with fourth entry 'Review'" + contains: "Review" + key_links: + - from: "src/App.tsx" + to: "src/components/wizard/ReviewStep.tsx" + via: "import { ReviewStep }" + pattern: "ReviewStep" + - from: "src/components/wizard/StepIndicator.tsx" + to: "STEP_LABELS array" + via: "array entry at index 3" + pattern: "'Review'" +--- + + +Wire ReviewStep into the running application: add it as step index 3 in App.tsx and add 'Review' as the fourth entry in StepIndicator's STEP_LABELS array. + +Purpose: ReviewStep is fully implemented and tested in isolation (Plan 03). This plan completes the integration so the full 4-step wizard flow is navigable end-to-end. +Output: Updated App.tsx and StepIndicator.tsx. No new files. + + + +@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md + + + +@.planning/phases/04-review-download-security/04-RESEARCH.md +@src/App.tsx +@src/components/wizard/StepIndicator.tsx +@src/App.test.tsx + + + + + +From src/App.tsx (current — step 3 add point annotated in comments): +```typescript +// Guard: clamp to valid range (phase 4 will add step 3 for review/download) +const stepIndex = Math.min(state.currentStep, steps.length - 1); +// steps array currently has indices 0, 1, 2 +``` + +From src/components/wizard/StepIndicator.tsx (current): +```typescript +const STEP_LABELS = ['Backend', 'Remote Config', 'Deployment']; +// Add 'Review' as index 3 +``` + +The App.tsx clamp logic (`Math.min(state.currentStep, steps.length - 1)`) already handles step 3 correctly once ReviewStep is in the array — no logic changes needed, only array population. + + + + + + Task 1: Wire ReviewStep into App.tsx and add 'Review' label to StepIndicator + src/App.tsx, src/components/wizard/StepIndicator.tsx + + Edit src/App.tsx: + 1. Add import: `import { ReviewStep } from './components/wizard/ReviewStep';` + 2. Add ReviewStep as the fourth element in the steps array: + ```typescript + const steps = [ + , + , + , + , // step index 3 — Phase 4 + ]; + ``` + 3. Remove or update the comment "phase 4 will add step 3 for review/download" — it is now implemented. + 4. The `Math.min(state.currentStep, steps.length - 1)` clamp requires NO change — it automatically works with 4 steps. + + Edit src/components/wizard/StepIndicator.tsx: + 1. Update STEP_LABELS to include 'Review' as the fourth entry: + ```typescript + const STEP_LABELS = ['Backend', 'Remote Config', 'Deployment', 'Review']; + ``` + 2. Update the header comment to reflect the new step: `// 1.Backend > 2.Remote Config > 3.Deployment > 4.Review` + 3. No other logic changes needed — the map over STEP_LABELS automatically renders the fourth step. + + After edits, run `npm test` to verify: + - App.test.tsx (existing step routing tests) must remain GREEN + - StepIndicator.test.tsx must remain GREEN + - All other tests must remain GREEN + + + npm test 2>&1 | tail -20 + + App.tsx imports and renders ReviewStep at step index 3. StepIndicator shows 4 step labels including 'Review'. Full npm test suite GREEN. TypeScript compilation clean. + + + + + +`npm run build` succeeds. `npm test` full suite GREEN. The app wires ReviewStep without breaking any existing step routing, StepIndicator navigation, or prior test assertions. + + + +- src/App.tsx imports ReviewStep and includes it as steps[3] +- src/components/wizard/StepIndicator.tsx STEP_LABELS has 4 entries ending with 'Review' +- All existing tests (App.test.tsx, StepIndicator.test.tsx, all Phase 1–3 tests) remain GREEN +- `npm run build` produces no errors + + + +After completion, create `.planning/phases/04-review-download-security/04-04-SUMMARY.md` + diff --git a/.planning/phases/04-review-download-security/04-05-PLAN.md b/.planning/phases/04-review-download-security/04-05-PLAN.md new file mode 100644 index 0000000..68bcaf6 --- /dev/null +++ b/.planning/phases/04-review-download-security/04-05-PLAN.md @@ -0,0 +1,116 @@ +--- +phase: 04-review-download-security +plan: 05 +type: checkpoint +wave: 5 +depends_on: [04-04] +files_modified: [] +autonomous: false +requirements: [CONF-02, CONF-03, DOWN-01, DOWN-02, DOWN-03, DOWN-04, DOWN-05, DOWN-06, SECU-01, SECU-02, SECU-03] + +must_haves: + truths: + - "IT admin can navigate the full 4-step wizard end-to-end in a real browser" + - "Security checkbox gate prevents downloads until acknowledged" + - "All four individual file downloads work in the browser" + - "ZIP bundle downloads and contains all 4 files" + - "Copy-to-clipboard works for each output block" + - "No data is written to localStorage or sessionStorage at any point" + artifacts: + - path: "src/components/wizard/ReviewStep.tsx" + provides: "Visually verified step 3 component" + - path: "src/utils/downloadFile.ts" + provides: "Verified individual download helper" + - path: "src/utils/downloadZip.ts" + provides: "Verified ZIP bundle helper" + key_links: + - from: "Browser navigation (steps 0→3)" + to: "ReviewStep rendered at step 3" + via: "App.tsx step router" + pattern: "n/a" +--- + + +Human verification of the complete Phase 4 feature set in a running browser. All automated tests pass, but ZIP content, actual file downloads, and clipboard paste cannot be verified by jsdom. + +Purpose: Confirm the full IT admin workflow — complete wizard, review generated files, acknowledge security warning, download individually and as ZIP, copy to clipboard — works correctly end-to-end. +Output: Human approval that Phase 4 is shippable. + + + +@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md +@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md + + + +@.planning/ROADMAP.md + + + + + + + Complete Phase 4 feature set: + - ReviewStep (step 3) with live rclone.conf preview + - Security acknowledgement checkbox gate (all downloads disabled until checked) + - "No data sent to server" client-side notice + - Individual download buttons for rclone.conf, intune-install.ps1, intune-detection.ps1, rmm-script.ps1 + - Copy-to-clipboard button per output block + - "Download All (ZIP)" button producing rclone-deployment.zip with all 4 files + - 4-label StepIndicator (Backend > Remote Config > Deployment > Review) + - Full automated test suite GREEN (including SECU-03 localStorage spy) + + + Run the dev server: `npm run dev` then open http://localhost:5173 + + Step 1 — Navigate the full wizard: + 1. Select a backend (e.g., Azure Blob) + 2. Enter a remote name and fill in all required fields (storage account, SAS token or access key) + 3. Proceed to Deployment Options, leave defaults or adjust, click Next + 4. Confirm you reach step 4 "Review" with StepIndicator showing "1. Backend › 2. Remote Config › 3. Deployment › 4. Review" + + Step 2 — Verify the live preview (CONF-02): + 5. The rclone.conf output block shows a populated INI-format config reflecting your entered values + 6. Go back to step 2, change a field value, return to step 4 — verify the preview updated + + Step 3 — Verify security gate (SECU-01, SECU-02): + 7. Before checking the checkbox: confirm all Download and Copy buttons appear disabled/greyed out + 8. Confirm the "No data is sent to any server" notice is visible + 9. Check the acknowledgement checkbox — confirm all buttons become active + + Step 4 — Individual downloads (DOWN-01 through DOWN-04): + 10. Click "Download" on the rclone.conf block — verify rclone.conf file saved to Downloads + 11. Click "Download" on Intune Install Script — verify intune-install.ps1 saved + 12. Click "Download" on Intune Detection Script — verify intune-detection.ps1 saved + 13. Click "Download" on RMM Script — verify rmm-script.ps1 saved + + Step 5 — Clipboard copy (CONF-03, DOWN-06): + 14. Click "Copy" on the rclone.conf block — paste into a text editor, verify content matches displayed preview + 15. Click "Copy" on at least one script block — verify clipboard content matches displayed script + + Step 6 — ZIP bundle (DOWN-05): + 16. Click "Download All (ZIP)" — verify rclone-deployment.zip saved to Downloads + 17. Open the ZIP file with your OS ZIP tool — verify it contains exactly 4 files: rclone.conf, intune-install.ps1, intune-detection.ps1, rmm-script.ps1 + + Step 7 — SECU-03 (no storage writes): + 18. Open DevTools → Application → Local Storage and Session Storage + 19. Navigate the full wizard end-to-end — verify both storage areas remain empty at all times + + Type "approved" if all checks pass, or describe which checks failed with details + + + + + +Human approval received. All 7 verification steps confirmed passing. Phase 4 requirements CONF-02, CONF-03, DOWN-01–DOWN-06, SECU-01, SECU-02, SECU-03 verified in a real browser. + + + +- Human has confirmed "approved" after completing all 18 verification steps +- No failures reported for any requirement behavior +- Phase 4 declared complete + + + +After approval, create `.planning/phases/04-review-download-security/04-05-SUMMARY.md` +