Files
2026-04-01 12:37:26 +02:00

26 KiB

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

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 </user_constraints>


<phase_requirements>

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 <p> element inserted after <h2>; 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
</phase_requirements>

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

src/
├── App.tsx                          # Add showIntro state + IntroSection component
├── components/
│   ├── wizard/
│   │   ├── BackendSelectionStep.tsx  # Add watch('name') + RemoteNamePreview
│   │   ├── RemoteConfigStep.tsx      # Add step description <p>
│   │   ├── DeploymentStep.tsx        # Add step description <p>
│   │   └── ReviewStep.tsx            # Add step description <p>
│   └── 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 <IntroSection> 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:

// src/App.tsx — WizardShell modification
function WizardShell() {
  const { state } = useWizard();
  const [showIntro, setShowIntro] = useState(true);

  if (showIntro) {
    return (
      <div className="min-h-screen bg-surface flex flex-col items-center py-12 px-4">
        <div className="w-full max-w-2xl">
          <div className="flex items-center justify-between mb-8">
            <h1 className="text-3xl font-bold text-on-surface">Ready2Blob</h1>
            <ThemeToggle />
          </div>
          <IntroSection onStart={() => setShowIntro(false)} />
        </div>
      </div>
    );
  }

  // ... 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:

// BackendSelectionStep.tsx — add watch to existing useForm destructure
const {
  register,
  handleSubmit,
  watch,                    // ADD THIS
  formState: { errors },
} = useForm<RemoteNameFormValues>({ ... });

const remoteName = watch('name');   // re-renders on every keystroke

// In JSX, below the TextFieldMD3:
<RemoteNamePreview value={remoteName} />

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:

// src/components/ui/RemoteNamePreview.tsx
interface RemoteNamePreviewProps {
  value: string;
}

export function RemoteNamePreview({ value }: RemoteNamePreviewProps) {
  const isEmpty = !value || value.trim() === '';

  return (
    <div className="mt-2 rounded-md bg-surface-variant px-3 py-2 text-xs font-mono">
      {isEmpty ? (
        <span className="text-on-surface-variant/50">
          [my-remote]
          <span className="block text-on-surface-variant/40 font-sans mt-1 text-xs">
            Type a name to see how it appears in your config
          </span>
        </span>
      ) : (
        <span className="text-on-surface-variant">[{value}]</span>
      )}
    </div>
  );
}

Pattern 4: Step Descriptions — Simple JSX Addition

What: A <p> element placed immediately after the <h2> in each step component.

When to use: All 4 step components. No component extraction needed — just inline JSX.

Example:

// BackendSelectionStep.tsx
<h2>Step 1: Select Backend</h2>
<p className="text-sm text-on-surface-container/70 mt-1 mb-4">
  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.
</p>

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 <App /> 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

// BackendSelectionStep.tsx — verified pattern using existing useForm
const {
  register,
  handleSubmit,
  watch,
  formState: { errors },
} = useForm<RemoteNameFormValues>({
  resolver: zodResolver(remoteNameSchema),
  mode: 'onSubmit',
  reValidateMode: 'onChange',
  defaultValues: { name: state.remote.name },
});

const remoteName = watch('name');

// In JSX below TextFieldMD3:
<TextFieldMD3
  id="remote-name"
  label="Remote name"
  registration={register('name')}
  error={errors.name}
  required
  helpText="This becomes the section header [name] in your rclone.conf. Example: azure-prod, backup-s3. Letters, numbers, dashes, underscores only."
/>
<RemoteNamePreview value={remoteName} />

MD3 Token Usage for Preview Block

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

<div className="mt-2 rounded-md bg-surface-variant px-3 py-2 text-xs font-mono text-on-surface-variant">
  [{value}]
</div>

Registry Field Enrichment Pattern

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

// DeploymentStep.tsx — insert after <h2>
<h2>Step 3: Deployment Options</h2>
<p className="text-sm text-on-surface-container/70 mt-1 mb-4">
  Choose how the rclone config file will be placed on the target machine and
  whether to include an rclone installation script.
</p>

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)