docs(07): create phase plan — 3 plans for VALID-01 and UX-01
This commit is contained in:
@@ -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
|
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
|
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
|
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
|
## Progress
|
||||||
|
|
||||||
@@ -86,4 +91,4 @@ Plans:
|
|||||||
| 4. Review, Download & Security | v1.0 | 5/5 | Complete | 2026-03-27 |
|
| 4. Review, Download & Security | v1.0 | 5/5 | Complete | 2026-03-27 |
|
||||||
| 5. Tech Debt | 4/4 | Complete | 2026-03-30 | - |
|
| 5. Tech Debt | 4/4 | Complete | 2026-03-30 | - |
|
||||||
| 6. New Backends | 4/4 | Complete | 2026-03-31 | - |
|
| 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 | - |
|
||||||
|
|||||||
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Key types and contracts extracted from codebase. Executor uses these directly. -->
|
||||||
|
|
||||||
|
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<Record<string, z.ZodTypeAny>>
|
||||||
|
// 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
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<feature>
|
||||||
|
<name>Wave 0 TDD stubs — VALID-01 and UX-01</name>
|
||||||
|
<files>src/components/wizard/RemoteConfigStep.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
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.
|
||||||
|
</behavior>
|
||||||
|
<implementation>
|
||||||
|
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.
|
||||||
|
</implementation>
|
||||||
|
</feature>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
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)
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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`
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/07-validation-ux-polish/07-00-SUMMARY.md`
|
||||||
|
</output>
|
||||||
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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).
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Extracted from codebase. Executor uses these directly. -->
|
||||||
|
|
||||||
|
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<Record<string, z.ZodTypeAny>> {
|
||||||
|
const fields = BACKEND_REGISTRY[backendType].fields;
|
||||||
|
const shape: Record<string, z.ZodTypeAny> = {};
|
||||||
|
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).
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Extend FieldDef and add validate rules to 3 registry entries</name>
|
||||||
|
<files>src/schemas/registry.ts</files>
|
||||||
|
<behavior>
|
||||||
|
- 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
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
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.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npx vitest run src/components/wizard/RemoteConfigStep.test.tsx</automated>
|
||||||
|
</verify>
|
||||||
|
<done>FieldDef has validate and tooltipText properties. 3 registry entries have validate rules. TypeScript compiles without errors (npx tsc --noEmit).</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 2: Extend buildZodSchema() to chain .regex() from field.validate</name>
|
||||||
|
<files>src/schemas/index.ts</files>
|
||||||
|
<behavior>
|
||||||
|
- 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
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
Replace the buildZodSchema loop body with the regex-aware version:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function buildZodSchema(backendType: BackendType): z.ZodObject<Record<string, z.ZodTypeAny>> {
|
||||||
|
const fields = BACKEND_REGISTRY[backendType].fields;
|
||||||
|
const shape: Record<string, z.ZodTypeAny> = {};
|
||||||
|
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.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npx vitest run src/components/wizard/RemoteConfigStep.test.tsx</automated>
|
||||||
|
</verify>
|
||||||
|
<done>VALID-01 tests from Plan 00 all pass (GREEN). Full suite still at 147+ pass with 0 failures: `npx vitest run`.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
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
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/07-validation-ux-polish/07-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Extracted from codebase + Plan 01 additions. Executor uses these directly. -->
|
||||||
|
|
||||||
|
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 && <p>...}).
|
||||||
|
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 <label> element to avoid label text contamination.
|
||||||
|
Wrap label + ⓘ button in a flex container (e.g. <div className="flex items-center gap-1">).
|
||||||
|
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).
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 1: Populate tooltipText in registry + add ⓘ to FieldRenderer and PasswordField</name>
|
||||||
|
<files>src/schemas/registry.ts, src/components/ui/FieldRenderer.tsx, src/components/ui/PasswordField.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- 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)
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
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 `<label>` and any required star in a flex container `<div className="flex items-center gap-1">`, place the ⓘ button OUTSIDE and AFTER the label element:
|
||||||
|
```tsx
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<label htmlFor={id} className="text-sm font-medium text-gray-700">{label}{required star if present}</label>
|
||||||
|
{tooltipText && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowTooltip(v => !v)}
|
||||||
|
aria-label={`More info about ${label}`}
|
||||||
|
className="text-blue-500 hover:text-blue-700 text-xs leading-none"
|
||||||
|
>
|
||||||
|
ⓘ
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
- Add tooltip panel below the input (above helpText/error):
|
||||||
|
```tsx
|
||||||
|
{tooltipText && showTooltip && (
|
||||||
|
<p className="text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1.5 mt-1">
|
||||||
|
{tooltipText}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
Step 3 — Update FieldRenderer (src/components/ui/FieldRenderer.tsx):
|
||||||
|
- Add `const [showTooltip, setShowTooltip] = useState(false)` (one useState per FieldRenderer instance)
|
||||||
|
- Add import for useState if not already present
|
||||||
|
- For text and select input cases: apply the same pattern as PasswordField — flex container for label + ⓘ button sibling (outside label), tooltip panel below input
|
||||||
|
- For password input case: FieldRenderer calls PasswordField — pass `tooltipText={field.tooltipText}` as a prop
|
||||||
|
|
||||||
|
Note: FieldRenderer likely has a switch/if on field.inputType. Apply the ⓘ + panel to each branch (text, select), and pass tooltipText down to PasswordField for the password branch.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npx vitest run src/components/wizard/RemoteConfigStep.test.tsx</automated>
|
||||||
|
</verify>
|
||||||
|
<done>UX-01 tests for OneDrive token ⓘ presence and toggle behavior pass. All VALID-01 tests still pass.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 2: Wire AzureAuthToggle to pass tooltipText + add SFTP auth-method tooltip</name>
|
||||||
|
<files>src/components/wizard/AzureAuthToggle.tsx, src/components/wizard/SftpAuthToggle.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- azureblob sas_url PasswordField call in AzureAuthToggle shows ⓘ with the registry tooltipText
|
||||||
|
- azureblob key PasswordField call in AzureAuthToggle shows ⓘ with the registry tooltipText
|
||||||
|
- SftpAuthToggle renders a ⓘ button labeled 'More info about authentication methods' above the segmented control
|
||||||
|
- Clicking that ⓘ shows the auth-method explanation panel
|
||||||
|
- Clicking again hides it
|
||||||
|
- SFTP pass and key_pem PasswordField calls do NOT get tooltipText (auth method ⓘ is above the whole toggle)
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
AzureAuthToggle.tsx:
|
||||||
|
- Import BACKEND_REGISTRY from '../schemas/registry' (or '../../schemas/registry' — verify the import path)
|
||||||
|
- Add two lookups at the top of the component function:
|
||||||
|
```typescript
|
||||||
|
const sasUrlTooltip = BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'sas_url')?.tooltipText;
|
||||||
|
const keyTooltip = BACKEND_REGISTRY.azureblob.fields.find(f => f.key === 'key')?.tooltipText;
|
||||||
|
```
|
||||||
|
- Pass `tooltipText={sasUrlTooltip}` to the PasswordField call for sas_url
|
||||||
|
- Pass `tooltipText={keyTooltip}` to the PasswordField call for key
|
||||||
|
|
||||||
|
SftpAuthToggle.tsx:
|
||||||
|
- Add `const [showAuthTip, setShowAuthTip] = useState(false)` to the component
|
||||||
|
- Import useState if not already present (it likely already is — component uses CSS-hidden toggle state)
|
||||||
|
- Before the segmented control div (the Password / Private Key tabs), add:
|
||||||
|
```tsx
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="text-sm font-medium text-gray-700">Authentication Method</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowAuthTip(v => !v)}
|
||||||
|
aria-label="More info about authentication methods"
|
||||||
|
className="text-blue-500 hover:text-blue-700 text-xs leading-none"
|
||||||
|
>
|
||||||
|
ⓘ
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{showAuthTip && (
|
||||||
|
<p className="text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1.5 mt-1">
|
||||||
|
Password auth sends your password to the SFTP server on each connection.
|
||||||
|
Key-based auth uses a private key (more secure — the server only stores your public key).
|
||||||
|
Paste the full private key PEM content (including the -----BEGIN/END----- lines) if using key-based auth.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
- Do NOT add tooltipText to the pass or key_pem PasswordField calls inside SftpAuthToggle.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npx vitest run src/components/wizard/RemoteConfigStep.test.tsx</automated>
|
||||||
|
</verify>
|
||||||
|
<done>All UX-01 tests from Plan 00 pass (ⓘ on sas_url, key, SFTP auth method, onedrive token; toggle behavior). Full suite green: `npx vitest run`.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="checkpoint:human-verify" gate="blocking">
|
||||||
|
<name>Task 3: Human verify — tooltip toggle and inline validation in the running wizard</name>
|
||||||
|
<what-built>
|
||||||
|
Phase 7 complete: inline format validation on Azure account, S3 region, and GCS project number; contextual ⓘ tooltips on SAS URL, access key, SFTP auth method, and OneDrive token.
|
||||||
|
</what-built>
|
||||||
|
<how-to-verify>
|
||||||
|
Start the dev server: `npm run dev` (or `npx vite`)
|
||||||
|
|
||||||
|
1. Go to the wizard, select Azure Blob Storage
|
||||||
|
- Type 'ABC' in Storage Account Name → expect inline error 'Must be 3–24 lowercase alphanumeric characters' after clicking Next
|
||||||
|
- Type 'mystorageaccount' → error clears
|
||||||
|
- Confirm ⓘ icon appears next to 'SAS URL' and 'Access Key' labels
|
||||||
|
- Click ⓘ on SAS URL → explanation panel appears below the field
|
||||||
|
- Click ⓘ again → explanation panel disappears
|
||||||
|
|
||||||
|
2. Select Amazon S3
|
||||||
|
- Type 'us east 1' in Region → expect inline error about valid format
|
||||||
|
- Type 'us-east-1' → error clears
|
||||||
|
|
||||||
|
3. Select Google Cloud Storage
|
||||||
|
- Type 'abc' in Project Number → expect inline error about digits only
|
||||||
|
- Type '123456789' → error clears
|
||||||
|
|
||||||
|
4. Select SFTP
|
||||||
|
- Confirm ⓘ icon appears above the Password/Private Key tabs with label 'Authentication Method ⓘ'
|
||||||
|
- Click ⓘ → explanation panel appears
|
||||||
|
- Click ⓘ again → panel disappears
|
||||||
|
|
||||||
|
5. Select OneDrive
|
||||||
|
- Confirm ⓘ icon appears next to the Token field label
|
||||||
|
- Click ⓘ → explanation of rclone authorize JSON token appears
|
||||||
|
</how-to-verify>
|
||||||
|
<action>Run `npm run dev` and follow the verification steps below to confirm all tooltip and validation behaviors work in the browser.</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npx vitest run</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Human confirms all 4 tooltip fields show ⓘ with correct explanations, toggle works, and 3 validation fields show inline errors on bad input.</done>
|
||||||
|
<resume-signal>Type "approved" if all tooltip and validation behaviors work correctly, or describe any issues found</resume-signal>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
1. `npx tsc --noEmit` — no TypeScript errors across all 5 modified files
|
||||||
|
2. `npx vitest run` — full suite passes (147+ tests, 0 failures)
|
||||||
|
3. Human verification: all 4 tooltip fields show ⓘ, toggle works, 3 validation fields show errors on bad input
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- UX-01 tests from Plan 00 all pass (GREEN)
|
||||||
|
- All VALID-01 tests from Plan 01 still pass
|
||||||
|
- Full test suite passes with 0 regressions
|
||||||
|
- Human verifies tooltip toggle and inline validation in the running wizard UI
|
||||||
|
- Phase 7 complete — VALID-01 and UX-01 requirements satisfied
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/07-validation-ux-polish/07-02-SUMMARY.md`
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user