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 <noreply@anthropic.com>
This commit is contained in:
@@ -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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.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
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Key contracts the executor needs — extracted from codebase. No exploration needed. -->
|
||||
|
||||
From src/store/types.ts:
|
||||
```typescript
|
||||
export interface WizardState {
|
||||
currentStep: number;
|
||||
remote: {
|
||||
name: string;
|
||||
backendType: BackendType | null;
|
||||
params: Record<string, string>;
|
||||
};
|
||||
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<WizardAction> }
|
||||
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
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Write ReviewStep.test.tsx with RED stubs for all Phase 4 requirements</name>
|
||||
<files>src/components/wizard/ReviewStep.test.tsx</files>
|
||||
<behavior>
|
||||
- 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
|
||||
</behavior>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm test -- src/components/wizard/ReviewStep.test.tsx 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Add SECU-03 assertion to reducer.test.ts</name>
|
||||
<files>src/store/reducer.test.ts</files>
|
||||
<behavior>
|
||||
- SECU-03: localStorage.setItem and sessionStorage.setItem are never called during any wizard reducer dispatch
|
||||
</behavior>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm test -- src/store/reducer.test.ts 2>&1 | tail -10</automated>
|
||||
</verify>
|
||||
<done>reducer.test.ts has the SECU-03 test and it passes GREEN. All pre-existing reducer tests still pass.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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).
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-review-download-security/04-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/04-review-download-security/04-RESEARCH.md
|
||||
@src/store/types.ts
|
||||
@src/components/wizard/ReviewStep.test.tsx
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Contracts the executor must implement — the test file already imports these paths. -->
|
||||
|
||||
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<void>
|
||||
|
||||
// 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
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create downloadFile.ts and downloadZip.ts utilities</name>
|
||||
<files>src/utils/downloadFile.ts, src/utils/downloadZip.ts</files>
|
||||
<behavior>
|
||||
- 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)
|
||||
</behavior>
|
||||
<action>
|
||||
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<void> {
|
||||
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').
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm run build 2>&1 | tail -10</automated>
|
||||
</verify>
|
||||
<done>Both utility files exist and TypeScript compilation succeeds with no errors. jszip appears in package.json dependencies.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Create OutputBlock.tsx reusable component</name>
|
||||
<files>src/components/wizard/OutputBlock.tsx</files>
|
||||
<behavior>
|
||||
- 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
|
||||
</behavior>
|
||||
<action>
|
||||
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 (
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-sm font-medium text-gray-700">{label}</span>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={handleCopy}
|
||||
className="text-xs px-2 py-1 rounded border border-gray-300 disabled:opacity-40"
|
||||
>
|
||||
{copied ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
{filename && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => downloadFile(content, filename)}
|
||||
className="text-xs px-2 py-1 rounded border border-gray-300 disabled:opacity-40"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<pre className="bg-gray-900 text-gray-100 rounded p-4 text-xs overflow-x-auto whitespace-pre-wrap">
|
||||
<code>{content}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Styling uses Tailwind classes consistent with the existing wizard components. The `disabled:opacity-40` class communicates the locked state visually without hiding buttons.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm test 2>&1 | tail -15</automated>
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
`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.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-review-download-security/04-02-SUMMARY.md`
|
||||
</output>
|
||||
@@ -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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.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
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Contracts the executor must satisfy — read the test file for exact assertion details -->
|
||||
|
||||
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<void>
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Implement ReviewStep.tsx — full component turning all stubs GREEN</name>
|
||||
<files>src/components/wizard/ReviewStep.tsx</files>
|
||||
<behavior>
|
||||
- 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
|
||||
</behavior>
|
||||
<action>
|
||||
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 (
|
||||
<div>
|
||||
{/* SECU-02: client-side only notice */}
|
||||
<p className="text-sm text-green-700 bg-green-50 rounded p-3 mb-6">
|
||||
No data is sent to any server. All file generation happens in your browser.
|
||||
</p>
|
||||
|
||||
{/* SECU-01: security acknowledgement gate */}
|
||||
<div className="bg-yellow-50 border border-yellow-300 rounded p-4 mb-6">
|
||||
<p className="text-sm text-yellow-800 font-medium mb-2">
|
||||
Security warning: generated files contain credentials in plain text.
|
||||
</p>
|
||||
<label className="flex items-center gap-2 text-sm text-yellow-900 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={acknowledged}
|
||||
onChange={(e) => setAcknowledged(e.target.checked)}
|
||||
/>
|
||||
I understand that the generated files contain credentials in plain text.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Output blocks — DOWN-01 through DOWN-04, CONF-03, DOWN-06 */}
|
||||
<OutputBlock
|
||||
label="rclone.conf"
|
||||
content={rcloneConf}
|
||||
filename="rclone.conf"
|
||||
disabled={!acknowledged}
|
||||
/>
|
||||
<OutputBlock
|
||||
label="Intune Install Script"
|
||||
content={intuneInstall}
|
||||
filename="intune-install.ps1"
|
||||
disabled={!acknowledged}
|
||||
/>
|
||||
<OutputBlock
|
||||
label="Intune Detection Script"
|
||||
content={intuneDetection}
|
||||
filename="intune-detection.ps1"
|
||||
disabled={!acknowledged}
|
||||
/>
|
||||
<OutputBlock
|
||||
label="RMM Script"
|
||||
content={rmmScript}
|
||||
filename="rmm-script.ps1"
|
||||
disabled={!acknowledged}
|
||||
/>
|
||||
|
||||
{/* DOWN-05: ZIP bundle */}
|
||||
<button
|
||||
type="button"
|
||||
disabled={!acknowledged}
|
||||
onClick={handleDownloadZip}
|
||||
className="w-full py-2 px-4 bg-blue-600 text-white rounded font-medium disabled:opacity-40 disabled:cursor-not-allowed hover:bg-blue-700"
|
||||
>
|
||||
Download All (ZIP)
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm test -- src/components/wizard/ReviewStep.test.tsx 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
`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.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-review-download-security/04-03-SUMMARY.md`
|
||||
</output>
|
||||
@@ -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'"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/04-review-download-security/04-RESEARCH.md
|
||||
@src/App.tsx
|
||||
@src/components/wizard/StepIndicator.tsx
|
||||
@src/App.test.tsx
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Existing extension points — both files were pre-annotated for this change -->
|
||||
|
||||
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.
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Wire ReviewStep into App.tsx and add 'Review' label to StepIndicator</name>
|
||||
<files>src/App.tsx, src/components/wizard/StepIndicator.tsx</files>
|
||||
<action>
|
||||
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 = [
|
||||
<BackendSelectionStep key="backend" />,
|
||||
<RemoteConfigStep key={state.remote.backendType ?? 'none'} />,
|
||||
<DeploymentStep key="deployment" />,
|
||||
<ReviewStep key="review" />, // 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
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npm test 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
`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.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-review-download-security/04-04-SUMMARY.md`
|
||||
</output>
|
||||
@@ -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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/ROADMAP.md
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<what-built>
|
||||
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)
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
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
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" if all checks pass, or describe which checks failed with details</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Human has confirmed "approved" after completing all 18 verification steps
|
||||
- No failures reported for any requirement behavior
|
||||
- Phase 4 declared complete
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After approval, create `.planning/phases/04-review-download-security/04-05-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user