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` + + + +After completion, create `.planning/phases/07-validation-ux-polish/07-00-SUMMARY.md` + 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 + + + +After completion, create `.planning/phases/07-validation-ux-polish/07-01-SUMMARY.md` + 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

). +aria-label on the ⓘ button: `More info about ${field.label}` (for FieldRenderer/PasswordField) or 'More info about authentication methods' (for SftpAuthToggle). + +⓪ icon source: use the Unicode character ⓘ (text character — no Heroicons needed, no new dependency). + + + + + + + Task 1: Populate tooltipText in registry + add ⓘ to FieldRenderer and PasswordField + src/schemas/registry.ts, src/components/ui/FieldRenderer.tsx, src/components/ui/PasswordField.tsx + + - FieldRenderer renders a ⓘ button next to the label when field.tooltipText is present + - Clicking ⓘ shows the tooltip panel below the input + - Clicking ⓘ again hides the tooltip panel + - PasswordField renders the same ⓘ + panel when tooltipText prop is provided + - OneDrive token field (inputType: password, rendered via FieldRenderer → PasswordField) shows ⓘ + - Fields without tooltipText show no ⓘ (no empty button rendered) + + + Step 1 — Populate tooltipText in src/schemas/registry.ts for 3 fields: + + azureblob sas_url field — add: + ```typescript + tooltipText: 'A SAS URL (Shared Access Signature) bundles the storage endpoint with a time-limited, scope-limited token. It grants access only to containers you specify and expires automatically. Use this if you want limited-access credentials. If you have the full account key, switch to Access Key.', + ``` + + azureblob key field — add: + ```typescript + tooltipText: 'The full storage account key grants unrestricted read/write access to all containers in the account. Keep this secret. If you only need limited access, use a SAS URL instead.', + ``` + + onedrive token field — add: + ```typescript + tooltipText: 'This is the JSON token obtained by running `rclone authorize "onedrive"` on a machine with a browser. The command opens a browser window, you authenticate, and rclone prints a JSON token — paste that entire JSON blob here.', + ``` + + Step 2 — Update PasswordField (src/components/ui/PasswordField.tsx): + - Add `tooltipText?: string` to PasswordFieldProps interface + - Add `const [showTooltip, setShowTooltip] = useState(false)` inside the component + - Add import for useState if not already imported + - Wrap the existing `