diff --git a/.planning/phases/10-content-clarity/10-RESEARCH.md b/.planning/phases/10-content-clarity/10-RESEARCH.md new file mode 100644 index 0000000..5fcde49 --- /dev/null +++ b/.planning/phases/10-content-clarity/10-RESEARCH.md @@ -0,0 +1,477 @@ +# Phase 10: Content & Clarity - Research + +**Researched:** 2026-04-01 +**Domain:** React UI — content additions, inline preview component, registry data enrichment +**Confidence:** HIGH + +## Summary + +Phase 10 is a pure content and UI composition phase — no new dependencies, no new architectural patterns. Every change is either adding JSX to existing components, adding a small new component (`RemoteNamePreview`), or enriching data objects in `BACKEND_REGISTRY`. The technical risk is low; the effort is mostly writing copy and wiring a controlled preview. + +The one genuinely new code unit is the live remote-name config preview below the `TextFieldMD3` in `BackendSelectionStep`. It needs a watched value from `react-hook-form` (`watch('name')`) and renders a styled `` block. The `intro section` (UX-01) requires local `showIntro` state in `WizardShell` (App.tsx) and a conditional render — straightforward React state work. + +The credential help-text task (UX-04) is purely a data change in `registry.ts` — no component work required because `FieldRenderer` and `PasswordField` already consume `helpText` and `tooltipText` from `FieldDef`. + +**Primary recommendation:** Implement in four isolated tasks: (1) intro section in App.tsx, (2) remote-name preview in BackendSelectionStep, (3) step descriptions in each step component, (4) registry enrichment for credential fields. Each task is independently testable and zero-dependency on the others. + +--- + + +## User Constraints (from CONTEXT.md) + +### Locked Decisions + +**Intro section (UX-01)** +- Inline above wizard in the same page — no routing, no separate landing page +- Action-first tone: lead with the outcome ("Go from zero to a deployable rclone setup in minutes") +- Mention top 3 backends + count: "Azure Blob, S3, OneDrive, and 4 more cloud backends" +- CTA button "Get Started" — clicking hides the intro and reveals Step 1 with StepIndicator +- On page reload, intro shows again (no persistence — wizard state is ephemeral) +- No collapse/shrink behavior — intro simply disappears when wizard starts + +**Remote name experience (UX-02)** +- Inline config preview below the TextFieldMD3 field, styled with MD3 surface tokens +- Live preview updates as user types, showing `[remote-name]` config syntax in a small code box +- When field is empty: show grayed-out placeholder example `[my-remote]` with note "Type a name to see how it appears in your config" +- Field placeholder: `e.g. my-backup` +- Help text: detailed with example — "This becomes the section header [name] in your rclone.conf. Example: azure-prod, backup-s3. Letters, numbers, dashes, underscores only." +- Preview depth (header only vs header + type line): Claude's discretion + +**Step descriptions (UX-03)** +- Claude's discretion on tone and wording +- Each of the 4 steps gets a 1-2 sentence description below the heading explaining what the user is doing and why + +**Credential help text (UX-04)** +- Add `tooltipText` to all fields that lack it (S3, S3-compatible, GCS, SFTP, B2, OneDrive drive_id) +- Include links to provider docs only for complex/non-obvious flows (OneDrive token procedure, GCS service account JSON creation) +- Add short `helpText` only where the label alone is ambiguous (S3 access_key_id, secret_access_key, S3-compatible equivalents) +- Existing helpText and tooltipText (Azure Blob fields, OneDrive token, SFTP password/key_pem) are already good — don't rewrite + +### Claude's Discretion +- Step description wording and tone for all 4 steps +- Remote name preview depth (header only vs header + type line) +- Exact tooltip wording for each backend field +- Which fields qualify as "non-obvious" enough to warrant doc links +- Intro section visual styling (spacing, typography, icon/illustration presence) + +### Deferred Ideas (OUT OF SCOPE) +None — discussion stayed within phase scope + + +--- + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|-----------------| +| UX-01 | First-time visitor sees an intro section explaining what Ready2Blob does, with a clear call-to-action to start the wizard | App.tsx WizardShell gains local `showIntro` state; intro conditionally renders above StepIndicator; "Get Started" dispatches nothing — just sets `showIntro = false` | +| UX-02 | Remote name field includes a placeholder example, help text explaining what it is, and a visual preview showing how it appears in the generated `[remote-name]` config | BackendSelectionStep adds `watch('name')` + new `RemoteNamePreview` component below TextFieldMD3 | +| UX-03 | Each wizard step has a 1-2 sentence description below the heading explaining what the user is doing and why | Each of the 4 step components gets a `

` element inserted after `

`; no architectural change | +| UX-04 | All backend credential fields have contextual help text explaining what to enter and where to find it | `BACKEND_REGISTRY` in registry.ts enriched with `tooltipText` and `helpText` for S3, S3-compatible, GCS, SFTP, B2, OneDrive drive_id fields | + + +--- + +## Standard Stack + +### Core +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| React | 18.3.1 | UI rendering, local state | Already in project | +| react-hook-form | 7.72.0 | Form values — `watch()` for live preview | Already in use in BackendSelectionStep | +| Tailwind v4 | 4.2.2 | Styling with MD3 tokens | Established pattern in project | + +### Supporting +| Library | Version | Purpose | When to Use | +|---------|---------|---------|-------------| +| Vitest + @testing-library/react | 4.1.1 / 16.3.2 | Unit tests | All new components need tests | +| @testing-library/user-event | 14.6.1 | User interaction simulation | Typing in preview field | + +No new dependencies needed. Zero new runtime packages for this phase. + +**Installation:** None required. + +--- + +## Architecture Patterns + +### Recommended Project Structure +``` +src/ +├── App.tsx # Add showIntro state + IntroSection component +├── components/ +│ ├── wizard/ +│ │ ├── BackendSelectionStep.tsx # Add watch('name') + RemoteNamePreview +│ │ ├── RemoteConfigStep.tsx # Add step description

+│ │ ├── DeploymentStep.tsx # Add step description

+│ │ └── ReviewStep.tsx # Add step description

+│ └── ui/ +│ └── RemoteNamePreview.tsx # NEW: live config preview component +└── schemas/ + └── registry.ts # Add tooltipText + helpText to credential fields +``` + +### Pattern 1: Intro Section — Local State Gate in WizardShell + +**What:** `showIntro` boolean in `WizardShell` (not in useReducer — this is UI-only ephemeral state). When `true`, render `` and hide StepIndicator + step content. When `false`, render normal wizard. + +**When to use:** Any piece of UI that gates the main content with a one-time splash that resets on reload. + +**Example:** +```tsx +// src/App.tsx — WizardShell modification +function WizardShell() { + const { state } = useWizard(); + const [showIntro, setShowIntro] = useState(true); + + if (showIntro) { + return ( +

+
+
+

Ready2Blob

+ +
+ setShowIntro(false)} /> +
+
+ ); + } + + // ... existing wizard render +} +``` + +**IMPORTANT:** IntroSection can be defined in App.tsx (inline) or extracted to a separate file. The existing test `App.test.tsx` tests `screen.getByText(/Select Backend/)` — this test will still pass because when `showIntro=true`, BackendSelectionStep is not rendered. Tests that call `renderAtStep()` bypass WizardShell entirely so they are unaffected. + +### Pattern 2: Live Preview with react-hook-form watch() + +**What:** `watch('name')` subscribes to live field value without triggering re-validation. Pass the watched value down to a display-only component. + +**When to use:** Whenever a field value needs to be reflected in a real-time preview outside the field itself. + +**Example:** +```tsx +// BackendSelectionStep.tsx — add watch to existing useForm destructure +const { + register, + handleSubmit, + watch, // ADD THIS + formState: { errors }, +} = useForm({ ... }); + +const remoteName = watch('name'); // re-renders on every keystroke + +// In JSX, below the TextFieldMD3: + +``` + +### Pattern 3: RemoteNamePreview Component + +**What:** A stateless display component that takes a `value: string` prop and renders the rclone config syntax. + +**Preview depth decision (Claude's discretion):** Show header + type line. This gives users a more realistic preview of the actual config file and makes the format immediately understandable. Example: +``` +[my-backup] +type = azureblob +``` +This is more informative than header-only and the type line is always present in every generated config. However — since the backend is not yet selected at Step 1, showing a static `type = ...` placeholder is misleading. **Recommend: header-only preview.** The type is selected in Step 2; Step 1 is purely about naming. + +**Example:** +```tsx +// src/components/ui/RemoteNamePreview.tsx +interface RemoteNamePreviewProps { + value: string; +} + +export function RemoteNamePreview({ value }: RemoteNamePreviewProps) { + const isEmpty = !value || value.trim() === ''; + + return ( +
+ {isEmpty ? ( + + [my-remote] + + Type a name to see how it appears in your config + + + ) : ( + [{value}] + )} +
+ ); +} +``` + +### Pattern 4: Step Descriptions — Simple JSX Addition + +**What:** A `

` element placed immediately after the `

` in each step component. + +**When to use:** All 4 step components. No component extraction needed — just inline JSX. + +**Example:** +```tsx +// BackendSelectionStep.tsx +

Step 1: Select Backend

+

+ Give your remote connection a name and choose where your files will be stored. + The name appears as a section header in your rclone.conf file. +

+``` + +### Pattern 5: Registry Enrichment (UX-04) + +**What:** Add `tooltipText` and `helpText` string properties to `FieldDef` objects in `BACKEND_REGISTRY`. No component changes — `FieldRenderer` and `PasswordField` already consume these. + +**When to use:** Any time field-level guidance needs to be added or updated. + +**Existing infrastructure confirmed:** +- `FieldRenderer` text branch: passes `tooltipText` via `field.tooltipText ? (tooltipIcon) : undefined` as `helpTextPrefix` to `TextFieldMD3` +- `FieldRenderer` select branch: renders tooltip button + panel when `field.tooltipText` exists +- `PasswordField`: accepts `tooltipText` prop, renders ⓘ button and tooltip panel +- `TextFieldMD3`: renders `helpText` via `helpTextPrefix`/`helpText` slot + +**No new infrastructure needed for UX-04.** + +### Anti-Patterns to Avoid +- **Adding intro state to useReducer/WizardState:** Intro visibility is a one-time UI gate that resets on reload. It should be local React state (`useState`) in WizardShell, not global wizard state. +- **Extracting step descriptions to a data structure:** 4 static strings don't benefit from a lookup table. Inline JSX is clearer and easier to maintain. +- **Using `useWatch` instead of `watch()`:** `watch()` from `useForm` is sufficient for a single field. `useWatch` is for cross-component subscription without prop drilling — not needed here. +- **Adding `placeholder` prop to TextFieldMD3:** The current `TextFieldMD3` hardcodes `placeholder=" "` (a single space) to enable the CSS floating-label trick via `peer-[:not(:placeholder-shown)]`. Do NOT add a visible placeholder text to this component — the UX-02 placeholder `e.g. my-backup` is implemented as the field's `helpText` + the `RemoteNamePreview` empty state, not as a native HTML placeholder. + +--- + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| Live field value subscription | Custom event listeners or manual state sync | `watch('name')` from react-hook-form | Already integrated; single source of truth; handles defaultValues correctly | +| Tooltip display | New tooltip infrastructure | Existing `tooltipText` in `FieldDef` + `FieldRenderer`/`PasswordField` | All tooltip UI (hover, click, dual-state) already implemented in Phase 9 | +| Code block styling | Custom code component | Tailwind `font-mono` + `bg-surface-variant` + `text-on-surface-variant` | MD3 token system already has these tokens for code blocks (used in ReviewStep OutputBlock) | + +**Key insight:** This phase is content, not infrastructure. Every piece of infrastructure needed already exists from Phases 8 and 9. The only new component is `RemoteNamePreview` (~30 lines). + +--- + +## Common Pitfalls + +### Pitfall 1: App.test.tsx — "Select Backend" heading test +**What goes wrong:** `App.test.tsx` line 58 tests `screen.getByText(/Select Backend/)`. If `showIntro=true` hides BackendSelectionStep on initial render, this test will fail because the heading is no longer in the DOM. +**Why it happens:** The test renders `` which now shows IntroSection by default. +**How to avoid:** Update `App.test.tsx` to either (a) test that IntroSection renders at step 0 instead, or (b) click "Get Started" before asserting step content. The test's intent (WIZD-02: "renders the correct step component") is better served by testing both intro state and post-intro state. +**Warning signs:** `Unable to find an element with the text: /Select Backend/` in App.test.tsx run. + +### Pitfall 2: TextFieldMD3 placeholder is always " " (single space) +**What goes wrong:** A developer adds a `placeholder` prop to `TextFieldMD3` expecting visible placeholder text, breaking the floating label CSS trick. +**Why it happens:** The CSS `peer-[:not(:placeholder-shown)]` selector relies on `placeholder=" "` being set. Any non-space placeholder value would make the label float immediately even when the field is empty, defeating the UX. +**How to avoid:** The UX-02 "placeholder example" (`e.g. my-backup`) must be implemented as `helpText` content, not as a native `placeholder` attribute. The `RemoteNamePreview` empty state message handles the visual placeholder role. + +### Pitfall 3: BackendSelectionStep has only one `getByRole('textbox')` — adding watch must not add extra inputs +**What goes wrong:** Tests in `BackendSelectionStep.test.tsx` find the remote name input via `screen.getByRole('textbox')` (single textbox assertion). If `RemoteNamePreview` accidentally renders an input or contenteditable, this assertion breaks. +**Why it happens:** `getByRole` throws if multiple matches exist. +**How to avoid:** `RemoteNamePreview` must render only display elements (`div`, `span`, `code`) — never form controls. + +### Pitfall 4: `watch()` subscribes on every render — performance is fine for 1 field +**What goes wrong:** Concern that calling `watch('name')` causes excessive re-renders. +**Why it happens:** Misunderstanding of react-hook-form internals. +**How to avoid:** `watch('name')` is optimized — it only triggers re-render of the subscriber when `name` changes. For a single text field this is the correct and intended API. No `useCallback` or memoization needed. + +### Pitfall 5: registry.ts tooltip text for S3 fields — existing test checks `getByRole('button', { name: /more info about access key id/i })` +**What goes wrong:** If `tooltipText` is NOT added to `s3.access_key_id`, but the test expects a tooltip button there, the test will fail. Conversely, if added where not expected, other tests could find unexpected buttons. +**Why it happens:** `RemoteConfigStep.test.tsx` has UX-01 stubs (lines 339-397) that were written as RED tests anticipating future tooltip additions. These tests currently test SAS URL, SFTP auth, and OneDrive token tooltips (already existing). Adding S3 tooltips would not break these tests unless a test specifically asserts the absence of a button. +**How to avoid:** Check `RemoteConfigStep.test.tsx` UX-01 section before finalizing which fields get tooltips. All current UX-01 tests target fields that already have `tooltipText`. New UX-04 fields are additive. + +--- + +## Code Examples + +Verified patterns from existing codebase: + +### Remote Name Watch + Preview Integration +```tsx +// BackendSelectionStep.tsx — verified pattern using existing useForm +const { + register, + handleSubmit, + watch, + formState: { errors }, +} = useForm({ + resolver: zodResolver(remoteNameSchema), + mode: 'onSubmit', + reValidateMode: 'onChange', + defaultValues: { name: state.remote.name }, +}); + +const remoteName = watch('name'); + +// In JSX below TextFieldMD3: + + +``` + +### MD3 Token Usage for Preview Block +```tsx +// Confirmed token names from index.css @theme block: +// bg-surface-variant = --r2b-surface-variant (gray-800 light / slate-900 dark) +// text-on-surface-variant = --r2b-on-surface-variant (gray-100 light / gray-200 dark) +// These are identical to what ReviewStep's OutputBlock uses for code display + +
+ [{value}] +
+``` + +### Registry Field Enrichment Pattern +```ts +// registry.ts — add tooltipText to s3 access_key_id (currently missing) +{ + key: 'access_key_id', + label: 'Access Key ID', + inputType: 'text', + required: true, + placeholder: 'AKIAIOSFODNN7EXAMPLE', + helpText: 'The access key ID from your IAM credentials (starts with AKIA for long-term keys).', + tooltipText: 'Found in the AWS Console under IAM → Users → Security credentials → Access keys. Create a new access key if you don\'t have one. Use an IAM user with least-privilege S3 access — avoid root account keys.', +}, +``` + +### Step Description Pattern +```tsx +// DeploymentStep.tsx — insert after

+

Step 3: Deployment Options

+

+ Choose how the rclone config file will be placed on the target machine and + whether to include an rclone installation script. +

+``` + +--- + +## Validation Architecture + +### Test Framework +| Property | Value | +|----------|-------| +| Framework | Vitest 4.1.1 + @testing-library/react 16.3.2 | +| Config file | vite.config.ts (vitest inline config) | +| Quick run command | `npx vitest run --reporter=verbose` | +| Full suite command | `npx vitest run` | + +### Phase Requirements → Test Map +| Req ID | Behavior | Test Type | Automated Command | File Exists? | +|--------|----------|-----------|-------------------|-------------| +| UX-01 | IntroSection renders on initial App load | unit | `npx vitest run src/App.test.tsx` | ✅ (needs update) | +| UX-01 | "Get Started" click hides intro and shows Step 1 | unit | `npx vitest run src/App.test.tsx` | ✅ (needs new test) | +| UX-02 | RemoteNamePreview renders `[value]` when field has content | unit | `npx vitest run src/components/ui/RemoteNamePreview.test.tsx` | ❌ Wave 0 | +| UX-02 | RemoteNamePreview shows empty-state message when value is empty | unit | `npx vitest run src/components/ui/RemoteNamePreview.test.tsx` | ❌ Wave 0 | +| UX-02 | BackendSelectionStep live preview updates as user types | unit | `npx vitest run src/components/wizard/BackendSelectionStep.test.tsx` | ✅ (needs new test) | +| UX-03 | Each step component renders a description paragraph below h2 | unit | `npx vitest run src/App.test.tsx` | ✅ (needs new tests) | +| UX-04 | S3 access_key_id field renders tooltip ⓘ button | unit | `npx vitest run src/components/wizard/RemoteConfigStep.test.tsx` | ✅ (needs new test) | +| UX-04 | GCS service_account_credentials field renders tooltip ⓘ button | unit | `npx vitest run src/components/wizard/RemoteConfigStep.test.tsx` | ✅ (needs new test) | + +### Sampling Rate +- **Per task commit:** `npx vitest run` +- **Per wave merge:** `npx vitest run` +- **Phase gate:** Full suite green before `/gsd:verify-work` + +### Wave 0 Gaps +- [ ] `src/components/ui/RemoteNamePreview.test.tsx` — covers UX-02 preview rendering + +*(App.test.tsx, BackendSelectionStep.test.tsx, and RemoteConfigStep.test.tsx exist but need new test cases added as part of the implementation tasks.)* + +--- + +## Fields Requiring UX-04 Enrichment + +Complete audit of `registry.ts` against UX-04 decisions: + +### Fields to ADD tooltipText (currently missing) + +| Backend | Field key | Label | Tooltip needed? | Why | +|---------|-----------|-------|-----------------|-----| +| s3 | access_key_id | Access Key ID | YES | Non-obvious where to find in AWS console | +| s3 | secret_access_key | Secret Access Key | YES | Security-sensitive; users confuse with access key ID | +| s3 | region | Region | OPTIONAL | `us-east-1` placeholder is self-explanatory; low priority | +| s3-compatible | access_key_id | Access Key ID | YES | Provider-specific location varies | +| s3-compatible | secret_access_key | Secret Access Key | YES | Same as S3 | +| s3-compatible | endpoint | Endpoint URL | YES | Location varies by provider (Wasabi vs R2 vs MinIO) | +| gcs | project_number | Project Number | YES | Users confuse project number with project ID | +| gcs | service_account_credentials | Service Account JSON | YES | Non-obvious creation flow — warrants doc link | +| sftp | host | Host | NO | Self-explanatory | +| sftp | user | Username | NO | Self-explanatory | +| b2 | account | Application Key ID | YES | Users confuse with account ID | +| b2 | key | Application Key | YES | Location in B2 dashboard not obvious | +| onedrive | drive_id | Drive ID | YES | Not obvious — only found in rclone authorize output | + +### Fields to ADD helpText (currently missing) + +| Backend | Field key | Label | helpText needed? | Why | +|---------|-----------|-------|-----------------|-----| +| s3 | access_key_id | Access Key ID | YES | Label alone ambiguous (vs secret key) | +| s3 | secret_access_key | Secret Access Key | YES | Label alone ambiguous | +| s3-compatible | access_key_id | Access Key ID | YES | Same as S3 | +| s3-compatible | secret_access_key | Secret Access Key | YES | Same as S3 | + +### Fields already well-documented (DO NOT REWRITE) +- azureblob: account, key, sas_url — all have helpText + tooltipText +- onedrive: token — has helpText + tooltipText +- sftp: pass, key_pem — have helpText +- b2: account, key — have basic helpText (may need tooltip additions per above) + +--- + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| Hardcoded colors | MD3 CSS token system | Phase 8 | All new UI uses `bg-surface`, `text-on-surface-container` etc. | +| Static field labels only | tooltipText + helpText in FieldDef | Phase 9 (FieldRenderer rebuilt) | No new infrastructure needed for UX-04 | +| No input styling | TextFieldMD3 floating label | Phase 9 | Remote name field already uses TextFieldMD3 | + +--- + +## Open Questions + +1. **Should RemoteNamePreview be a separate file or defined inline in BackendSelectionStep?** + - What we know: It's ~30 lines, used only in BackendSelectionStep, testable either way + - What's unclear: Whether it will be reused elsewhere (ReviewStep could theoretically show it) + - Recommendation: Extract to `src/components/ui/RemoteNamePreview.tsx` — matches project pattern (all reusable UI in `ui/`), makes testing cleaner, costs nothing + +2. **TextFieldMD3 does not accept a `placeholder` prop — UX-02 says field placeholder should be `e.g. my-backup`** + - What we know: The `placeholder=" "` hardcoding is load-bearing for the floating label. The spec's "placeholder" intent is to show the user an example value. + - What's unclear: Whether to extend TextFieldMD3 to support an optional visible placeholder while preserving the CSS trick, or to convey the example through helpText. + - Recommendation: Convey the example through `helpText` ("This becomes the section header [name] in your rclone.conf. Example: azure-prod, backup-s3."). The `RemoteNamePreview` empty-state message `[my-remote]` serves the visual placeholder role. Do NOT modify TextFieldMD3's placeholder mechanism. + +--- + +## Sources + +### Primary (HIGH confidence) +- Direct codebase inspection — `src/App.tsx`, `src/components/ui/TextFieldMD3.tsx`, `src/components/ui/FieldRenderer.tsx`, `src/components/ui/PasswordField.tsx`, `src/components/wizard/BackendSelectionStep.tsx`, `src/schemas/registry.ts`, `src/index.css` +- `src/components/wizard/RemoteConfigStep.test.tsx` — confirmed existing UX-01 tooltip tests and which fields already have tooltips +- `src/components/wizard/BackendSelectionStep.test.tsx` — confirmed test selector patterns to avoid breaking + +### Secondary (MEDIUM confidence) +- react-hook-form documentation: `watch()` API behavior for controlled preview use case — consistent with observed usage in existing codebase + +### Tertiary (LOW confidence) +- None + +## Metadata + +**Confidence breakdown:** +- Standard stack: HIGH — verified from package.json and codebase +- Architecture: HIGH — all integration points verified by reading actual source files +- Pitfalls: HIGH — derived from reading actual test files and component implementations + +**Research date:** 2026-04-01 +**Valid until:** 2026-05-01 (stable React/RHF APIs; only invalidated by component refactors)