Files
kawaandClaude Sonnet 4.6 a26608791f chore: complete v1.3 milestone — Backend Expansion
Archives v1.3 milestone (Phase 13: 18 rclone backends), updates PROJECT.md
with all 30 validated requirements, reorganizes ROADMAP.md with all 4 milestones
in collapsed details blocks, adds v1.2 and v1.3 retrospective entries.

Also commits minor CSS token fixes in src/ (text-on-surface-container class
alignment) that were uncommitted from phase 13 execution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 16:54:45 +02:00

148 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Project Retrospective
*A living document updated after each milestone. Lessons feed forward into future planning.*
## Milestone: v1.0 — MVP
**Shipped:** 2026-03-27
**Phases:** 4 | **Plans:** 18 | **Timeline:** 2 days
### What Was Built
- Backend Schema Registry as single source of truth driving form rendering, Zod validation, and config generation for 3 backends
- 4 pure generator functions (rclone.conf, Intune install/detection, RMM script) with 61 tests and strict PowerShell encoding/path requirements
- 4-step multi-step wizard (backend selection → config → deployment options → review/download) with full back-navigation
- Review and download layer: live preview, security gate, individual file downloads, ZIP bundle, clipboard copy
### What Worked
- **Inside-out build order** (foundation → generators → wizard UI → download layer): front-loaded high-risk correctness work (PowerShell encoding, SYSTEM-context paths, credential handling) before any UI existed — each phase was independently verifiable
- **Wave 0 TDD with `expect.fail` stubs**: defining the API contract in RED before implementation forced explicit interface design and caught integration issues upfront; named failure messages made debugging fast
- **BACKEND_REGISTRY as single source of truth**: zero field-name duplication between form rendering, Zod schemas, and config generation — no drift possible across phases
- **useReducer + Context API with no external state lib**: sufficient for a 4-step wizard, kept bundle lean
### What Was Inefficient
- **scriptTargets collected but never consumed**: `DeploymentStep` dispatches `scriptTargets` but `ReviewStep` ignores it — all output blocks render unconditionally. This was knowable at Phase 2 design time but deferred to v1.1 as tech debt
- **BACKENDS constant hardcoded in BackendSelectionStep**: adding to BACKEND_REGISTRY does not surface in UI — a maintainability gap that was an obvious fix but left unaddressed
- **ReviewStep missing Back button**: UX inconsistency with all other steps — a trivial add that was missed
### Patterns Established
- Wave 0 TDD pattern: stub all requirement behaviors RED before any implementation — use `expect.fail('not yet implemented')` not import errors
- Embed requirement IDs in test describe blocks (WIZD-01, BACK-02, etc.) for direct test-to-requirement traceability
- Generator functions self-contained: `buildRcloneConfContent` duplicated inline in rmm-script.ts to avoid circular dependency risk
- INITIAL_STATE defaults to both targets selected — users deselect rather than discover
### Key Lessons
1. **Define data flow end-to-end before implementing any step**: the `scriptTargets` disconnect would have been caught if the full data flow (DeploymentStep → state → ReviewStep) had been traced before Phase 4 started
2. **Registry-driven UI is strictly better than hardcoded lists**: the BACKEND_REGISTRY pattern proved its value — extending it for the UI selector should have been done at the same time
3. **Inside-out build order works well for correctness-heavy tools**: generator correctness (PowerShell encoding edge cases) was much easier to verify without UI noise
### Cost Observations
- Sessions: ~6 (4 execution phases + planning + verification)
- Notable: Pure frontend with no external API calls kept the scope tight — all phases parallelized within themselves
---
## Milestone: v1.2 — UI Polish & Clarity
**Shipped:** 2026-04-01
**Phases:** 5 (812) | **Plans:** 14 | **Stats:** 78 files, +9,908 / -287 lines
### What Was Built
- Full MD3 color token system in a single token file; all components use semantic utility classes (`bg-surface`, `text-on-primary`) instead of hardcoded Tailwind colors
- System/Light/Dark theme toggle persisted in localStorage with zero flash of unstyled content (DOM class toggle, not React Context)
- TextFieldMD3 with CSS-only floating label, MD3 button hierarchy constants, rebuilt StepIndicator with numbered circles + checkmarks + connectors
- App intro section, step descriptions on all 4 steps, remote name live config preview, contextual help/tooltip enrichment across all credential fields
- Mobile-responsive layout, `focus-visible` ring-3 indicators, step fade/slide transition with `prefers-reduced-motion` guard, auto-scroll to first validation error
- Dark mode visibility fixes: semantic tokens on select, h2 headings, DeploymentStep form controls, ReviewStep security checkbox
### What Worked
- **Bottom-up migration order** (tokens → primitives → content → polish → fixes): each phase stood on solid ground from the one below; no circular rework
- **CSS-only floating label** (`peer-[:not(:placeholder-shown)]`) with no JS state — simpler to reason about, trivially animatable, one fewer React state variable
- **DOM class toggle for dark mode** (not React Context): theme switch is instant and doesn't trigger a re-render cascade across 150+ components
- **`key={state.currentStep}` on step wrapper**: one-line way to force React remount and reset CSS animation to initial state — no animation JS state needed
- **TDD discipline held** on Phase 12 dark mode fixes: found and fixed selector issues before they became regressions
### What Was Inefficient
- **Phase 12 inserted as a gap fix**: dark mode regressions were discovered post-Phase 11 instead of being caught in Phase 11 validation — cost 1 extra phase
- **`aria-label` / `getByLabelText` selector ambiguity** recurred across Phases 9, 10, and 12 — the DEBT-01 pattern (`{ selector: 'input' }`) had to be applied three times in three different phases instead of being resolved once and documented
- **Traceability table in REQUIREMENTS.md not updated** as plans completed — table showed "Planned" for completed items, causing confusion at milestone close
### Patterns Established
- Two-layer CSS token pattern: `@theme var()` references + `@layer base :root/.dark` raw values — required for `.dark` cascade in Tailwind v4
- `vi.stubGlobal` for localStorage/matchMedia — Node v25 experimental WebStorage breaks standard Storage API in jsdom
- CSS-only reduced-motion guard via `@media (prefers-reduced-motion: reduce)` — no JS listener needed
- Tooltip hover: dual-state (`hoverTooltip` + `showTooltip`); click-unpin clears `hoverTooltip` to prevent sticky tooltip after dismiss
- `ring-inset` on ThemeToggle focus ring because `overflow-hidden` on container clips outset rings
### Key Lessons
1. **Validate dark mode in every phase, not as a separate cleanup phase**: each component migration should include a dark-mode check, not defer it
2. **When a test selector pattern is discovered, document it once and reference it** — the `{ selector: 'input' }` guard should have been in a testing conventions note from Phase 9
3. **Keep traceability tables current** during execution, not just at planning time
---
## Milestone: v1.3 — Backend Expansion
**Shipped:** 2026-04-16
**Phases:** 1 (13) | **Plans:** 4 | **Stats:** 28 files, +3,959 / -206 lines
### What Was Built
- BACKEND_REGISTRY expanded from 7 → 18 backends; `BackendType` derived from `keyof typeof BACKEND_REGISTRY` — no manual union to maintain
- `BACKEND_SCHEMAS` auto-generated from registry keys via `Object.fromEntries` — zero per-backend boilerplate
- `OAuthInstructions` collapsible component with backend-specific `rclone authorize` commands (TDD, 4 tests)
- `GdriveAuthToggle` dual-auth component (OAuth token vs service account) — CSS hidden pattern extended from SftpAuthToggle
- `BackendSelectionStep` with 3 category groups, instant search filtering across 4 fields, inline SVG icons
- `RemoteConfigStep` wired for all 18 backends via clean `renderBackendFields()` switch function; `backendLabel` derived from registry
### What Worked
- **`as const` registry with derived BackendType**: one touch-point when adding a backend (registry entry only); Zod schemas auto-generate, type union auto-narrows
- **Incremental plan structure** (registry → components → UI → wiring): each plan had exactly the right scope — no plan was too large or too small
- **TDD for new UI components**: `OAuthInstructions` and `GdriveAuthToggle` written test-first; caught a step-prefix detection edge case before integration
- **type=search for search input**: simple attribute that prevents `getByRole('textbox')` collision — elegant selector discipline
- **renderBackendFields() switch**: 7+ backend branches are unreadable as nested ternaries; switch is flat and extensible
### What Was Inefficient
- **Registry had 18 backends but plan said 17**: a counting error in the plan caused minor confusion during 13-01 execution — plans should verify counts against reality
- **Traceability table not updated** (same as v1.2) — REMOTE-03/04/05 showed "Planned" at milestone close despite being complete
### Patterns Established
- `BackendType` from `keyof typeof BACKEND_REGISTRY` (not explicit union) — the canonical pattern going forward
- `BACKEND_SCHEMAS` from `Object.fromEntries` — zero per-backend call sites
- Registry `as const` (not `Record<BackendType,…>` cast) — cast is circular when BackendType derived from registry
- `Partial<Record<BackendType, FC<IconProps>>>` for optional icon map — missing entries render nothing (no error)
- `getByLabelText` partial regex (`/label/i` not `/^label$/i`) — TextFieldMD3 appends `*` to required field labels
### Key Lessons
1. **Update the traceability table as plans complete**, not just at the start — stale status fields caused confusion at milestone close (2nd occurrence after v1.2)
2. **Count registry entries in the plan, not just at planning time** — the 17/18 mismatch was avoidable
3. **`as const` registries with derived types are strictly superior to explicit unions**: eliminates an entire class of drift bugs, zero cost
---
## Cross-Milestone Trends
### Process Evolution
| Milestone | Phases | Plans | Key Change |
|-----------|--------|-------|------------|
| v1.0 | 4 | 18 | Wave 0 TDD pattern established; inside-out build order validated |
| v1.1 | 3 | 11 | CSS-hidden auth toggle pattern; FieldDef additive extension model |
| v1.2 | 5 | 14 | Two-layer CSS token system; DOM class dark mode; CSS-only floating label |
| v1.3 | 1 | 4 | `keyof typeof` derived BackendType; `Object.fromEntries` schema auto-gen |
### Cumulative Quality
| Milestone | Tests | Notes |
|-----------|-------|-------|
| v1.0 | 98 (12 files) | 24/24 requirements satisfied, 5/5 E2E flows |
| v1.1 | 159 (18 files) | 7 browser UI verifications deferred |
| v1.2 | 159+ (same files, heavier coverage) | All DEBT-01 + POLISH/DARK/UX requirements satisfied |
| v1.3 | 165+ | All 6 REMOTE requirements satisfied; visual verification approved |
### Top Lessons (Verified Across Milestones)
1. Single source of truth (BACKEND_REGISTRY) eliminates entire classes of drift bugs — validated at v1.0, proven at v1.3 scale (18 backends)
2. Wave 0 TDD stubs are worth the upfront cost — they define the contract and make RED→GREEN progress visible
3. **Update traceability tables as plans complete** — stale status fields caused confusion at both v1.2 and v1.3 milestone close (recurring issue)
4. CSS-only approaches (floating label, reduced-motion guard, auth toggle) are simpler than JS state — reach for CSS first
5. `as const` registries with derived types (`keyof typeof`) are strictly superior to explicit unions — zero drift possible