Add STACK, FEATURES, ARCHITECTURE, PITFALLS, and SUMMARY research files for Ready2Blob. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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
Recommended Architecture
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 asremote-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:
- (Optionally) download and install rclone — copy
rclone.exeto a stable path (e.g.,C:\ProgramData\rclone\) - Write
rclone.confto the user-appropriate path ($env:APPDATA\rclone\rclone.conffor per-user, orC:\ProgramData\rclone\rclone.conffor system-wide) - (Optionally) create a scheduled task or a startup script to mount the remote on login
- 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