From 0a0a51b45a1f50a28e58dcf36055e681c17ca71d Mon Sep 17 00:00:00 2001 From: Kawa Date: Mon, 30 Mar 2026 09:26:17 +0200 Subject: [PATCH] =?UTF-8?q?docs(05-tech-debt):=20create=20phase=205=20plan?= =?UTF-8?q?=20=E2=80=94=204=20plans=20across=202=20waves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave 0 TDD stubs, Wave 1 parallel (registry + ReviewStep), Wave 2 act() fix. Covers TECH-01 through TECH-05. Co-Authored-By: Claude Sonnet 4.6 --- .planning/ROADMAP.md | 10 +- .planning/STATE.md | 16 +- .planning/phases/05-tech-debt/05-00-PLAN.md | 182 ++++++++++++++ .planning/phases/05-tech-debt/05-01-PLAN.md | 248 ++++++++++++++++++++ .planning/phases/05-tech-debt/05-02-PLAN.md | 221 +++++++++++++++++ .planning/phases/05-tech-debt/05-03-PLAN.md | 210 +++++++++++++++++ 6 files changed, 877 insertions(+), 10 deletions(-) create mode 100644 .planning/phases/05-tech-debt/05-00-PLAN.md create mode 100644 .planning/phases/05-tech-debt/05-01-PLAN.md create mode 100644 .planning/phases/05-tech-debt/05-02-PLAN.md create mode 100644 .planning/phases/05-tech-debt/05-03-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 2fbbd8d..d704ec4 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -39,7 +39,13 @@ Full phase details: [.planning/milestones/v1.0-ROADMAP.md](milestones/v1.0-ROADM 3. Adding a new entry to BACKEND_REGISTRY automatically surfaces that backend in the BackendSelectionStep UI with no other code changes 4. BackendSelectionStep test suite runs with zero `act()` warnings in the Vitest output 5. `BackendFormValues` export is absent from `src/schemas/index.ts` and no TypeScript errors arise -**Plans**: TBD +**Plans**: 4 plans + +Plans: +- [ ] 05-00-PLAN.md — Wave 0 TDD stubs: ReviewStep filtering + Back button failing tests, registry.test.ts .fields prep +- [ ] 05-01-PLAN.md — Registry enrichment (TECH-03) + consumers update + dead export removal (TECH-04) +- [ ] 05-02-PLAN.md — ReviewStep scriptTargets filtering + Back button (TECH-01, TECH-02) +- [ ] 05-03-PLAN.md — act() warnings fix: userEvent migration + vi.useFakeTimers (TECH-05) ### Phase 6: New Backends **Goal**: IT pros can configure OneDrive, SFTP, Google Cloud Storage, and Backblaze B2 remotes through the same wizard flow, with appropriate guidance for OAuth-based and key-based auth methods @@ -72,6 +78,6 @@ Full phase details: [.planning/milestones/v1.0-ROADMAP.md](milestones/v1.0-ROADM | 2. Generators | v1.0 | 4/4 | Complete | 2026-03-26 | | 3. Wizard UI | v1.0 | 5/5 | Complete | 2026-03-27 | | 4. Review, Download & Security | v1.0 | 5/5 | Complete | 2026-03-27 | -| 5. Tech Debt | v1.1 | 0/TBD | Not started | - | +| 5. Tech Debt | v1.1 | 0/4 | Not started | - | | 6. New Backends | v1.1 | 0/TBD | Not started | - | | 7. Validation & UX Polish | v1.1 | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index c3e6fc1..5afdc3e 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -1,11 +1,11 @@ --- gsd_state_version: 1.0 milestone: v1.1 -milestone_name: "Backlog & Tech Debt" -status: roadmap_ready -stopped_at: Roadmap created for v1.1 — 3 phases (5-7), 11 requirements mapped -last_updated: "2026-03-27T00:00:00.000Z" -last_activity: 2026-03-27 — v1.1 roadmap created, ready to plan Phase 5 +milestone_name: Backlog & Tech Debt +status: planning +stopped_at: Phase 5 context gathered +last_updated: "2026-03-30T07:14:08.967Z" +last_activity: 2026-03-27 — v1.1 roadmap created, 11 requirements mapped across 3 phases progress: total_phases: 3 completed_phases: 0 @@ -74,6 +74,6 @@ None yet. ## Session Continuity -Last session: 2026-03-27T00:00:00.000Z -Stopped at: v1.1 roadmap created — Phases 5-7 defined, all 11 requirements mapped -Resume file: None +Last session: 2026-03-30T07:14:08.962Z +Stopped at: Phase 5 context gathered +Resume file: .planning/phases/05-tech-debt/05-CONTEXT.md diff --git a/.planning/phases/05-tech-debt/05-00-PLAN.md b/.planning/phases/05-tech-debt/05-00-PLAN.md new file mode 100644 index 0000000..94bd0e0 --- /dev/null +++ b/.planning/phases/05-tech-debt/05-00-PLAN.md @@ -0,0 +1,182 @@ +--- +phase: 05-tech-debt +plan: "00" +type: tdd +wave: 0 +depends_on: [] +files_modified: + - src/components/wizard/ReviewStep.test.tsx + - src/schemas/registry.test.ts +autonomous: true +requirements: + - TECH-01 + - TECH-02 + - TECH-03 + +must_haves: + truths: + - "ReviewStep test suite has failing cases for intune-only rendering" + - "ReviewStep test suite has a failing case for rmm-only rendering" + - "ReviewStep test suite has a failing case for neither-target rendering (only rclone.conf)" + - "ReviewStep test suite has a failing case for Back button dispatching SET_STEP(2)" + - "registry.test.ts accesses BACKEND_REGISTRY entries via .fields — ready for shape change" + artifacts: + - path: "src/components/wizard/ReviewStep.test.tsx" + provides: "Failing test cases for TECH-01 and TECH-02" + contains: "scriptTargets" + - path: "src/schemas/registry.test.ts" + provides: "Updated field access via .fields for TECH-03" + contains: ".fields" + key_links: + - from: "ReviewStep.test.tsx new cases" + to: "ReviewStep.tsx (not yet modified)" + via: "renderWithDeployment helper dispatching SET_DEPLOYMENT" + pattern: "SET_DEPLOYMENT" +--- + + +Write Wave 0 test stubs: failing test cases for TECH-01 (scriptTargets filtering) and TECH-02 (Back button) in ReviewStep.test.tsx, and update registry.test.ts field access to use .fields ahead of the TECH-03 registry shape change. + +Purpose: Establish RED state before implementation plans run — per project TDD pattern (Wave 0 stubs before implementation). +Output: Modified ReviewStep.test.tsx with 4 new failing describe blocks, modified registry.test.ts with .fields access. + + + +@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/STATE.md +@.planning/phases/05-tech-debt/05-CONTEXT.md +@.planning/phases/05-tech-debt/05-RESEARCH.md + + + + +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')[]; + }; +} +export type WizardAction = + | { type: 'SET_STEP'; payload: number } + | { type: 'SET_DEPLOYMENT'; payload: Partial } + // ... other actions + +export const INITIAL_STATE: WizardState = { + deployment: { scriptTargets: ['intune', 'rmm'], ... }, +}; +``` + +From src/store/context.tsx: +```typescript +// WizardProvider does NOT accept initialState — uses INITIAL_STATE hardcoded +export function WizardProvider({ children }: { children: React.ReactNode }) +``` + +From src/schemas/registry.ts (CURRENT shape — will change in Plan 01): +```typescript +export const BACKEND_REGISTRY: Record = { ... } +// After TECH-03 it becomes: Record +``` + + + + + + + Task 1: Add TECH-01 and TECH-02 failing test cases to ReviewStep.test.tsx + src/components/wizard/ReviewStep.test.tsx + + - TECH-01-a: When scriptTargets is ['intune'] only, Intune Install and Intune Detection OutputBlocks are present, RMM Script OutputBlock is absent + - TECH-01-b: When scriptTargets is ['rmm'] only, RMM Script OutputBlock is present, Intune blocks are absent + - TECH-01-c: When scriptTargets is [], only rclone.conf OutputBlock is shown (no intune or rmm blocks) + - TECH-01-d: When scriptTargets is [], ZIP download button calls downloadZip with only 1 file (rclone.conf) + - TECH-02: Back button is present in rendered output and clicking it dispatches SET_STEP(2) (verify via step change — component navigates away or via mock) + + + Add a `renderWithDeployment` helper at the top of the test file (after existing `renderStep`) that wraps in WizardProvider and dispatches SET_DEPLOYMENT before assertions. Pattern: render with WizardProvider, use `act(() => dispatch({ type: 'SET_DEPLOYMENT', payload: { scriptTargets: [...] } }))` via a TestHelper component that receives a dispatch ref, OR use a wrapper component that accepts a prop and dispatches in useEffect. + + Preferred approach (no code changes to production): create a `WizardConsumerSetup` React component inside the test file that calls `dispatch(SET_DEPLOYMENT)` in a `useEffect` on mount, then renders children. Wrap ReviewStep with it in renderWithDeployment. + + Add these new describe blocks (append to existing file — do NOT remove any existing tests): + - describe('TECH-01: scriptTargets filtering', ...) with 4 it() cases covering intune-only, rmm-only, neither, and ZIP-neither + - describe('TECH-02: Back button navigation', ...) with 1 it() case verifying button labeled 'Back' renders + + These tests MUST FAIL (RED state) because ReviewStep not yet modified. Confirm by running the test suite and seeing failures on the new cases. + + Note: ZIP-neither test — mock downloadZip is already set up in beforeEach. The test clicks the ZIP button with scriptTargets=[] and asserts files array has length 1. + + Note: TECH-02 Back button test — check `screen.getByRole('button', { name: /back/i })` renders. The navigation itself (SET_STEP dispatch) is trivially verifiable by asserting the button exists and is not disabled (dispatch correctness verified by ReviewStep implementation, not mocked here). + + + cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run src/components/wizard/ReviewStep.test.tsx 2>&1 | tail -20 + + + Existing ReviewStep tests still pass (CONF-02, CONF-03, DOWN-01 through DOWN-06, SECU-01, SECU-02). + New TECH-01 and TECH-02 test cases exist in the file and fail (RED state — ReviewStep not yet modified). + + + + + Task 2: Update registry.test.ts field access to use .fields + src/schemas/registry.test.ts + + Update registry.test.ts to access fields via `.fields` in preparation for the TECH-03 shape change. The current registry shape is `FieldDef[]` directly — these tests will break after Plan 01 enriches the shape. Update them now so they are ready. + + Changes needed (per RESEARCH.md pitfall 2): + - Line 15: `BACKEND_REGISTRY[backend].length` → `BACKEND_REGISTRY[backend].fields.length` + - Line 21: `for (const field of BACKEND_REGISTRY[backend])` → `for (const field of BACKEND_REGISTRY[backend].fields)` + - Line 38: `BACKEND_REGISTRY.azureblob.find(f => f.key === 'account')` → `BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'account')` + - Line 44: `BACKEND_REGISTRY.s3.map(f => f.key)` → `BACKEND_REGISTRY.s3.fields.map(f => f.key)` + - Line 51: `BACKEND_REGISTRY['s3-compatible'].find(f => f.key === 'endpoint')` → `BACKEND_REGISTRY['s3-compatible'].fields.find(f => f.key === 'endpoint')` + + These changes will make registry.test.ts FAIL (RED state) because registry.ts still has the old `FieldDef[]` shape. That's expected — Plan 01 fixes the production code to green. + + Also add a new test case verifying the enriched shape structure (will also be RED until Plan 01): + ``` + it('each backend entry has displayName and description metadata', () => { + for (const backend of EXPECTED_BACKENDS) { + expect(BACKEND_REGISTRY[backend].displayName).toBeTruthy(); + expect(BACKEND_REGISTRY[backend].description).toBeTruthy(); + } + }); + ``` + + + cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run src/schemas/registry.test.ts 2>&1 | tail -15 + + + registry.test.ts uses .fields access throughout. + Test suite shows failures on the registry tests (RED — registry shape not yet enriched). TypeScript may show type errors — that is expected and correct until Plan 01 runs. + + + + + + +Run full suite to confirm scope of RED state: +``` +cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run 2>&1 | tail -20 +``` +Expected: existing passing tests still pass, only new TECH-01/TECH-02/TECH-03 stubs fail. + + + +- ReviewStep.test.tsx has new failing test cases for TECH-01 (intune-only, rmm-only, neither, ZIP-neither) and TECH-02 (Back button present) +- registry.test.ts uses .fields access throughout and has a new displayName/description test case +- All pre-existing tests (CONF-02, CONF-03, DOWN-01–DOWN-06, SECU-01, SECU-02, WIZD-01, WIZD-04, all registry tests that existed) remain green or show the expected RED only on the new .fields lines + + + +After completion, create `.planning/phases/05-tech-debt/05-00-SUMMARY.md` + diff --git a/.planning/phases/05-tech-debt/05-01-PLAN.md b/.planning/phases/05-tech-debt/05-01-PLAN.md new file mode 100644 index 0000000..d8138b3 --- /dev/null +++ b/.planning/phases/05-tech-debt/05-01-PLAN.md @@ -0,0 +1,248 @@ +--- +phase: 05-tech-debt +plan: "01" +type: execute +wave: 1 +depends_on: + - "05-00" +files_modified: + - src/schemas/registry.ts + - src/schemas/index.ts + - src/components/wizard/RemoteConfigStep.tsx + - src/components/wizard/BackendSelectionStep.tsx +autonomous: true +requirements: + - TECH-03 + - TECH-04 + +must_haves: + truths: + - "BACKEND_REGISTRY entries each have displayName, description, and fields properties" + - "BackendSelectionStep renders cards using Object.entries(BACKEND_REGISTRY) — no hardcoded BACKENDS array" + - "All consumers of BACKEND_REGISTRY access field arrays via .fields" + - "BackendFormValues export is absent from src/schemas/index.ts" + - "TypeScript compiles with zero errors after changes" + artifacts: + - path: "src/schemas/registry.ts" + provides: "Enriched BACKEND_REGISTRY with displayName, description, fields" + contains: "displayName" + - path: "src/components/wizard/BackendSelectionStep.tsx" + provides: "Registry-driven card list" + contains: "Object.entries(BACKEND_REGISTRY)" + key_links: + - from: "src/schemas/registry.ts" + to: "src/schemas/index.ts" + via: "buildZodSchema accesses BACKEND_REGISTRY[backendType].fields" + pattern: "\\.fields" + - from: "src/schemas/registry.ts" + to: "src/components/wizard/RemoteConfigStep.tsx" + via: "field iteration uses BACKEND_REGISTRY[backendType].fields" + pattern: "BACKEND_REGISTRY\\[backendType\\]\\.fields" + - from: "src/schemas/registry.ts" + to: "src/components/wizard/BackendSelectionStep.tsx" + via: "Object.entries(BACKEND_REGISTRY) replaces BACKENDS const" + pattern: "Object\\.entries\\(BACKEND_REGISTRY\\)" +--- + + +Enrich BACKEND_REGISTRY with display metadata, wire BackendSelectionStep to derive its card list from the registry, update all consumers to use .fields access, and remove the dead BackendFormValues export. + +Purpose: TECH-03 makes the registry the single source of truth for both field definitions AND display metadata — Phase 6 backend additions auto-surface with zero additional UI code. TECH-04 removes dead code. +Output: registry.ts with new shape, BackendSelectionStep.tsx driven by registry, schemas/index.ts clean. + + + +@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/05-tech-debt/05-CONTEXT.md +@.planning/phases/05-tech-debt/05-RESEARCH.md +@.planning/phases/05-tech-debt/05-00-SUMMARY.md + + + + +From src/schemas/registry.ts (CURRENT shape — executor REPLACES this): +```typescript +export type BackendType = 'azureblob' | 's3' | 's3-compatible'; +export interface FieldDef { key, label, inputType, required, placeholder?, helpText?, options? } +export const BACKEND_REGISTRY: Record = { ... } +``` + +New shape (TECH-03 target): +```typescript +export const BACKEND_REGISTRY: Record = { + azureblob: { + displayName: 'Azure Blob Storage', + description: 'Microsoft Azure cloud storage', + fields: [ /* existing FieldDef[] content unchanged */ ], + }, + s3: { + displayName: 'Amazon S3', + description: 'AWS Simple Storage Service', + fields: [ /* existing FieldDef[] content unchanged */ ], + }, + 's3-compatible': { + displayName: 'S3-Compatible', + description: 'Wasabi, MinIO, Cloudflare R2, and others', + fields: [ /* existing FieldDef[] content unchanged */ ], + }, +}; +``` + +Display strings MUST match existing BackendSelectionStep card text exactly (test assertions check these strings). + +From src/schemas/index.ts line 10 (consumer 1 — must update): +```typescript +const fields = BACKEND_REGISTRY[backendType]; // CHANGE TO: BACKEND_REGISTRY[backendType].fields +``` + +From src/components/wizard/RemoteConfigStep.tsx (consumers 2 and 3 — must update): +```typescript +// Line 58: BACKEND_REGISTRY.azureblob.find(f => f.key === 'account') +// CHANGE TO: BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'account') +// Line 73: BACKEND_REGISTRY[backendType].map(field => ...) +// CHANGE TO: BACKEND_REGISTRY[backendType].fields.map(field => ...) +``` + +From src/components/wizard/BackendSelectionStep.tsx (TECH-03 — replace hardcoded BACKENDS): +```typescript +// Remove: const BACKENDS: { type: BackendType; name: string; description: string }[] = [...] +// Replace rendering with: +import { BACKEND_REGISTRY } from '../../schemas/registry'; +// ... +{Object.entries(BACKEND_REGISTRY).map(([type, entry]) => ( + handleCardClick(type as BackendType)} + /> +))} +``` + +From src/schemas/index.ts lines 27-28 (TECH-04 — remove entirely): +```typescript +// REMOVE these two lines: +export type BackendFormValues = + z.infer; +``` + + + + + + + Task 1: Enrich BACKEND_REGISTRY and update all consumers + src/schemas/registry.ts, src/schemas/index.ts, src/components/wizard/RemoteConfigStep.tsx + + - registry.test.ts 'each backend entry has displayName and description metadata' passes + - registry.test.ts 'each backend has at least one field definition' passes (via .fields.length) + - registry.test.ts 'Azure Blob has account field' passes (via .fields.find) + - schemas/index.ts buildZodSchema receives FieldDef[] from .fields — all BACKEND_SCHEMAS build correctly + - RemoteConfigStep renders fields correctly (existing RemoteConfigStep.test.tsx passes) + + + 1. Edit src/schemas/registry.ts: + - Change BACKEND_REGISTRY value type from `FieldDef[]` to `{ displayName: string; description: string; fields: FieldDef[] }` + - Wrap each backend's existing FieldDef array in the new object shape, adding displayName and description + - displayName and description strings MUST match the existing BACKENDS array in BackendSelectionStep.tsx exactly: + - azureblob: displayName='Azure Blob Storage', description='Microsoft Azure cloud storage' + - s3: displayName='Amazon S3', description='AWS Simple Storage Service' + - s3-compatible: displayName='S3-Compatible', description='Wasabi, MinIO, Cloudflare R2, and others' + - All existing FieldDef content is preserved unchanged inside fields + + 2. Edit src/schemas/index.ts line 10: + - Change `const fields = BACKEND_REGISTRY[backendType];` to `const fields = BACKEND_REGISTRY[backendType].fields;` + + 3. Edit src/components/wizard/RemoteConfigStep.tsx: + - Line 58: Change `BACKEND_REGISTRY.azureblob.find(f => f.key === 'account')` to `BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'account')` + - Line 73: Change `BACKEND_REGISTRY[backendType].map(field =>` to `BACKEND_REGISTRY[backendType].fields.map(field =>` + + + cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run src/schemas/registry.test.ts src/components/wizard/RemoteConfigStep.test.tsx 2>&1 | tail -15 + + + registry.test.ts passes with all tests green (including new displayName/description test and .fields access tests). + RemoteConfigStep.test.tsx still passes. + TypeScript sees no errors on the modified files. + + + + + Task 2: Wire BackendSelectionStep to registry + remove dead export + src/components/wizard/BackendSelectionStep.tsx, src/schemas/index.ts + + - BackendSelectionStep renders 'Azure Blob Storage', 'Amazon S3', 'S3-Compatible' cards (from registry, not hardcoded) + - Azure Blob card appears before S3 in DOM order (Object.entries insertion order preserved) + - Clicking a card with a valid remote name still dispatches SET_BACKEND_TYPE and SET_STEP (existing WIZD-01 test passes) + - BackendFormValues type is absent from src/schemas/index.ts + - npx tsc --noEmit exits with code 0 + + + 1. Edit src/components/wizard/BackendSelectionStep.tsx: + - Add import: `import { BACKEND_REGISTRY } from '../../schemas/registry';` + - Remove the `BACKENDS` constant entirely (lines 21-37) + - In the JSX rendering section, replace `{BACKENDS.map((backend) => (` block with: + ```tsx + {Object.entries(BACKEND_REGISTRY).map(([type, entry]) => ( + handleCardClick(type as BackendType)} + /> + ))} + ``` + - Remove the now-unused local type import for BackendType from '../../store/types' IF it is only used by the BACKENDS const (check — it may still be needed for pendingBackend.current type annotation). If BackendType is still needed, keep the import. + + 2. Edit src/schemas/index.ts: + - Remove lines 27-28: the `export type BackendFormValues = z.infer;` export + - Also remove the blank comment line above it (line 26: `// Utility type: infer TypeScript type from a backend's Zod schema`) to avoid orphan comment + + 3. Run TypeScript check to confirm no consumers of BackendFormValues exist: + `npx tsc --noEmit` + + + cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run src/components/wizard/BackendSelectionStep.test.tsx && npx tsc --noEmit 2>&1 | tail -10 + + + BackendSelectionStep.test.tsx passes (WIZD-01 card text assertions still green — display strings match). + npx tsc --noEmit exits with zero errors. + BackendFormValues export is absent from src/schemas/index.ts. + + + + + + +Full suite green except pre-existing RED stubs (TECH-01, TECH-02 ReviewStep stubs remain RED — implemented in Plan 02): +``` +cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run && npx tsc --noEmit +``` +Expected: All registry tests green, all BackendSelectionStep tests green, RemoteConfigStep tests green. Only TECH-01/TECH-02 ReviewStep stubs still RED. + + + +- BACKEND_REGISTRY shape is `Record` +- BackendSelectionStep uses `Object.entries(BACKEND_REGISTRY)` — no hardcoded BACKENDS array +- schemas/index.ts uses `.fields` access and has no BackendFormValues export +- RemoteConfigStep uses `.fields` access +- registry.test.ts fully green +- BackendSelectionStep.test.tsx fully green +- npx tsc --noEmit: zero errors + + + +After completion, create `.planning/phases/05-tech-debt/05-01-SUMMARY.md` + diff --git a/.planning/phases/05-tech-debt/05-02-PLAN.md b/.planning/phases/05-tech-debt/05-02-PLAN.md new file mode 100644 index 0000000..56c62f0 --- /dev/null +++ b/.planning/phases/05-tech-debt/05-02-PLAN.md @@ -0,0 +1,221 @@ +--- +phase: 05-tech-debt +plan: "02" +type: execute +wave: 1 +depends_on: + - "05-00" +files_modified: + - src/components/wizard/ReviewStep.tsx +autonomous: true +requirements: + - TECH-01 + - TECH-02 + +must_haves: + truths: + - "User who deselected RMM sees only the Intune output blocks in ReviewStep (RMM block is unmounted)" + - "User who deselected Intune sees only the RMM output block in ReviewStep (Intune blocks are unmounted)" + - "User with both targets deselected sees only the rclone.conf block" + - "ZIP bundle respects scriptTargets — deselected targets are excluded from the ZIP file list" + - "User can click Back on ReviewStep to dispatch SET_STEP(2)" + artifacts: + - path: "src/components/wizard/ReviewStep.tsx" + provides: "scriptTargets-driven conditional rendering + Back button" + contains: "showIntune" + key_links: + - from: "ReviewStep.tsx" + to: "state.deployment.scriptTargets" + via: "showIntune and showRmm booleans derived from scriptTargets.includes()" + pattern: "scriptTargets\\.includes" + - from: "Back button" + to: "dispatch({ type: 'SET_STEP', payload: 2 })" + via: "onClick handler using dispatch from useWizard()" + pattern: "SET_STEP.*payload.*2" +--- + + +Modify ReviewStep to conditionally render output blocks based on state.deployment.scriptTargets, filter the ZIP bundle to match selected targets, and add a Back button that dispatches SET_STEP(2). + +Purpose: TECH-01 eliminates visual noise from deselected script targets. TECH-02 provides the expected Back navigation consistent with the DeploymentStep pattern. +Output: Modified ReviewStep.tsx only — no other files touched. + + + +@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/phases/05-tech-debt/05-CONTEXT.md +@.planning/phases/05-tech-debt/05-RESEARCH.md +@.planning/phases/05-tech-debt/05-00-SUMMARY.md + + + + +From src/store/types.ts: +```typescript +export const INITIAL_STATE: WizardState = { + deployment: { + scriptTargets: ['intune', 'rmm'], // default = both selected + }, +}; +export type WizardAction = + | { type: 'SET_STEP'; payload: number } + | { type: 'SET_DEPLOYMENT'; payload: Partial } + // ... +``` + +From src/store/context.tsx: +```typescript +export function useWizard(): { state: WizardState; dispatch: React.Dispatch } +``` + +Current ReviewStep.tsx signature: +```typescript +const { state } = useWizard(); // dispatch is NOT currently destructured — must add it +``` + +From CONTEXT.md — locked decisions: +- Intune maps to: intuneInstall + intuneDetection blocks +- RMM maps to: rmmScript block +- rclone.conf OutputBlock is ALWAYS shown regardless of scriptTargets +- Edge case: both deselected → only rclone.conf shown + ZIP has only rclone.conf +- Unmount entirely (NOT CSS-hidden) — no state to preserve between show/hide + +Back button pattern (from DeploymentStep.tsx): +```tsx +
+ +
+``` +Note: payload is 2 because DeploymentStep is step index 2. +
+
+ + + + + Task 1: Add scriptTargets filtering and Back button to ReviewStep + src/components/wizard/ReviewStep.tsx + + - showIntune = state.deployment.scriptTargets.includes('intune') — boolean + - showRmm = state.deployment.scriptTargets.includes('rmm') — boolean + - {showIntune && } — unmounted when false + - {showIntune && } — unmounted when false + - {showRmm && } — unmounted when false + - rclone.conf OutputBlock always rendered (no condition) + - handleDownloadZip builds files array dynamically: rclone.conf always, intune files if showIntune, rmm file if showRmm + - Back button renders at bottom (before ZIP button) with label 'Back', dispatches SET_STEP(2) on click + - useMemo calls for intuneInstall, intuneDetection, rmmScript are KEPT (used in ZIP handler even when hidden) + + + Edit src/components/wizard/ReviewStep.tsx: + + 1. Change `const { state } = useWizard();` to `const { state, dispatch } = useWizard();` + + 2. After the useMemo calls, add two boolean derivations: + ```tsx + const showIntune = state.deployment.scriptTargets.includes('intune'); + const showRmm = state.deployment.scriptTargets.includes('rmm'); + ``` + + 3. Replace handleDownloadZip with the dynamic version: + ```tsx + async function handleDownloadZip() { + const files: { name: string; content: string }[] = [ + { name: 'rclone.conf', content: rcloneConf }, + ]; + if (showIntune) { + files.push({ name: 'intune-install.ps1', content: intuneInstall }); + files.push({ name: 'intune-detection.ps1', content: intuneDetection }); + } + if (showRmm) { + files.push({ name: 'rmm-script.ps1', content: rmmScript }); + } + await downloadZip(files, 'rclone-deployment.zip'); + } + ``` + + 4. Wrap Intune OutputBlocks with showIntune condition (unmount entirely): + ```tsx + {showIntune && ( + + )} + {showIntune && ( + + )} + ``` + + 5. Wrap RMM OutputBlock with showRmm condition: + ```tsx + {showRmm && ( + + )} + ``` + + 6. Add Back button before the ZIP button, following DeploymentStep's established pattern: + ```tsx +
+ +
+ ``` + + Do NOT remove any existing comment lines (SECU-01, SECU-02, etc.). + Do NOT touch any useMemo calls — they are needed for ZIP handler even when the blocks are hidden. + The existing DOWN-05 test (both targets selected = 4 files in ZIP) still passes because INITIAL_STATE has scriptTargets=['intune','rmm']. + The existing DOWN-02, DOWN-03, DOWN-04 tests still pass because they use default state with both targets. + + Anti-patterns to avoid: + - Do NOT use CSS `hidden` class — must unmount (conditional render with &&) + - Do NOT wrap rclone.conf OutputBlock in a condition — it is always shown +
+ + cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run src/components/wizard/ReviewStep.test.tsx 2>&1 | tail -20 + + + All ReviewStep tests pass (green), including: + - Pre-existing CONF-02, CONF-03, DOWN-01 through DOWN-06, SECU-01, SECU-02 + - New TECH-01 filtering cases (intune-only, rmm-only, neither, ZIP-neither) + - New TECH-02 Back button case + Zero act() warnings in output (existing tests use fireEvent on checkbox/download buttons which do not trigger async state updates outside the component). + +
+ +
+ + +Run full suite to confirm ReviewStep is green and no regressions: +``` +cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run +``` +Expected: All ReviewStep tests green. TECH-01 and TECH-02 stubs from Plan 00 now pass. + + + +- ReviewStep conditionally renders Intune blocks based on showIntune boolean (conditional render, not CSS) +- ReviewStep conditionally renders RMM block based on showRmm boolean +- rclone.conf OutputBlock always renders +- handleDownloadZip builds files array filtered by showIntune/showRmm +- Back button renders and dispatches SET_STEP(2) via dispatch +- All pre-existing ReviewStep tests still pass (DOWN-02/03/04 positional index tests unaffected — default state has both targets) +- New TECH-01 and TECH-02 tests from Plan 00 now pass (GREEN state) + + + +After completion, create `.planning/phases/05-tech-debt/05-02-SUMMARY.md` + diff --git a/.planning/phases/05-tech-debt/05-03-PLAN.md b/.planning/phases/05-tech-debt/05-03-PLAN.md new file mode 100644 index 0000000..45f380c --- /dev/null +++ b/.planning/phases/05-tech-debt/05-03-PLAN.md @@ -0,0 +1,210 @@ +--- +phase: 05-tech-debt +plan: "03" +type: execute +wave: 2 +depends_on: + - "05-01" + - "05-02" +files_modified: + - src/components/wizard/BackendSelectionStep.test.tsx + - src/components/wizard/ReviewStep.test.tsx +autonomous: true +requirements: + - TECH-05 + +must_haves: + truths: + - "BackendSelectionStep test suite runs with zero act() warnings in Vitest output" + - "ReviewStep test suite runs with zero act() warnings (bonus — low-effort fix)" + - "All previously passing tests remain green after the fireEvent-to-userEvent migration" + artifacts: + - path: "src/components/wizard/BackendSelectionStep.test.tsx" + provides: "userEvent-based interactions replacing fireEvent" + contains: "userEvent.setup()" + - path: "src/components/wizard/ReviewStep.test.tsx" + provides: "vi.useFakeTimers() for OutputBlock setTimeout warnings" + contains: "vi.useFakeTimers" + key_links: + - from: "BackendSelectionStep.test.tsx" + to: "userEvent v14 API" + via: "const user = userEvent.setup(); await user.click()" + pattern: "userEvent\\.setup\\(\\)" +--- + + +Fix act() warnings in BackendSelectionStep.test.tsx by migrating from fireEvent to userEvent v14, and suppress ReviewStep act() warnings with vi.useFakeTimers() for the OutputBlock setTimeout. + +Purpose: TECH-05 — clean test output with zero act() warnings makes CI signal trustworthy and prevents false positives. +Output: Both test files use act()-safe patterns; no production code changes. + + + +@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/phases/05-tech-debt/05-CONTEXT.md +@.planning/phases/05-tech-debt/05-RESEARCH.md +@.planning/phases/05-tech-debt/05-01-SUMMARY.md +@.planning/phases/05-tech-debt/05-02-SUMMARY.md + + + + +userEvent v14 API (already installed: @testing-library/user-event 14.6.1): +```typescript +import userEvent from '@testing-library/user-event'; + +// Per test or per describe block +const user = userEvent.setup(); +// ... +await user.click(element); // replaces fireEvent.click(element) +await user.type(input, text); // replaces fireEvent.change(input, { target: { value: text } }) +``` + +The user setup() instance must be created INSIDE describe() or it() (not at module level) to avoid state leakage between tests. + +BackendSelectionStep.test.tsx — which fireEvent calls to migrate: +- fireEvent.change(nameInput, { target: { value: '...' } }) → await user.type(nameInput, '...') + NOTE: user.type() appends to existing value. If input has a defaultValue, use user.clear() first or set up user.type with full value. + Alternative: fireEvent.change is safe for setting values (no state updates) — only fireEvent.click triggers async state. Can keep fireEvent.change and only replace fireEvent.click. +- fireEvent.click(azureButton) → await user.click(azureButton) +- All it() callbacks that contain await user.click() MUST be async + +vi.useFakeTimers pattern for ReviewStep (for OutputBlock's setCopied setTimeout): +```typescript +import { beforeEach, afterEach, vi } from 'vitest'; + +beforeEach(() => { + vi.useFakeTimers(); + // Note: existing beforeEach in ReviewStep.test.tsx runs vi.clearAllMocks() and sets up stubs + // Add vi.useFakeTimers() call to the EXISTING beforeEach block (not a new one) +}); + +afterEach(() => { + vi.useRealTimers(); +}); +``` + +Root cause reference: +- BackendSelectionStep: fireEvent.click on a backend card → handleSubmit → async React Hook Form validation → dispatch → WizardProvider state update — all without act() wrapping +- ReviewStep: fireEvent.click on Copy button → setCopied(true) → setTimeout(() => setCopied(false), 2000) — timer fires after test assertion, producing act() warning from OutputBlock + + + + + + + Task 1: Migrate BackendSelectionStep.test.tsx from fireEvent to userEvent + src/components/wizard/BackendSelectionStep.test.tsx + + - All WIZD-01 and WIZD-04 tests continue to pass (same assertions, just act()-safe interactions) + - Vitest output for BackendSelectionStep.test.tsx contains zero occurrences of "act(" in warnings + - Tests that previously used await waitFor() still use it (async validation is still async) + - Tests that clicked backend cards use await user.click() and are async + + + Edit src/components/wizard/BackendSelectionStep.test.tsx: + + 1. Remove fireEvent from the @testing-library/react import (keep render, screen, waitFor) + 2. Add import: `import userEvent from '@testing-library/user-event';` + 3. For each it() that calls fireEvent.click on a backend card button, convert to userEvent pattern: + - Add `const user = userEvent.setup();` at the top of the it() body + - Make the it() callback async + - Replace `fireEvent.click(azureButton)` with `await user.click(azureButton)` + - Replace `fireEvent.change(nameInput, { target: { value: 'my-remote' } })` with `await user.clear(nameInput); await user.type(nameInput, 'my-remote');` + OR keep fireEvent.change for input value setting and only replace fireEvent.click (fireEvent.change does not trigger act()-needing state updates in this component — either approach is valid) + + Specific tests to migrate (all 5 tests in BackendSelectionStep.test.tsx that use fireEvent): + - WIZD-01: 'clicking a backend card dispatches SET_BACKEND_TYPE and SET_STEP' + - WIZD-04: 'shows inline error after first Next attempt with invalid name' + - WIZD-04: 'accepts alphanumeric, dashes, and underscores' + - WIZD-04: 'rejects names with spaces or special characters' + - WIZD-04: 'renders remote name input at the top of the step' (if it uses fireEvent — check) + + Tests that only use screen queries and no interactions (e.g., 'renders Azure Blob Storage card') do NOT need changes. + + Anti-patterns to avoid: + - Do NOT wrap fireEvent.click in act() manually — replace with userEvent instead + - Do NOT use legacy `userEvent.click()` shorthand — always use `userEvent.setup()` + `await user.click()` + - userEvent.setup() must be inside the test body, not at module level + + After changes, verify no fireEvent.click calls remain in the file. + + + cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run src/components/wizard/BackendSelectionStep.test.tsx 2>&1 | grep -E "act\(|PASS|FAIL|Tests" | head -20 + + + All BackendSelectionStep tests pass. + Zero lines containing "act(" appear in the warning output. + Grep for "act(" returns 0 matches in test output. + + + + + Task 2: Fix ReviewStep act() warnings with vi.useFakeTimers + src/components/wizard/ReviewStep.test.tsx + + Edit src/components/wizard/ReviewStep.test.tsx: + + 1. Add `afterEach` to the existing imports from vitest: `import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';` + + 2. Add `vi.useFakeTimers()` at the start of the existing `beforeEach` block (before vi.clearAllMocks()): + ```typescript + beforeEach(() => { + vi.useFakeTimers(); // ADD THIS LINE FIRST + vi.clearAllMocks(); + vi.mocked(downloadZip).mockResolvedValue(undefined); + vi.stubGlobal('navigator', { clipboard: { writeText: vi.fn().mockResolvedValue(undefined) } }); + vi.stubGlobal('URL', { createObjectURL: vi.fn(() => 'blob:mock'), revokeObjectURL: vi.fn() }); + }); + ``` + + 3. Add a new `afterEach` block after beforeEach: + ```typescript + afterEach(() => { + vi.useRealTimers(); + }); + ``` + + This suppresses the act() warning from OutputBlock's `setTimeout(() => setCopied(false), 2000)` — the timer is frozen during tests and never fires unexpectedly. + + No other changes needed — all existing assertions remain valid with fake timers active. + The new TECH-01 and TECH-02 tests from Plan 00 also benefit from this change (no warnings from their interactions either). + + + cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run src/components/wizard/ReviewStep.test.tsx 2>&1 | grep -E "act\(|Tests" | head -10 + + + All ReviewStep tests pass (including TECH-01 and TECH-02 new cases from Plan 00). + Zero lines containing "act(" appear in the test output. + + + + + + +Full suite must be entirely green with zero act() warnings: +``` +cd /c/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run 2>&1 | grep -E "act\(|Test Files|Tests" +``` +Expected: +- "Tests X passed (X)" with no failures +- Zero lines containing "act(" +- npx tsc --noEmit: zero errors + + + +- BackendSelectionStep.test.tsx uses userEvent.setup() + await user.click() for all card interactions +- ReviewStep.test.tsx uses vi.useFakeTimers() in beforeEach and vi.useRealTimers() in afterEach +- Full Vitest run produces zero "act(" warning lines +- All 5 requirements (TECH-01 through TECH-05) verified green in the full test suite +- npx tsc --noEmit: zero errors (phase gate) + + + +After completion, create `.planning/phases/05-tech-debt/05-03-SUMMARY.md` +