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
|
||||
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 | - |
|
||||
|
||||
@@ -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