Files
Ready2Blob/.planning/research/ARCHITECTURE.md
T
kawaandClaude Sonnet 4.6 0b72904e36 docs: complete project research
Add STACK, FEATURES, ARCHITECTURE, PITFALLS, and SUMMARY research files for Ready2Blob.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 09:46:22 +01:00

19 KiB

Architecture Patterns

Domain: Pure frontend multi-step configuration wizard (static site, no backend) Project: Ready2Blob Researched: 2026-03-26 Confidence: MEDIUM — rclone.conf format and Intune deployment patterns verified from training data (stable, well-documented domains); client-side download patterns are stable browser APIs; web research unavailable for cross-validation


High-Level System Diagram

┌─────────────────────────────────────────────────────────────────┐
│                        Browser (Static App)                      │
│                                                                  │
│  ┌──────────────┐    ┌──────────────┐    ┌────────────────────┐ │
│  │  Wizard UI   │───▶│  Wizard State│───▶│  Config Builders   │ │
│  │  (Steps/Nav) │    │  (Form Data) │    │  (rclone.conf +    │ │
│  └──────────────┘    └──────────────┘    │   PS Scripts)      │ │
│                                          └────────┬───────────┘ │
│                                                   │             │
│                                          ┌────────▼───────────┐ │
│                                          │  Download Manager  │ │
│                                          │  (individual files │ │
│                                          │   or ZIP bundle)   │ │
│                                          └────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
                              │
                    (file download only)
                              │
                         User's disk

No network requests leave the browser. All state is ephemeral (in-memory for the session lifetime).


Component Boundaries

Component Responsibility Inputs Outputs Communicates With
Wizard UI Render step forms, handle navigation (next/back/jump), validate per-step User interactions Step completion events Wizard State
Wizard State Single source of truth for all collected form data; tracks current step and completion status Step form submissions Reactive state object Wizard UI (reads), Config Builders (reads)
Backend Schema Registry Defines the fields required per rclone backend type (Azure Blob, S3, OneDrive, etc.) Backend type selection Field definitions for each step Wizard UI (drives dynamic form rendering)
rclone.conf Builder Transforms wizard state into a valid rclone.conf string Wizard State snapshot rclone.conf string Download Manager
PowerShell Script Builder Generates PS scripts (Intune Win32 or RMM) from wizard state + optional rclone install flag Wizard State snapshot, script type selection .ps1 string(s) Download Manager
Download Manager Packages one or more text files and triggers browser download (individual or ZIP) File content strings + filenames Browser file download rclone.conf Builder, PS Script Builder

Data Flow

User fills wizard step N
        │
        ▼
Wizard UI validates step N inputs
        │
        ▼
Wizard State updated (merge step N data into central store)
        │
        ▼
User reaches Review/Download step
        │
        ├──▶ rclone.conf Builder
        │         reads: [remote_name, backend_type, ...backend-specific fields]
        │         produces: rclone.conf string
        │
        ├──▶ PowerShell Script Builder (Intune)
        │         reads: [remote_name, mount_path, include_install_flag, install_source_url]
        │         produces: deploy-intune.ps1 string
        │
        └──▶ PowerShell Script Builder (RMM)
                  reads: [remote_name, mount_path, include_install_flag]
                  produces: deploy-rmm.ps1 string

        All strings → Download Manager
        User selects files → individual download (Blob URL) or ZIP (JSZip)

Key invariant: builders are pure functions — same wizard state always produces the same file content. There is no side-effectful build step.


rclone.conf Format (INI-like)

Confidence: HIGH — rclone.conf format is stable and well-documented.

The rclone config file uses a simple INI-like format. Each remote is a named section.

[remote-name]
type = azureblob
account = mystorageaccount
key = base64encodedaccesskey==

Structure Rules

  • Section header: [remote-name] — any identifier the user chooses; appears in rclone commands as remote-name:
  • Each key-value pair on its own line: key = value (spaces around = are conventional but optional)
  • No quoting of values needed (rclone parses raw strings)
  • Comments: lines starting with # or ;
  • Multiple remotes = multiple sections in the same file

Required Fields Per Backend

Backend type value Minimum required fields Common optional fields
Azure Blob azureblob account, then one of: key, sas_url, or client_id+client_secret+tenant endpoint, chunk_size, upload_cutoff
AWS S3 s3 provider = AWS, access_key_id, secret_access_key, region storage_class, server_side_encryption
S3-compatible s3 provider = Other, access_key_id, secret_access_key, endpoint varies by provider
OneDrive onedrive client_id, client_secret, token (OAuth flow) drive_id, drive_type
SFTP sftp host, user, then one of pass or key_file port, use_insecure_cipher
Google Drive drive client_id, client_secret, token (OAuth flow) team_drive, shared_with_me

Example Full Config (Azure Blob + S3)

[my-azure]
type = azureblob
account = contosostorage
key = dGhpcyBpcyBhIHBsYWNlaG9sZGVyIGtleQ==

[my-s3]
type = s3
provider = AWS
access_key_id = AKIAIOSFODNN7EXAMPLE
secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region = us-east-1

Generation Pattern

The rclone.conf builder is pure string templating:

function buildRcloneConf(remotes: RemoteConfig[]): string {
  return remotes.map(remote => {
    const lines = [`[${remote.name}]`, `type = ${remote.type}`];
    for (const [key, value] of Object.entries(remote.params)) {
      if (value !== undefined && value !== '') {
        lines.push(`${key} = ${value}`);
      }
    }
    return lines.join('\n');
  }).join('\n\n');
}

No library needed — plain string concatenation is the correct approach.


Intune / RMM Deployment Patterns

Confidence: MEDIUM — Intune Win32 app deployment is well-established; exact script conventions vary by org.

Intune Win32 App Approach

Intune Win32 apps require a .intunewin package, but PowerShell-only deployments (Intune PowerShell scripts feature) are simpler and sufficient for this use case.

The generated script must:

  1. (Optionally) download and install rclone — copy rclone.exe to a stable path (e.g., C:\ProgramData\rclone\)
  2. Write rclone.conf to the user-appropriate path ($env:APPDATA\rclone\rclone.conf for per-user, or C:\ProgramData\rclone\rclone.conf for system-wide)
  3. (Optionally) create a scheduled task or a startup script to mount the remote on login
  4. Return exit code 0 on success; non-zero on failure (Intune reads exit codes)

Intune Script Deployment Constraints

  • Scripts run as SYSTEM by default, or as logged-on user (configurable)
  • Script must handle the case where rclone is already installed (idempotent)
  • 64-bit PowerShell is required for rclone.exe (32-bit PS cannot run 64-bit binaries reliably)
  • Scripts have a 30-minute execution timeout in Intune
  • Output directory for config must account for execution context (SYSTEM vs user)

RMM Script Approach (NinjaRMM, Datto, etc.)

Simpler than Intune Win32: paste PS script, run as SYSTEM or user. Same functional requirements as above but no .intunewin packaging. Script should be self-contained.

PowerShell Script Structure

#Requires -RunAsAdministrator   # or omit if running as user context

$rclonePath   = "C:\ProgramData\rclone"
$rcloneExe    = "$rclonePath\rclone.exe"
$rcloneConf   = "$rclonePath\rclone.conf"

# --- Optional: Install rclone ---
# if ($InstallRclone) { ... download from $RcloneDownloadUrl ... }

# --- Write config ---
$confContent = @"
[remote-name]
type = azureblob
account = REPLACE_ME
key = REPLACE_ME
"@

New-Item -ItemType Directory -Force -Path $rclonePath | Out-Null
Set-Content -Path $rcloneConf -Value $confContent -Encoding UTF8

# --- Optional: Register mount as scheduled task ---
# ...

exit 0

The PS script builder generates this structure by substituting values from wizard state into a template string.


Patterns to Follow

Pattern 1: Centralized Wizard State (Single Store)

What: All form data lives in one top-level state object, passed down or accessed via context/store. Steps read from and write to slices of this store.

When: Any multi-step form where later steps depend on earlier choices (e.g., backend type selection in step 1 drives which fields appear in step 2).

Why: Avoids prop-drilling, makes "go back and edit" trivial, makes config builders pure functions with a single well-typed input.

Shape:

interface WizardState {
  currentStep: number;
  remote: {
    name: string;
    backendType: BackendType;
    params: Record<string, string>;  // backend-specific key/value pairs
  };
  deployment: {
    includeInstall: boolean;
    installSource: 'github' | 'custom';
    customInstallUrl?: string;
    mountPath?: string;
    scriptTargets: ('intune' | 'rmm')[];
  };
  outputOptions: {
    includeConf: boolean;
    includeIntune: boolean;
    includeRmm: boolean;
    bundleAsZip: boolean;
  };
}

Pattern 2: Backend Schema Registry

What: A static data structure (not code) that defines, per backend type, which fields are required, their labels, input types, placeholder text, and validation rules.

When: The wizard needs to render dynamic forms based on which rclone backend the user selected.

Why: Adding support for a new backend means adding one entry to the registry, not writing new UI components. Keeps UI code backend-agnostic.

Shape:

interface FieldDef {
  key: string;        // matches rclone config key exactly
  label: string;
  inputType: 'text' | 'password' | 'select' | 'toggle';
  required: boolean;
  placeholder?: string;
  helpText?: string;
  options?: { value: string; label: string }[];  // for select
}

type BackendSchema = Record<BackendType, FieldDef[]>;

Pattern 3: Pure Builder Functions

What: Config and script builders are pure functions — they receive wizard state and return a string. No side effects, no DOM access, no async.

When: Always — this is the core generation logic.

Why: Easily testable (unit test: given state X, output matches expected string). Deterministic. Separates concerns cleanly.

Pattern 4: Blob URL Download

What: To trigger a file download in the browser without a server, create a Blob from the string content, generate an object URL, attach it to an <a> element, and programmatically click it.

When: Single file download.

function downloadTextFile(filename: string, content: string): void {
  const blob = new Blob([content], { type: 'text/plain' });
  const url  = URL.createObjectURL(blob);
  const a    = document.createElement('a');
  a.href     = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}

Confidence: HIGH — Blob URL download is a standard, well-supported browser API (all modern browsers, no library needed for single files).

Pattern 5: ZIP Bundle via JSZip

What: When the user wants all generated files in one download, use JSZip to assemble a ZIP in-memory and trigger download.

When: Multi-file download (rclone.conf + one or more .ps1 files).

import JSZip from 'jszip';

async function downloadZip(files: { name: string; content: string }[]): Promise<void> {
  const zip = new JSZip();
  files.forEach(f => zip.file(f.name, f.content));
  const blob = await zip.generateAsync({ type: 'blob' });
  downloadTextFile('ready2blob-deployment.zip', URL.createObjectURL(blob));
}

Confidence: HIGH — JSZip is the established library for client-side ZIP creation. FileSaver.js is an optional companion for older browser compatibility but not required with the Blob URL pattern above.


Anti-Patterns to Avoid

Anti-Pattern 1: Per-Step Local State

What: Each wizard step manages its own form state with no shared store.

Why bad: When the user navigates back to step 2 from step 4, their inputs are gone. The config builder cannot access step 2 data from step 4. Breaks the "review before download" pattern.

Instead: Lift all state to the wizard root. Steps only control their own UI (focus, error display), not their data.

Anti-Pattern 2: Generating Files Only at Download Time from DOM

What: Reading form field values directly from the DOM to build the config string at download time.

Why bad: Bypasses validation, couples the builder to the DOM structure, cannot unit-test without a browser. Fragile.

Instead: Always read from wizard state, which is the validated, typed representation of user input.

Anti-Pattern 3: Hardcoding Backend Fields in Step Components

What: Writing a dedicated <AzureBlobStep />, <S3Step />, <OneDriveStep /> component for every backend.

Why bad: Adding a new backend requires a new component. Does not scale. Leads to duplication of validation logic.

Instead: Use the Backend Schema Registry to drive a single generic <DynamicBackendStep /> that renders fields from schema definitions.

Anti-Pattern 4: Storing Secrets Beyond Session

What: Persisting wizard state to localStorage, sessionStorage, or any cache.

Why bad: Credentials (storage keys, SAS tokens) would persist on the machine after the browser tab is closed. Security risk explicitly called out in project constraints.

Instead: Wizard state lives only in in-memory React/Vue/Svelte state. Closing the tab is the only "logout".

Anti-Pattern 5: Using a Backend for File Generation

What: Sending form data to a server to generate files, which returns them as downloads.

Why bad: Server receives credentials in plaintext. Violates the project's explicit no-backend constraint. Creates data retention risk.

Instead: All generation is client-side (builders are pure TS/JS functions).


Suggested Build Order (Component Dependencies)

Dependencies flow from foundational to dependent. Build in this order:

Phase 1 — Foundation
  └── Wizard State shape definition (TypeScript types + store setup)
  └── Backend Schema Registry (static data, no UI)

Phase 2 — Core Generators (no UI needed yet, fully testable)
  └── rclone.conf Builder (pure function, unit-testable immediately)
  └── PowerShell Script Builder — Intune variant
  └── PowerShell Script Builder — RMM variant
  └── Download Manager (Blob URL + JSZip wrapper)

Phase 3 — Wizard UI Shell
  └── Step navigation (stepper, next/back, step completion tracking)
  └── Wizard State wired to UI (reads/writes)

Phase 4 — Dynamic Step Forms
  └── Backend type selector (step 1)
  └── Dynamic backend fields step (step 2, driven by Schema Registry)
  └── Deployment options step (step 3)
  └── Review + Download step (step 4, calls builders + download manager)

Phase 5 — Polish
  └── Per-step validation with user-visible errors
  └── Security warning modal before download
  └── Preview pane (show generated file content before download)

Rationale for this order:

  • Builders and the schema registry have zero UI dependencies — build and test them first
  • The wizard UI shell (navigation only) can be built against mock/empty state
  • Dynamic forms are built last because they depend on both state wiring AND the schema registry being final
  • The download step can only be meaningfully built once all builders exist

Scalability Considerations

Concern At MVP (5 backends) At Growth (20+ backends) Notes
Backend support Hardcode schema for top 5 Schema registry makes adding trivial Registry pattern is the key enabler
Bundle size Single JS bundle is fine Consider lazy-loading backend schemas Each schema is tiny; not a real concern until 50+ backends
State complexity Flat wizard state struct No change needed Wizard is inherently linear; state stays simple
Testing Unit tests on builders Add snapshot tests for generated files Builders are pure functions — easiest thing to test
Localization Not needed v1 Field labels/help text in schema enables i18n Plan label strings as separate keys in schema if i18n is future

Key Technical Decisions

Decision Recommended Choice Rationale
State management React Context + useReducer OR Zustand (lightweight store) No server state; no need for React Query or Redux. Zustand reduces boilerplate vs Context for this use case.
ZIP library JSZip v3 De facto standard for browser ZIP. No alternatives with meaningful adoption.
File download Native Blob URL API No library needed. FileSaver.js unnecessary for modern browsers.
Config generation Plain string templates (template literals) rclone.conf is simple enough that a template engine adds no value.
Script generation Template literal function per script type Same reasoning. Mustache/Handlebars would be overkill.
Backend schema Static TypeScript object (no database, no fetch) Schemas are known at build time. Static data = zero loading time.

Sources

  • rclone.conf format: training data (stable since rclone v1.x; format has not changed); confidence HIGH
  • Azure Blob rclone backend fields: training data; confidence MEDIUM (specific field names should be cross-checked against https://rclone.org/azureblob/ before implementing the schema registry)
  • Intune PowerShell script deployment: training data; confidence MEDIUM (execution context and timeout limits are well-documented but should be verified for current Intune behavior)
  • JSZip client-side ZIP: training data; confidence HIGH (library API is stable)
  • Blob URL download pattern: training data (standard browser API, MDN-documented); confidence HIGH
  • Web search and WebFetch unavailable during this research session — claims marked MEDIUM should be verified against official docs during implementation phases