- Test gdrive: GdriveAuthToggle with OAuth Token and Service Account tabs
- Test dropbox: OAuthInstructions toggle button visible
- Test ftp: host, username, password, port, TLS mode fields
- Test webdav: URL, username, password, vendor select fields
- Test smb: host and username fields
- Test http: only URL field, no username/password
- BackendCard accepts optional icon prop rendered in flex row with name
- BackendSelectionStep groups 18 backends into 3 category sections
- Search bar (type=search/searchbox role) filters across displayName, description, category label, field labels
- Empty categories hidden via conditional rendering
- BACKEND_ICONS wired to BackendCard icon prop
- Changed search input to type=search to preserve getByRole('textbox') selector in existing tests
- GdriveAuthToggle: two-tab segmented control (OAuth Token / Service Account)
following AzureAuthToggle pattern; both fields always registered via CSS
hidden/block; OAuth tab embeds OAuthInstructions component
- BackendIcons: inline SVG icon map for all 18 backends (Partial<Record<BackendType,...>>)
using fill="currentColor" for MD3 dark-mode compatibility, no external deps
- Created src/components/icons/ directory
- Added bg-surface-container and text-on-surface semantic token classes to select element className
- Fixes select dropdown invisible text in dark mode (browser-applied white background)
- 204 tests pass, zero regressions
- BackendSelectionStep: add onInvalidSubmit scrolling to remote-name input
- BackendSelectionStep: pass onInvalidSubmit to form onSubmit and handleCardClick
- RemoteConfigStep: add FieldErrors import from react-hook-form
- RemoteConfigStep: add onInvalidSubmit scrolling to first errored field by key
- RemoteConfigStep: pass onInvalidSubmit to form onSubmit
- Add scrollIntoView mock (vi.fn()) in beforeEach to both test files
- Add POLISH-04 test in BackendSelectionStep: scrolls to remote-name on empty submit
- Add POLISH-04 test in RemoteConfigStep: scrolls to first errored field on empty submit
- md3-buttons.ts: upgrade ring-2/50 to ring-3 on all three constants (FILLED, OUTLINED, TEXT)
- BackendCard: add focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-primary
- StepIndicator: upgrade group-focus-visible completed-step circle from ring-2/50 to ring-3
- ThemeToggle: add focus-visible ring-3 with ring-inset to both selected/unselected branches
- FieldRenderer: select uses focus-visible:ring-2 (was focus:ring-2); tooltip buttons get focus-visible:ring-2 rounded
- BackendSelectionStep: responsive grid (1-col mobile, 2-col desktop) for cards; button row stacks vertically on mobile
- BackendCard: add w-full to fill grid cell on all viewports
- RemoteConfigStep/DeploymentStep/ReviewStep: flex-col sm:flex-row button rows; w-full sm:w-auto on Back/Next buttons
- StepIndicator: add hidden sm:block to all three label spans (completed, active, future)
- Add useState import to WizardShell
- Add showIntro local state (default true) — no persistence, resets on reload
- Add IntroSection component with action-first copy and Get Started CTA
- Copy mentions Azure Blob, S3, OneDrive, and 4 more backends
- Explains rclone.conf + deployment script generation
- Uses MD3_BTN_FILLED for CTA, MD3 tokens throughout
- When showIntro false, renders existing StepIndicator + step content unchanged
- Stateless component accepting value prop (string)
- Empty/whitespace state: shows grayed [my-remote] placeholder + guidance text
- Value state: shows [typed-value] in on-surface-variant color
- Renders only div/span elements — no form controls
- 9 tests covering empty state, value state, and element type constraints
- FieldRenderer: 4 new tests for hover show/hide, click-pin, click-unpin
- TextFieldMD3: 3 new tests for helpTextPrefix with helpText, without, and prefix-only
- Import fireEvent from @testing-library/react added to FieldRenderer.test.tsx
- TextFieldMD3: add helpTextPrefix prop rendered inline left of helpText
- Guard changed to (helpText || helpTextPrefix) so icon renders without helpText
- FieldRenderer: move tooltip icon from above field to inline with helpText via helpTextPrefix
- FieldRenderer: add hoverTooltip state; tooltip visible on mouseEnter, dismisses on mouseLeave
- FieldRenderer: click-to-unpin clears hoverTooltip to prevent sticky tooltip after dismiss
- PasswordField: same tooltip fix as FieldRenderer text-branch
- Select-branch also gains hover support
- Replace text breadcrumbs with numbered circles + connector lines
- Completed steps show checkmark, are clickable buttons with label text
- Active step highlighted with border-primary and bg-primary/10
- Future steps muted with border-outline and text-on-surface-container/40
- Connector lines filled bg-primary for completed, bg-outline for future
- All inline style={{}} props removed, no hardcoded color values
- handleStepClick logic preserved: SET_REMOTE_PARAMS({}) before SET_STEP(0)
- All 5 WIZD-03 tests pass without modification
- 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)
- 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
- 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