diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 610fa31..359fc8c 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -74,7 +74,12 @@ Plans:
2. User who enters an S3 region in an invalid format (e.g., spaces or uppercase) sees an inline error message
3. User can hover or click a tooltip icon on sensitive fields (SAS token, access key, SFTP auth method, OneDrive token) and read a plain-language explanation without navigating away
4. Tooltip content accurately distinguishes SAS token from storage account key in the Azure backend form
-**Plans**: TBD
+**Plans**: 3 plans
+
+Plans:
+- [ ] 07-00-PLAN.md — Wave 0 TDD stubs: failing tests for VALID-01 regex rejection/acceptance and UX-01 tooltip toggle
+- [ ] 07-01-PLAN.md — VALID-01: FieldDef validate extension + buildZodSchema regex chaining for 3 fields
+- [ ] 07-02-PLAN.md — UX-01: tooltipText in registry + FieldRenderer/PasswordField ⓘ toggle + AzureAuthToggle/SftpAuthToggle wiring + human verification
## Progress
@@ -86,4 +91,4 @@ Plans:
| 4. Review, Download & Security | v1.0 | 5/5 | Complete | 2026-03-27 |
| 5. Tech Debt | 4/4 | Complete | 2026-03-30 | - |
| 6. New Backends | 4/4 | Complete | 2026-03-31 | - |
-| 7. Validation & UX Polish | v1.1 | 0/TBD | Not started | - |
+| 7. Validation & UX Polish | v1.1 | 0/3 | Not started | - |
diff --git a/.planning/phases/07-validation-ux-polish/07-00-PLAN.md b/.planning/phases/07-validation-ux-polish/07-00-PLAN.md
new file mode 100644
index 0000000..9bc89bd
--- /dev/null
+++ b/.planning/phases/07-validation-ux-polish/07-00-PLAN.md
@@ -0,0 +1,140 @@
+---
+phase: 07-validation-ux-polish
+plan: "00"
+type: tdd
+wave: 1
+depends_on: []
+files_modified:
+ - src/components/wizard/RemoteConfigStep.test.tsx
+autonomous: true
+requirements:
+ - VALID-01
+ - UX-01
+
+must_haves:
+ truths:
+ - "VALID-01 failing tests exist for Azure account name regex rejection and acceptance"
+ - "VALID-01 failing tests exist for S3 region regex rejection and acceptance"
+ - "VALID-01 failing tests exist for GCS project_number regex rejection and acceptance"
+ - "UX-01 failing tests exist for ⓘ button presence on sas_url, azureblob key, onedrive token, and SFTP auth method"
+ - "UX-01 failing tests exist for tooltip toggle behavior (click shows, click again hides)"
+ - "All existing 147 tests still pass after stub additions"
+ artifacts:
+ - path: "src/components/wizard/RemoteConfigStep.test.tsx"
+ provides: "Failing test stubs for VALID-01 and UX-01"
+ contains: "describe.*VALID-01|describe.*UX-01"
+ key_links:
+ - from: "RemoteConfigStep.test.tsx VALID-01 block"
+ to: "azureblob account field validation"
+ via: "userEvent.type + form submit + screen.getByText(error message)"
+ - from: "RemoteConfigStep.test.tsx UX-01 block"
+ to: "ⓘ button toggle"
+ via: "getByRole('button', { name: /more info/i }) + userEvent.click"
+---
+
+
+Write failing test stubs for VALID-01 (format validation) and UX-01 (tooltip toggle) in the existing RemoteConfigStep test file.
+
+Purpose: Establish the RED state before implementation — tests fail because the validate property and tooltipText property don't exist yet.
+Output: Augmented RemoteConfigStep.test.tsx with 11 new failing its; all 147 existing tests still pass.
+
+
+
+@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/07-validation-ux-polish/07-CONTEXT.md
+@.planning/phases/07-validation-ux-polish/07-RESEARCH.md
+
+
+
+
+From src/schemas/registry.ts (current — no validate or tooltipText yet):
+```typescript
+export type BackendType = 'azureblob' | 's3' | 's3-compatible' | 'onedrive' | 'sftp' | 'gcs' | 'b2';
+
+export interface FieldDef {
+ key: string;
+ label: string;
+ inputType: 'text' | 'password' | 'select' | 'toggle';
+ required: boolean;
+ placeholder?: string;
+ helpText?: string;
+ options?: { value: string; label: string }[];
+ // NOTE: validate? and tooltipText? do NOT exist yet — that's why tests will fail
+}
+```
+
+From src/schemas/index.ts (current — no regex chaining yet):
+```typescript
+function buildZodSchema(backendType: BackendType): z.ZodObject>
+// Current loop: z.string().min(1, ...) OR z.string().optional()
+// No .regex() chaining yet — that's why VALID-01 tests fail
+```
+
+Test file patterns already established (from Phase 5/6):
+- userEvent.setup() per test body
+- vi.useFakeTimers() in beforeEach + vi.useRealTimers() in afterEach
+- WizardConsumerSetup pattern: in-test React component dispatches SET_DEPLOYMENT via useEffect
+- Label text used for assertions, not filename text
+
+
+
+
+ Wave 0 TDD stubs — VALID-01 and UX-01
+ src/components/wizard/RemoteConfigStep.test.tsx
+
+ VALID-01 — Azure account name:
+ - it('rejects account name with uppercase letters') → submit form with account='MyStorage' → expect error 'Must be 3–24 lowercase alphanumeric characters'
+ - it('rejects account name shorter than 3 characters') → submit form with account='ab' → expect same error
+ - it('accepts valid account name') → submit form with account='mystorageaccount' → expect NO format error
+
+ VALID-01 — S3 region:
+ - it('rejects S3 region with invalid format (spaces)') → submit with region='us east 1' → expect error text (e.g. 'valid AWS region format')
+ - it('accepts valid S3 region') → submit with region='us-east-1' → expect no error
+
+ VALID-01 — GCS project_number:
+ - it('rejects GCS project_number with non-digits') → submit with project_number='abc' → expect error (e.g. 'digits only')
+ - it('accepts valid GCS project_number') → submit with project_number='123456789' → expect no error
+
+ UX-01 — Tooltip toggle:
+ - it('renders ⓘ button on azureblob sas_url field') → render azureblob step → expect button with aria-label matching /more info about SAS URL/i
+ - it('shows tooltip panel when ⓘ is clicked on sas_url') → click ⓘ → expect tooltip text visible
+ - it('hides tooltip panel when ⓘ is clicked again') → click twice → expect tooltip text not visible
+ - it('renders ⓘ button on SFTP auth method section') → render sftp step → expect button with aria-label matching /more info about authentication/i
+ - it('renders ⓘ button on OneDrive token field') → render onedrive step → expect button with aria-label matching /more info about/i on token field
+
+ All new tests: write them to FAIL now (the ⓘ button doesn't exist, regex validation doesn't exist).
+ Existing 147 tests: MUST still pass after adding these stubs.
+
+
+ Add two new describe blocks to the existing RemoteConfigStep.test.tsx:
+ 1. `describe('VALID-01 — format validation')` with 7 its (3 backends × ~2-3 tests each)
+ 2. `describe('UX-01 — contextual tooltips')` with 4 its (button presence + toggle behavior)
+
+ Use the existing test setup patterns already in the file (WizardConsumerSetup, userEvent.setup(), vi.useFakeTimers in beforeEach).
+
+ Confirm all tests run by running the suite — expect 11 new failures + 147 existing passes.
+
+
+
+
+Run after writing stubs:
+1. `npx vitest run src/components/wizard/RemoteConfigStep.test.tsx` — expect 11 new FAILs (RED state confirmed), 0 regressions on existing tests
+2. `npx vitest run` — expect all 147 pre-existing tests still pass (no collateral breakage)
+
+
+
+- 11 new failing tests added (RED state for VALID-01 and UX-01)
+- 0 pre-existing test regressions
+- Test file committed with message: `test(07-00): add failing stubs for VALID-01 and UX-01`
+
+
+
diff --git a/.planning/phases/07-validation-ux-polish/07-01-PLAN.md b/.planning/phases/07-validation-ux-polish/07-01-PLAN.md
new file mode 100644
index 0000000..96fc585
--- /dev/null
+++ b/.planning/phases/07-validation-ux-polish/07-01-PLAN.md
@@ -0,0 +1,209 @@
+---
+phase: 07-validation-ux-polish
+plan: "01"
+type: execute
+wave: 2
+depends_on:
+ - "07-00"
+files_modified:
+ - src/schemas/registry.ts
+ - src/schemas/index.ts
+autonomous: true
+requirements:
+ - VALID-01
+
+must_haves:
+ truths:
+ - "User who enters 'MyStorage' in the Azure account name field sees an inline error before advancing"
+ - "User who enters 'us east 1' in the S3 region field sees an inline error"
+ - "User who enters 'abc' in the GCS project_number field sees an inline error"
+ - "Valid values ('mystorageaccount', 'us-east-1', '123456789') produce no format error"
+ - "Empty required fields still show 'required' error (not the regex error) — empty string hits min(1) before regex"
+ - "All 147 pre-existing tests still pass after adding validation"
+ artifacts:
+ - path: "src/schemas/registry.ts"
+ provides: "FieldDef interface with validate property; 3 registry entries with validate rules"
+ contains: "validate\\?: \\{ regex: RegExp; message: string \\}"
+ - path: "src/schemas/index.ts"
+ provides: "buildZodSchema() chains .regex() when field.validate is present"
+ contains: "field\\.validate"
+ key_links:
+ - from: "BACKEND_REGISTRY azureblob.account"
+ to: "buildZodSchema('azureblob')"
+ via: "field.validate.regex applied as .regex() on ZodString"
+ pattern: "field\\.validate"
+ - from: "buildZodSchema()"
+ to: "BACKEND_SCHEMAS"
+ via: "Zod schema exported, consumed by zodResolver in RemoteConfigStep"
+---
+
+
+Implement VALID-01: extend FieldDef with a validate property, add regex rules to 3 registry entries (azureblob account, s3 region, gcs project_number), and extend buildZodSchema() to chain .regex() when the property is present.
+
+Purpose: Users see inline format errors on malformed credential fields before they can advance in the wizard.
+Output: Modified registry.ts (FieldDef + 3 validate rules) and index.ts (buildZodSchema regex chaining).
+
+
+
+@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/07-validation-ux-polish/07-CONTEXT.md
+@.planning/phases/07-validation-ux-polish/07-RESEARCH.md
+@.planning/phases/07-validation-ux-polish/07-00-SUMMARY.md
+
+
+
+
+Current FieldDef (src/schemas/registry.ts):
+```typescript
+export interface FieldDef {
+ key: string;
+ label: string;
+ inputType: 'text' | 'password' | 'select' | 'toggle';
+ required: boolean;
+ placeholder?: string;
+ helpText?: string;
+ options?: { value: string; label: string }[];
+ // validate and tooltipText DO NOT EXIST YET
+}
+```
+
+Current buildZodSchema (src/schemas/index.ts):
+```typescript
+function buildZodSchema(backendType: BackendType): z.ZodObject> {
+ const fields = BACKEND_REGISTRY[backendType].fields;
+ const shape: Record = {};
+ for (const field of fields) {
+ shape[field.key] = field.required
+ ? z.string().min(1, `${field.label} is required`)
+ : z.string().optional();
+ }
+ return z.object(shape);
+}
+```
+
+Zod v4 important: cast to (schema as z.ZodString).regex(...) because schema variable is typed ZodTypeAny.
+Zod v4 chaining order: .min(1).regex() is correct for required fields. .regex().optional() for optional (not needed here — all 3 validated fields are required).
+
+
+
+
+
+
+ Task 1: Extend FieldDef and add validate rules to 3 registry entries
+ src/schemas/registry.ts
+
+ - Adding validate to azureblob.account and submitting 'ABC' produces Zod error 'Must be 3–24 lowercase alphanumeric characters'
+ - Adding validate to s3.region and submitting 'us east 1' produces Zod error matching region format
+ - Adding validate to gcs.project_number and submitting 'abc' produces Zod error 'digits only' (or similar)
+ - Submitting '' on account still produces the required error (not the regex error) — min(1) fires first
+
+
+ 1. Add two optional properties to FieldDef interface:
+ ```typescript
+ validate?: { regex: RegExp; message: string };
+ tooltipText?: string; // also add here for Plan 02 — it's just an interface addition, no behavior yet
+ ```
+ Note: Adding tooltipText here avoids a second interface-only edit in Plan 02.
+
+ 2. In BACKEND_REGISTRY, locate the azureblob `account` field entry and add:
+ ```typescript
+ validate: {
+ regex: /^[a-z0-9]{3,24}$/,
+ message: 'Must be 3–24 lowercase alphanumeric characters (no hyphens or uppercase)',
+ },
+ ```
+
+ 3. Locate the s3 `region` field entry and add:
+ ```typescript
+ validate: {
+ regex: /^[a-z][a-z0-9-]+[a-z0-9]$/,
+ message: 'Must be a valid AWS region format (e.g. us-east-1)',
+ },
+ ```
+
+ 4. Locate the gcs `project_number` field entry and add:
+ ```typescript
+ validate: {
+ regex: /^\d+$/,
+ message: 'Must contain digits only',
+ },
+ ```
+
+ No other fields get validate rules — user decision is explicit on scope.
+
+
+ npx vitest run src/components/wizard/RemoteConfigStep.test.tsx
+
+ FieldDef has validate and tooltipText properties. 3 registry entries have validate rules. TypeScript compiles without errors (npx tsc --noEmit).
+
+
+
+ Task 2: Extend buildZodSchema() to chain .regex() from field.validate
+ src/schemas/index.ts
+
+ - buildZodSchema('azureblob') produces a schema where the account field rejects 'ABC' with the registry message
+ - buildZodSchema('s3') produces a schema where the region field rejects 'us east 1'
+ - buildZodSchema('gcs') produces a schema where project_number rejects 'abc'
+ - buildZodSchema('onedrive') and others without validate are unaffected
+ - VALID-01 failing tests from Plan 00 now PASS
+
+
+ Replace the buildZodSchema loop body with the regex-aware version:
+
+ ```typescript
+ function buildZodSchema(backendType: BackendType): z.ZodObject> {
+ const fields = BACKEND_REGISTRY[backendType].fields;
+ const shape: Record = {};
+ for (const field of fields) {
+ let schema: z.ZodTypeAny = field.required
+ ? z.string().min(1, `${field.label} is required`)
+ : z.string();
+
+ if (field.validate) {
+ schema = (schema as z.ZodString).regex(field.validate.regex, field.validate.message);
+ }
+
+ if (!field.required) {
+ schema = (schema as z.ZodString).optional();
+ }
+
+ shape[field.key] = schema;
+ }
+ return z.object(shape);
+ }
+ ```
+
+ Critical: cast to (schema as z.ZodString) before .regex() — ZodTypeAny does not expose .regex() in TypeScript but it is present at runtime. This pattern is verified.
+ Do NOT touch BACKEND_SCHEMAS export or anything else in the file.
+
+
+ npx vitest run src/components/wizard/RemoteConfigStep.test.tsx
+
+ VALID-01 tests from Plan 00 all pass (GREEN). Full suite still at 147+ pass with 0 failures: `npx vitest run`.
+
+
+
+
+
+1. `npx tsc --noEmit` — no TypeScript errors
+2. `npx vitest run src/components/wizard/RemoteConfigStep.test.tsx` — VALID-01 tests pass (GREEN), UX-01 stubs still fail (expected — UX-01 is Plan 02)
+3. `npx vitest run` — all pre-existing 147 tests pass, no regressions
+
+
+
+- FieldDef has validate and tooltipText properties
+- 3 registry entries (azureblob.account, s3.region, gcs.project_number) have validate rules with the exact regexes from CONTEXT.md
+- buildZodSchema() chains .regex() when field.validate is present
+- VALID-01 test stubs from Plan 00 all pass
+- Full test suite passes
+
+
+
diff --git a/.planning/phases/07-validation-ux-polish/07-02-PLAN.md b/.planning/phases/07-validation-ux-polish/07-02-PLAN.md
new file mode 100644
index 0000000..78d9d83
--- /dev/null
+++ b/.planning/phases/07-validation-ux-polish/07-02-PLAN.md
@@ -0,0 +1,316 @@
+---
+phase: 07-validation-ux-polish
+plan: "02"
+type: execute
+wave: 3
+depends_on:
+ - "07-01"
+files_modified:
+ - src/schemas/registry.ts
+ - src/components/ui/FieldRenderer.tsx
+ - src/components/ui/PasswordField.tsx
+ - src/components/wizard/SftpAuthToggle.tsx
+ - src/components/wizard/AzureAuthToggle.tsx
+autonomous: false
+requirements:
+ - UX-01
+
+must_haves:
+ truths:
+ - "User can click ⓘ next to the SAS URL label and read an explanation of SAS URL vs access key"
+ - "User can click ⓘ next to the Access Key label and read an explanation of full account key access"
+ - "User can click ⓘ on the SFTP authentication method section and read the difference between password and key-based auth"
+ - "User can click ⓘ next to the OneDrive token label and read how to obtain the JSON token"
+ - "Clicking ⓘ again hides the tooltip panel (toggle behavior)"
+ - "All 147+ tests (including VALID-01 from Plan 01) pass after tooltip implementation"
+ artifacts:
+ - path: "src/schemas/registry.ts"
+ provides: "tooltipText populated for azureblob.sas_url, azureblob.key, onedrive.token"
+ contains: "tooltipText:"
+ - path: "src/components/ui/FieldRenderer.tsx"
+ provides: "ⓘ button + inline tooltip panel when field.tooltipText is present"
+ contains: "showTooltip"
+ - path: "src/components/ui/PasswordField.tsx"
+ provides: "ⓘ button + inline tooltip panel when tooltipText prop is present"
+ contains: "tooltipText"
+ - path: "src/components/wizard/SftpAuthToggle.tsx"
+ provides: "ⓘ button above auth method toggle with inline explanation"
+ contains: "showAuthTip"
+ - path: "src/components/wizard/AzureAuthToggle.tsx"
+ provides: "Passes tooltipText from registry to PasswordField for sas_url and key"
+ contains: "tooltipText"
+ key_links:
+ - from: "BACKEND_REGISTRY azureblob.sas_url.tooltipText"
+ to: "AzureAuthToggle → PasswordField tooltipText prop"
+ via: "BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'sas_url')?.tooltipText"
+ pattern: "BACKEND_REGISTRY\\.azureblob\\.fields\\.find"
+ - from: "BACKEND_REGISTRY onedrive.token.tooltipText"
+ to: "FieldRenderer ⓘ button rendered"
+ via: "field.tooltipText present → useState showTooltip"
+ - from: "SftpAuthToggle showAuthTip state"
+ to: "inline explanation panel"
+ via: "useState + conditional render"
+---
+
+
+Implement UX-01: add tooltipText data to 3 registry fields, render ⓘ toggle buttons in FieldRenderer and PasswordField, wire AzureAuthToggle to pass tooltipText from the registry, and add a standalone auth-method tooltip to SftpAuthToggle.
+
+Purpose: Users can access plain-language explanations of sensitive or confusing credential fields without leaving the wizard.
+Output: 5 modified files; ⓘ buttons visible on sas_url, key, onedrive token, and SFTP auth method.
+
+
+
+@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/07-validation-ux-polish/07-CONTEXT.md
+@.planning/phases/07-validation-ux-polish/07-RESEARCH.md
+@.planning/phases/07-validation-ux-polish/07-01-SUMMARY.md
+
+
+
+
+After Plan 01, FieldDef in src/schemas/registry.ts:
+```typescript
+export interface FieldDef {
+ key: string;
+ label: string;
+ inputType: 'text' | 'password' | 'select' | 'toggle';
+ required: boolean;
+ placeholder?: string;
+ helpText?: string;
+ options?: { value: string; label: string }[];
+ validate?: { regex: RegExp; message: string };
+ tooltipText?: string; // Added in Plan 01 — now populate it for tooltip fields
+}
+```
+
+Current PasswordField props (src/components/ui/PasswordField.tsx) — to add tooltipText?:
+```typescript
+interface PasswordFieldProps {
+ id: string;
+ label: string;
+ error?: FieldError;
+ registration: UseFormRegisterReturn;
+ placeholder?: string;
+ helpText?: string;
+ // tooltipText?: string ← add this
+}
+```
+
+CRITICAL pitfall: azureblob.sas_url and azureblob.key are NOT rendered through the FieldRenderer registry loop.
+They are rendered inside AzureAuthToggle.tsx which calls PasswordField with hardcoded props.
+To wire the tooltip, AzureAuthToggle must read from BACKEND_REGISTRY:
+ BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'sas_url')?.tooltipText
+ BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'key')?.tooltipText
+Then pass those strings as tooltipText prop to the respective PasswordField calls.
+
+SFTP auth method tooltip is NOT field-level — it's a standalone explanation above the segmented control.
+SftpAuthToggle manages its own useState for showAuthTip.
+The PasswordField calls inside SftpAuthToggle for 'pass' and 'key_pem' do NOT get tooltipText.
+
+CSS-hidden toggle pattern is established project convention (AzureAuthToggle, SftpAuthToggle use div.block/div.hidden).
+CONTEXT.md decision: use CSS-hidden pattern (div.hidden / div.block) for tooltip panel visibility — not conditional render with &&.
+Actually re-read: CONTEXT.md says "toggle state is local to the field component (no global state needed)" and research shows
+both CSS-hidden and conditional render work; CSS-hidden is the project convention. Use div.hidden / div.block OR useState + conditional render ({showTooltip &&
...}).
+Research Pattern 3 shows conditional render. Either is fine — use Claude's discretion for tooltip panel; CSS-hidden for SftpAuthToggle to stay consistent with its existing pattern.
+
+Accessibility: the ⓘ button MUST be outside (sibling to, not inside) the