docs(sprint-1-plan, chunk 4/4): impl order, acceptance mapping, DoD, open Qs

Patrick Plate
2026-06-24 14:55:00 +02:00
parent 46cbf0460b
commit 7167688de0
+224
@@ -0,0 +1,224 @@
# Sprint 1 Plan — "Spark" (Part 4/4)
> **Final chunk.** Implementation order, acceptance mapping, open-question cross-references, Definition of Done, and what actually ships at the end of Sprint 1.
>
> Previous chunks:
> - [Sprint-1-Plan](Sprint-1-Plan.md) — Background, architecture, repo layout, W0 skeleton, W1 plate-auth wire-up
> - [Sprint-1-Plan-Part-2](Sprint-1-Plan-Part-2.md) — W2 domain, W3 API
> - [Sprint-1-Plan-Part-3](Sprint-1-Plan-Part-3.md) — W4 frontend, W5 seed data, W6 deploy + CI/CD
---
## 7. Implementation Order
The 7 workstreams are not all sequential. There is one hard prerequisite chain and one parallelisable pair.
### 7.1 Dependency graph
```
W0 Skeleton
W1 plate-auth wire-up
├──────────────┐
▼ ▼
W2 Domain W3 API (depends on W2 entity + W1 @CurrentUser)
│ │
└──────┬───────┘
W4 Frontend
W5 Seed data (dev-only, can run in parallel with W4 once W2 is done)
W6 Deploy + CI/CD
```
### 7.2 Recommended sequence
| Order | Workstream | Why this position |
|-------|-----------|-------------------|
| 1 | **W0 Skeleton** | Nothing builds without the repo + Maven + pnpm scaffolding. |
| 2 | **W1 plate-auth wire-up** | Until plate-auth is wired, `@CurrentUser` does not exist → W3 can't compile. |
| 3 | **W2 Domain** | Ideas table + entity + `OnboardingHook` impl. Unblocks both W3 and W4 (the API contract). |
| 4a | **W3 API** | Can start as soon as `Idea` and `IdeaService` exist. |
| 4b | **W5 Seed (dev)** | `R__dev_seed_ideas.sql` only needs the `ideas` table to exist. Can run in parallel with W3. |
| 5 | **W4 Frontend** | Needs W3 endpoints alive (at least `/api/ideas` `GET` returning `[]`). |
| 6 | **W6 Deploy + CI/CD** | Last because it requires real images of W3 + W4 to ship. |
### 7.3 Daily-granular sketch (informal)
> **Reality check:** Sprint 1 has no day-count contract. This is a rough shape, not a commitment.
| Phase | Focus | Outcome |
|-------|-------|---------|
| Phase 1 | W0 + W1 | `mvn -pl backend test` green. Frontend builds. `/login` page renders. Backend boots with plate-auth auto-config. |
| Phase 2 | W2 + start W3 | Flyway migration creates `spark_org` + `ideas`. Onboarding hook registered. `GET /api/ideas` returns `[]` for authenticated user. |
| Phase 3 | Finish W3 + start W4 | `POST /api/ideas` works in Postman/curl with a real Bearer JWT. `/ideas` page renders empty list. |
| Phase 4 | W4 | `/ideas/new` form submits → backend persists → list refreshes. End-to-end happy path works in `docker-compose up`. |
| Phase 5 | W5 + start W6 | Dev seed loads 5 demo ideas locally. Backend Dockerfile builds. Frontend Dockerfile builds. |
| Phase 6 | W6 | TrueNAS deploy via Gitea Actions. `https://sparkboard.plate-software.de/login` reachable from public internet. PWA installable. |
---
## 8. Acceptance Criteria → Workstream Mapping
Each acceptance criterion from [Sprint-1-Assessment §7](Sprint-1-Assessment.md#7-acceptance-criteria) maps to one or more workstreams. If a workstream is incomplete, the linked acceptance criteria cannot pass.
| # | Acceptance | Workstreams | Verification |
|---|------------|------------|--------------|
| **A1** | Allowlisted user can sign in via Google and reach `/ideas` | W1 + W4 + W6 | Manual: load `https://sparkboard.plate-software.de`, click "Sign in with Google", land on `/ideas`. |
| **A2** | Non-allowlisted user is rejected with a clear error | W1 | Manual: sign in with a non-allowlisted Google account → plate-auth rejects → error page or `/login?error=access_denied`. |
| **A3** | Membership auto-created for new allowlisted user (no manual step) | W2 (`SparkboardOnboardingHook`) | DB inspection: after first login, `memberships` table contains `(user_id, 'SPARK_ORG', '00000000-0000-0000-0000-000000000001', 'MEMBER'\|'ADMIN')` row. |
| **A4** | Authenticated user can create + list ideas | W2 + W3 + W4 | E2E: log in → `/ideas/new` → submit → idea appears on `/ideas`. |
| **A5** | App is installable as PWA on iOS and Android | W4 (manifest + sw.js) | Manual on iPhone + Android: "Add to Home Screen" → standalone window with Sparkboard icon. |
| **A6** | App is deployed to TrueNAS via Gitea Actions on push to `main` | W6 | Watch `.gitea/workflows/deploy.yml` succeed; `smoke-test.sh` passes against `https://sparkboard.plate-software.de`. |
### 8.1 Coverage matrix (workstream → acceptance)
| Workstream | A1 | A2 | A3 | A4 | A5 | A6 |
|-----------|----|----|----|----|----|----|
| W0 Skeleton | — | — | — | — | — | — |
| W1 plate-auth wire-up | ✅ | ✅ | — | — | — | — |
| W2 Domain | — | — | ✅ | ✅ | — | — |
| W3 API | — | — | — | ✅ | — | — |
| W4 Frontend | ✅ | — | — | ✅ | ✅ | — |
| W5 Seed (dev) | — | — | — | — | — | — |
| W6 Deploy | ✅ | — | — | — | — | ✅ |
> **Gap check:** W0 and W5 are not on the acceptance hook directly. W0 is infrastructure (without it, nothing else exists). W5 is dev productivity (no production value but speeds up manual testing on Patrick's laptop). Both stay in scope.
---
## 9. Open Questions (Sprint-1-Specific)
These questions block or change Sprint 1 if answered "differently than the leaning." The full list of Sparkboard open questions lives in [Open-Questions](Open-Questions.md) — this section only highlights the ones Sprint 1 touches.
| ID | Question | Sprint 1 Impact | Leaning |
|----|----------|----------------|---------|
| **Q01** | Single-org name + ID strategy: hardcode `00000000-0000-0000-0000-000000000001` or compute? | High — affects W2 SQL + `IdeaController.FAMILY_SPARK_ID` constant. | Hardcode the all-zeros-with-trailing-1 magic UUID. Trivial to refactor to a `@ConfigurationProperties` lookup later. |
| **Q02** | Allowlist management: edit `application.yml` and redeploy, or admin UI? | Medium — affects W1 config shape and W6 deploy story. | YAML + redeploy in v1. Admin UI is Sprint 4+. |
| **Q03** | Admin promotion model: bootstrap admins via `sparkboard.admins[]` config, or DB seed? | Medium — affects W2 (`SparkboardOnboardingHook` logic). | Config-driven (`sparkboard.admins[]`). Hook reads it at hook time and writes `ADMIN` vs `MEMBER`. |
| **Q07** | PWA assets pipeline: hand-crafted SVGs/PNGs in `frontend/public/` or generated? | Low — affects W4 + manifest. | Hand-crafted for v1 (4 humans, no app-store presence). Generated assets are Sprint 4. |
> **Non-blocking but worth noting:** Q04 (idea status workflow), Q05 (reaction model), Q06 (security customisation), Q08 (mobile PWA vs native), Q09 (real-time updates), Q10 (notifications) are all Sprint 2+. They do **not** affect Sprint 1's surface.
---
## 10. Definition of Done
Sprint 1 is done when **all of these are true simultaneously**:
### 10.1 Code & build
- [ ] `mvn -pl backend test` exits 0 on Patrick's laptop and in CI.
- [ ] `pnpm --filter frontend build` exits 0 on Patrick's laptop and in CI.
- [ ] `docker compose up` boots all 3 services (postgres + backend + frontend); backend logs show "Flyway migrations applied" for both `flyway_schema_history` and `flyway_schema_history_auth`.
- [ ] No `TODO` markers blocking Sprint 1 acceptance in committed code (TODOs for Sprint 2+ are fine).
### 10.2 Functional (verified manually on TrueNAS deploy)
- [ ] **A1** — Patrick can sign in via Google → lands on `/ideas`.
- [ ] **A2** — A non-allowlisted Gmail rejection produces a visible error (not a 500).
- [ ] **A3** — DB inspection: `memberships` row for Patrick's user exists after first login.
- [ ] **A4** — Patrick creates an idea via `/ideas/new` → it appears on `/ideas` after redirect.
- [ ] **A5** — "Add to Home Screen" on iPhone Safari creates a standalone-mode Sparkboard icon.
- [ ] **A6** — A commit to `main` triggers `.gitea/workflows/deploy.yml`, which succeeds and runs `smoke-test.sh` against `https://sparkboard.plate-software.de`.
### 10.3 Testplan
- [ ] All test cases in [Sprint-1-Testplan](Sprint-1-Testplan.md) marked ✅ Passed, ⏭️ Skipped (with reason), or ❌ Failed (with linked follow-up ticket).
- [ ] No ❌ Failed test blocks an acceptance criterion.
### 10.4 Operations
- [ ] `https://sparkboard.plate-software.de` resolves and serves the Next.js app.
- [ ] frps tunnel on port `30011` is alive and stable for ≥1 hour of uptime.
- [ ] Caddy correctly routes `/api/*` to backend and everything else to frontend.
- [ ] Backend uses production secrets from environment variables (not literal `application.yml` values).
- [ ] Postgres data survives `docker compose down && docker compose up` (named volume mounted).
### 10.5 Documentation
- [ ] [Sprint-1-Testplan](Sprint-1-Testplan.md) is updated with actual results.
- [ ] [Open-Questions](Open-Questions.md) reflects any decisions made during Sprint 1.
- [ ] `README.md` in `sparkboard/` repo root has local-dev instructions that work on a clean checkout.
---
## 11. What Ships at End of Sprint 1
The deliverable, in plain English:
> Patrick (or one of the 4 humans on the allowlist) opens `https://sparkboard.plate-software.de` on a phone or laptop, signs in with their Google account, lands on a list of ideas the family has captured. They tap "New idea", type a title and optional body, hit submit, and see their idea on the list. They can install the app to their home screen on iOS or Android. Anyone not on the allowlist gets a clear rejection. The whole thing was deployed by `git push origin main`.
### 11.1 What ships (concrete)
| Component | Status at end of S1 |
|----------|---------------------|
| `sparkboard.plate-software.de` | Live, HTTPS via IONOS + frp + Caddy, port 30011 |
| Google sign-in (allowlisted) | Works for 4 humans on allowlist |
| `/ideas` (list page) | Server-rendered list, newest first |
| `/ideas/new` (create form) | Title + body → POST → redirect to `/ideas` |
| PWA manifest + service worker | Installable; no offline caching yet |
| Single org auto-membership | Every first login creates a `(SPARK_ORG, '00...01', MEMBER\|ADMIN)` row |
| plate-auth v0.1.0 dependency | Pulled from Gitea Maven + npm registries |
| Gitea Actions deploy | Push to `main` → SSH to TrueNAS → `docker compose pull && docker compose up -d` → smoke test |
| Test suite | Backend `mvn test` green, frontend `pnpm test` green, E2E smoke green |
### 11.2 What does NOT ship in S1 (intentional deferrals)
| Deferred | Lands in | Why |
|---------|----------|-----|
| Edit / delete idea | Sprint 2 (Kindling) | Read + create is the walking-skeleton minimum |
| Idea detail page | Sprint 2 (Kindling) | List view is enough to prove the model works |
| Reactions, comments | Sprint 2/3 (Kindling/Flame) | Single-feature focus; conversation later |
| Status workflow (RAW → BUILDING → SHIPPED) | Sprint 2 (Kindling) | Field exists on entity with default RAW, but no UI to change it |
| Notifications | Sprint 4 (Ember) | Requires push infra; not v1-critical |
| Offline mode / IndexedDB cache | Sprint 4 (Ember) | sw.js stub-only in S1 (R8 mitigation) |
| Native APK via Capacitor | Sprint 5 (Wildfire) | PWA-first; native only if PWA falls short |
| Admin UI for allowlist | Sprint 4 (Ember) | YAML + redeploy is fine for 4 humans |
| Per-user analytics, audit log | Phoenix (optional) | Not v1-critical |
| Multi-org support | Never (Sparkboard scope) | Sparkboard *defines* single-org. If multi-org is ever needed it becomes a different app. |
---
## 12. Risk Re-check Against Plan
Cross-reference of [Sprint-1-Assessment §4 risks](Sprint-1-Assessment.md#4-risk-assessment) with the workstream that owns each mitigation:
| Risk | Owner | Mitigation in plan |
|------|-------|-------------------|
| R1 plate-auth incomplete on Sprint 1 start | W0/W1 | Sprint 1 blocked until plate-auth v0.1.0 shipped — gating is explicit. |
| R2 plate-auth API changes mid-sprint | W1 | Pin to v0.1.0 in `pom.xml` + `package.json`; refuse to bump mid-sprint. |
| R3 Spring Boot 4.1.0 guinea-pig issues | W0 | Accept; document workarounds in `README.md` as found. |
| R4 Google OAuth setup friction | W1 | Use plate-auth's existing OAuth config; reuse the InspectFlow Google Cloud project credentials. |
| R5 frp instability on port 30011 | W6 | Reuse existing frpc.toml pattern from InspectFlow (port 30009); same proven setup. |
| R6 IONOS reverse proxy quirks | W6 | Reuse existing pattern from `inspectflow.plate-software.de`. |
| R7 Flyway migration collision (two histories) | W2 | Distinct table names (`flyway_schema_history` vs `flyway_schema_history_auth`) configured in plate-auth's auto-config; no cross-history FKs. |
| R8 PWA install rejection | W4 | Hand-crafted icons; valid manifest; sw.js stub registers cleanly (no caching logic to break). |
| R9 Single-org assumption leaks into multi-org pattern | W2/W3 | `FAMILY_SPARK_ID` as one constant in one place. One-line refactor to read from user context when needed. |
| R10 Polymorphic FK ergonomics | W2 | Tolerated; index on `(org_type, org_id)` for query perf; documented. |
| R11 OnboardingHook race condition | W2 | `MembershipService.upsert` is idempotent; hook can run safely on every login. |
All risks have a workstream and a concrete mitigation.
---
## 13. Cross-references
- [Sprint-1-Plan](Sprint-1-Plan.md) — chunk 1 (W0, W1)
- [Sprint-1-Plan-Part-2](Sprint-1-Plan-Part-2.md) — chunk 2 (W2, W3)
- [Sprint-1-Plan-Part-3](Sprint-1-Plan-Part-3.md) — chunk 3 (W4, W5, W6)
- [Sprint-1-Assessment](Sprint-1-Assessment.md) — risks, options, A1A6
- [Sprint-1-Testplan](Sprint-1-Testplan.md) — test cases mapped to A1A6
- [Architecture](Architecture.md) — system diagrams, SPI seam
- [Integration-Guide](Integration-Guide.md) — Sparkboard's plate-auth wire-up walkthrough
- [Open-Questions](Open-Questions.md) — full Q01Q10 with leanings
- [Roadmap](Roadmap.md) — what comes after Sprint 1
---
_End of Sprint 1 Plan. Status: **Draft v1, awaiting GO from Patrick.**_