- Add ThemeToggle segmented control (Light/Dark/System) with DOM class toggle
- Use getStored() lazy init and applyTheme() for side-effect-free render
- Stub localStorage and matchMedia in tests for Node v25 compatibility
- Wire ThemeToggle into App.tsx header flex row next to h1
- Change App outer div to bg-surface, h1 to text-on-surface
- All 7 ThemeToggle unit tests passing; 166 total tests green
- Test renders three buttons: Light, Dark, System
- Test default state is System when localStorage is empty
- Test clicking Dark adds .dark class to documentElement
- Test clicking Light removes .dark class from documentElement
- Test clicking Dark writes to localStorage key r2b-theme
- Test active/inactive aria-pressed state
- Test group has aria-label="Theme"
- Add @custom-variant dark directive for .dark class-based toggling
- Define 15 CSS custom property token pairs (light/dark) in @layer base
- Map all tokens to Tailwind utility classes via @theme with var() references
- Include warning/success tokens (amber + green) for ReviewStep coverage
- Add body transition with prefers-reduced-motion guard
- Add inline blocking script in index.html head to prevent FOUC
- AzureAuthToggle reads sas_url/key tooltipText from BACKEND_REGISTRY and passes to PasswordField
- SftpAuthToggle adds showAuthTip state with ⓘ button above segmented control
- All 159 tests pass including UX-01 tooltip toggle tests
- Populate tooltipText for azureblob.sas_url, azureblob.key, and onedrive.token in registry
- Add tooltipText prop and showTooltip state to PasswordField with toggle button + panel
- Add showTooltip state to FieldRenderer; pass tooltipText to PasswordField for password branch
- Add ⓘ button + panel for text/select branches in FieldRenderer
- Use sr-only span for accessible name to avoid getByLabelText collision
- Replace simple ternary assignment with let schema pattern
- Chain (schema as z.ZodString).regex() when field.validate is present
- Handle optional fields: z.string() first, then .regex() if needed, then .optional()
- VALID-01 tests all pass (GREEN): azureblob account, s3 region, gcs project_number
- Add SftpAuthToggle import
- Extend backendLabel record to cover onedrive/sftp/gcs/b2
- Add sftp branch with host/user FieldRenderers + SftpAuthToggle
- onedrive/gcs/b2 render via existing registry loop (no custom branch)
- Full Vitest suite green (147 tests); zero TypeScript errors
- Password and Private Key tabs using CSS-hidden segmented control
- Both pass and key_pem fields always registered with react-hook-form
- Password tab active by default (authMethod = 'password')
- Active tab: bg-blue-600 text-white; inactive: bg-white text-gray-700 hover:bg-gray-50
- Wired into RemoteConfigStep in Plan 03 (06-03-PLAN.md)
- Create 06-00-SUMMARY.md with RED state confirmation (27 failing, 49 green)
- Update STATE.md: progress 88%, decisions, session info
- Update ROADMAP.md: phase 6 in progress (1/4 summaries)
- Mark BACK-01 through BACK-04 complete in REQUIREMENTS.md
- EXPECTED_BACKENDS now includes onedrive, sftp, gcs, b2 (7 total)
- Added describe blocks for OneDrive, SFTP, GCS, Backblaze B2 field assertions
- 9 tests fail RED because registry.ts still has only 3 BackendType values
Re-ran npx vitest run: 12/12 suites pass, 104/104 tests pass, zero act() warnings.
The Vitest v4 runner-initialization failure in the initial verification report was
specific to the verifier agent context and does not reproduce in developer environment.
No code changes needed. Phase 5 is fully complete (15/15 must-haves verified).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add afterEach to vitest imports
- Add vi.useFakeTimers() at the start of beforeEach block
- Add afterEach(() => vi.useRealTimers()) to restore timers after each test
- Freezes OutputBlock's setTimeout(setCopied(false), 2000) during tests
- Replace fireEvent import with userEvent from @testing-library/user-event
- Add const user = userEvent.setup() inside each test body using interactions
- Replace fireEvent.click(azureButton) with await user.click(azureButton)
- Replace fireEvent.change(nameInput) with await user.clear() + await user.type()
- Make all interaction tests async
- Destructure dispatch from useWizard() alongside state
- Derive showIntune and showRmm booleans from state.deployment.scriptTargets
- Conditionally unmount Intune OutputBlocks (showIntune) and RMM OutputBlock (showRmm)
- rclone.conf OutputBlock always rendered regardless of scriptTargets
- handleDownloadZip builds files array dynamically — filters by showIntune/showRmm
- Add Back button dispatching SET_STEP(2) following DeploymentStep pattern
- All 15 ReviewStep tests green including new TECH-01 and TECH-02 cases
- Create 05-00-SUMMARY.md with RED state documentation
- Update STATE.md with plan progress and decisions
- Update ROADMAP.md phase 5 progress (1/4 plans complete)
- Mark TECH-01, TECH-02, TECH-03 requirements complete in REQUIREMENTS.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update BACKEND_REGISTRY[backend].length → .fields.length
- Update iteration to use BACKEND_REGISTRY[backend].fields
- Update field lookups (.find/.map) to go through .fields
- Add new test: each backend has displayName and description metadata (RED)
- Import ReviewStep in App.tsx and add as steps[3]
- Update STEP_LABELS in StepIndicator.tsx to include 'Review' as fourth entry
- Update comment: 1.Backend > 2.Remote Config > 3.Deployment > 4.Review
- Full npm test suite (98 tests, 12 files) remains GREEN
- 04-03-SUMMARY.md: documents implementation, deviations, and test fixes
- STATE.md: advanced to plan 04, recorded metrics and decisions
- ROADMAP.md: updated phase 4 progress (3/5 summaries)
- ReviewStep.tsx: live rclone.conf preview via useMemo+try/catch (CONF-02)
- Security acknowledgement checkbox gates all download/copy buttons (SECU-01)
- 'No data is sent to any server' privacy notice (SECU-02)
- 4 OutputBlocks with download (DOWN-01–04) and copy (DOWN-06, CONF-03)
- Download All (ZIP) button calls downloadZip with all 4 file entries (DOWN-05)
- Replaced all expect.fail stubs with real assertions — 10/10 tests GREEN
- Added vi to import for spy capability
- SECU-03: dispatches all action types, asserts setItem never called on
Storage.prototype (covers both localStorage and sessionStorage)
- All 9 reducer tests GREEN
- App.tsx: WizardShell routes to BackendSelectionStep/RemoteConfigStep/DeploymentStep
- App.tsx: StepIndicator renders above every step as persistent breadcrumb
- App.tsx: RemoteConfigStep keyed on backendType to force remount on backend change
- App.test.tsx: all 3 WIZD-02 tests GREEN (step 0, 1, 2 routing verified)
- Full suite: 87 tests GREEN across 11 test files
- WIZD-03: clicking completed step dispatches SET_STEP
- WIZD-03: clicking back to step 0 dispatches SET_REMOTE_PARAMS({})
- WIZD-03: clicking back to step 0 does NOT dispatch RESET
- WIZD-03: step 0 shows as active when currentStep is 0
- WIZD-03: completed steps are clickable
- BACK-01: Azure Blob form with account FieldRenderer + AzureAuthToggle
- BACK-02: S3 form via BACKEND_REGISTRY loop (provider hidden, region required)
- BACK-03: S3-compatible form includes endpoint field
- mode: onSubmit / reValidateMode: onChange for touch-then-live validation
- Guard: useEffect redirects to step 0 if backendType is null
- All hooks called unconditionally (fallback schema prevents rules-of-hooks violation)
- 8/8 BACK-01/02/03 tests GREEN
- BACK-01: Azure Blob form with account field + AzureAuthToggle
- BACK-02: S3 form fields (access_key_id, secret_access_key, region)
- BACK-03: S3-compatible form includes endpoint field
- renderWithBackend helper dispatches SET_BACKEND_TYPE before render
- @testing-library/user-event installed for value retention test
- Replace expect.fail stubs with real test assertions
- Tests cover card render order, card click dispatch, name validation
- RED: component file does not exist yet
- FieldRenderer: registry-driven input renderer (text, password, select, hidden provider)
- AzureAuthToggle: segmented SAS/Key toggle, defaults to SAS, both fields always registered via CSS show/hide
- Both fields preserved on toggle (no conditional rendering that would lose react-hook-form state)