Files
Ready2Blob/.planning/phases/04-review-download-security/04-03-PLAN.md
T
kawaandClaude Sonnet 4.6 405bf477b4 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>
2026-03-27 10:23:14 +01:00

11 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
04-review-download-security 03 tdd 3
04-02
src/components/wizard/ReviewStep.tsx
true
CONF-02
CONF-03
DOWN-01
DOWN-02
DOWN-03
DOWN-04
DOWN-05
DOWN-06
SECU-01
SECU-02
truths artifacts key_links
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
path provides exports min_lines
src/components/wizard/ReviewStep.tsx Step 3 component — live preview + security gate + all download/copy actions
ReviewStep
60
from to via pattern
src/components/wizard/ReviewStep.tsx src/generators/index.ts import { buildRcloneConf, buildIntuneInstall, buildIntuneDetection, buildRmmScript } buildRcloneConf
from to via pattern
src/components/wizard/ReviewStep.tsx src/store/context.tsx import { useWizard } useWizard
from to via pattern
src/components/wizard/ReviewStep.tsx src/components/wizard/OutputBlock.tsx import { OutputBlock } OutputBlock
from to via pattern
src/components/wizard/ReviewStep.tsx src/utils/downloadZip.ts import { downloadZip } 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-01DOWN-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.

<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>

@.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:

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):

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:

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:

export async function downloadZip(
  files: { name: string; content: string }[],
  zipName: string
): Promise<void>
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 (
    <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).
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 13 tests still GREEN.

<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>
After completion, create `.planning/phases/04-review-download-security/04-03-SUMMARY.md`