diff --git a/docs/INDEX.md b/docs/INDEX.md index 8e0102e..b4b99a4 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -44,6 +44,7 @@ Auto-generated on 2026-05-04 from front-matter and headings. | [M10 — Password-loze inlog via QR-pairing](./plans/M10-qr-pairing-login.md) | active | 2026-05-03 | | [M11 — Claude vraagt, gebruiker antwoordt](./plans/M11-claude-questions.md) | active | 2026-05-03 | | [M9 — Actief Product Backlog](./plans/M9-active-product-backlog.md) | active | 2026-05-03 | +| [PBI-11 — Mobile-shell met landscape-lock (settings + backlog + solo)](./plans/PBI-11-mobile-shell.md) | — | — | | [ST-1109 — PBI krijgt een status (Ready / Blocked / Done)](./plans/ST-1109-pbi-status.md) | active | 2026-05-03 | | [ST-1110 — Demo gebruiker read-only](./plans/ST-1110-demo-readonly.md) | active | 2026-05-03 | | [ST-1111 — Voer uit-knop met Claude Code job queue](./plans/ST-1111-claude-job-trigger.md) | active | 2026-05-03 | diff --git a/docs/plans/PBI-11-mobile-shell.md b/docs/plans/PBI-11-mobile-shell.md new file mode 100644 index 0000000..b3df338 --- /dev/null +++ b/docs/plans/PBI-11-mobile-shell.md @@ -0,0 +1,198 @@ +# PBI-11 — Mobile-shell met landscape-lock (settings + backlog + solo) + +> **Status:** READY · priority 3 · sort_order 8 +> **Stories:** ST-1133 (TaskDialog full-screen) · ST-1134 (foundation) · ST-1135 (UA-redirect) · ST-1136 (settings) · ST-1137 (backlog) · ST-1138 (solo) · ST-1139 (docs + E2E) + +## Doel + +Scrum4Me bruikbaar maken op een mobiele telefoon, beperkt tot drie schermen — Settings (account + product-selector + QR-pairing-instructie + logout), Product Backlog (PBI/Story/Task aanmaken), Solo Paneel (voortgang vastleggen). Landscape-orientatie afgedwongen via PWA-manifest + CSS-overlay. App-naam en -icoon onderdrukken op `/m/*`. Desktop-app blijft ongewijzigd. + +## Drie architectuur-beslissingen + +### Beslissing A — gedeelde dialog-classes (raakt ST-1133 + ST-1138) + +Alle entity-dialogen (PbiDialog, StoryDialog, TaskDialog, TaskDetailDialog) delen dezelfde class-string in [components/shared/entity-dialog-layout.ts](../../components/shared/entity-dialog-layout.ts): + +```ts +export const entityDialogContentClasses = cn( + 'flex flex-col p-0 gap-0', + 'max-h-[90vh] w-full max-w-[calc(100%-2rem)]', + 'sm:max-w-[90vw] sm:max-h-[85vh]', + 'lg:max-w-[50vw] lg:min-w-[480px]', +) +``` + +→ Mobile-fullscreen wordt via één edit op deze constant geregeld: + +```ts +'max-sm:w-screen max-sm:h-screen max-sm:max-w-none max-sm:rounded-none' +``` + +**Gevolg voor stories:** +- ST-1133 T-317 muteert `entity-dialog-layout.ts`, niet `task-dialog.tsx` rechtstreeks +- ST-1138 T-332 vervalt als file-edit — wordt verify-only (controleer dat TaskDetailDialog mee-erft) +- PBI/Story-dialogen krijgen mobile-fullscreen "voor niets" (handig voor ST-1137) + +### Beslissing B — eigen route group `app/(mobile)/` + +Parent layout `app/(app)/layout.tsx` rendert NavBar, MinWidthBanner, StatusBar, SoloRealtimeBridge, NotificationsBridge. Een nested layout in `(app)/m/` kan deze parent-output **niet** verwijderen (Next.js layouts erven naar binnen, niet vervangen). + +**Keuze:** verplaats `/m/*` naar een eigen route group `app/(mobile)/m/{settings,pair,products}/...` met eigen `app/(mobile)/layout.tsx`. + +**Auth-guard duplicatie voorkomen** door `getSession()`-check te extraheren naar `lib/auth-guard.ts`: + +```ts +// lib/auth-guard.ts +export async function requireSession() { + const session = await getSession() + if (!session.userId) redirect('/login') + return session +} +``` + +Beide layouts (`(app)/layout.tsx` en `(mobile)/layout.tsx`) roepen deze helper aan. Bestaande `/m/pair/page.tsx` (M10 QR-pairing) verhuist mee naar `app/(mobile)/m/pair/page.tsx` — geen URL-wijziging, alleen filesystem-move. + +**Gevolg voor stories:** +- ST-1134 T-321 schrijft `app/(mobile)/layout.tsx`, niet `app/(app)/m/layout.tsx` +- ST-1136 page wordt `app/(mobile)/m/settings/page.tsx` +- ST-1137 page wordt `app/(mobile)/m/products/[id]/page.tsx` +- ST-1138 page wordt `app/(mobile)/m/products/[id]/solo/page.tsx` +- M10's `/m/pair` verhuist naar `app/(mobile)/m/pair/` — URL ongewijzigd, geen redirect-migratie nodig + +### Beslissing C — gescheiden SplitPane cookie-key + +ST-1137 hergebruikt `BacklogSplitPane` (drie panelen). Op mobile rendert die in tab-mode (auto-switch + back-button uit ST-1116). De SplitPane bewaart split-percentages in een cookie. + +**Keuze:** gescheiden cookie-key voor mobile — `split-pane:backlog-3-mobile:` — zodat mobile-gebruikers (die in tab-mode geen split-percentages bewerken maar wel terug kunnen schakelen) de desktop-split niet beïnvloeden. + +**Gevolg voor stories:** +- ST-1137 T-328 geeft expliciete `cookieKey`-prop aan `BacklogSplitPane` op de mobile-route + +## Hergebruik (al aanwezig) + +| Wat | Bron | +|---|---| +| Mobile tab-mode in `SplitPane` (incl. `tabLabels`, `mobileBreakpoint`, `activeTab`) | ST-1116 — [components/split-pane/split-pane.tsx](../../components/split-pane/split-pane.tsx) | +| Click-cascade auto-switch in `BacklogSplitPane` | ST-1116 commit `3e86a8d` | +| QR-pairing route `/m/pair` | M10 commit `625221f` | +| `/m/pair` confirmation page | bestaand | +| Functional-spec mobile-tabs sectie | `docs/specs/functional.md:234-235` | + +## Stories + +### ST-1133 — TaskDialog full-screen op mobile (verifieer en fix) + +**Doel:** entity-dialogen renderen 100vw × 100vh op viewport `<640px`. + +**Acceptance:** +- `entityDialogContentClasses` in `components/shared/entity-dialog-layout.ts` bevat `max-sm:w-screen max-sm:h-screen max-sm:max-w-none max-sm:rounded-none` +- Sticky header en footer blijven bereikbaar; body scrollt +- Werkt voor TaskDialog, TaskDetailDialog, PbiDialog, StoryDialog (alle gebruiken de constant) +- Tests dekken mobile-render via `window.innerWidth`-mock voor minstens TaskDialog en TaskDetailDialog +- Geen regressie op desktop (`sm:max-w-[90vw]` blijft op `>=640px`) + +**Tasks:** +- T-316 inventariseer huidige render +- T-317 fix de gedeelde constant +- T-318 tests + +### ST-1134 — Mobile shell foundation (route group + landscape-guard + tab-bar + manifest) + +**Doel:** route group `(mobile)`, landscape-overlay, bottom tab-bar, PWA-manifest. + +**Acceptance:** +- `app/(mobile)/layout.tsx` rendert zonder NavBar / AppIcon / MinWidthBanner / StatusBar +- Auth-guard via gedeelde `lib/auth-guard.ts` helper; `(app)/layout.tsx` gebruikt dezelfde helper +- `` toont rotate-overlay in portrait (window.matchMedia) +- `` bottom-fixed met 3 lucide-iconen (ListTree, Activity, Settings); tap-targets ≥44×44 px +- `public/manifest.json` bevat `"orientation": "landscape"` +- M10 `/m/pair` verhuist filesystem-only naar `app/(mobile)/m/pair/` — URL onveranderd +- Tests: LandscapeGuard render-states, TabBar route-active, auth-guard helper + +**Tasks:** +- T-319 LandscapeGuard +- T-320 MobileTabBar +- T-321 `(mobile)/layout.tsx` + manifest + auth-guard extractie + filesystem-move van `/m/pair` + +### ST-1135 — Mobile UA-redirect bij login + +**Acceptance:** +- `lib/user-agent.ts` exporteert `isPhoneUA(ua: string | null): boolean` op basis van `Mobi`-substring +- `actions/auth.ts` `loginAction` redirect bij phone-UA naar `/m/products/[active]/solo`; zonder actief product naar `/m/settings` +- Tablet-UA en desktop-UA blijven op `/dashboard` +- Demo-user volgt zelfde routing +- Tests dekken alle paden (phone met/zonder product, tablet, desktop, null UA, demo) + +**Tasks:** T-322 helper · T-323 loginAction integratie · T-324 tests + +### ST-1136 — Mobile Settings-pagina + +**Acceptance:** +- `app/(mobile)/m/settings/page.tsx` +- Toont username, isDemo-badge, actief-product-naam +- Product-selector — klik → `setActiveProductAction` + redirect `/m/products/[id]/solo` +- QR-pairing-instructie — link "Open scrum4me.app/login op je desktop om in te loggen via QR" +- Logout-knop met AlertDialog "Uitloggen?" → `logoutAction` +- Geen avatar-upload, geen bio-edit +- Tests render-states + logout-flow + +**Tasks:** T-325 layout · T-326 logout-flow · T-327 tests + +### ST-1137 — Mobile Product Backlog-pagina + +**Acceptance:** +- `app/(mobile)/m/products/[id]/page.tsx` hergebruikt PbiList/StoryPanel/TaskPanel + backlog-store +- `BacklogSplitPane` rendert in tab-mode op `<1024px`; auto-switch op selectie blijft werken +- TaskDialog-searchParams wiring (`?newTask=`, `?editTask=`, `?storyId=`) werkt +- Cookie-key gescheiden: `split-pane:backlog-3-mobile:` +- + knoppen voor PBI/Story/Task werken; demo blijft read-only +- Tests: page-rendering met initial state, tab-mode, click-cascade-flow + +**Tasks:** T-328 page wrapper + cookie-key · T-329 TaskDialog wiring · T-330 tests + +### ST-1138 — Mobile Solo Paneel + +**Acceptance:** +- `app/(mobile)/m/products/[id]/solo/page.tsx` hergebruikt SoloBoard +- 3 kanban-kolommen blijven; horizontal scroll +- TaskDetailDialog rendert 100vw × 100vh op `<640px` — **gedekt door beslissing A** (entityDialogContentClasses) +- "Voer uit"-knop bereikbaar +- SSE-stream blijft werken +- Tests: solo-page rendert, TaskDetailDialog erft mobile-classes (zonder eigen file-edit) + +**Tasks:** +- T-331 page wrapper +- T-332 verify-only (geen file-edit; controleer dat shared constant uit ST-1133 doorwerkt) +- T-333 tests + +### ST-1139 — Docs sync + end-to-end verificatie + +**Acceptance:** +- `docs/specs/functional.md` heeft "Mobile shell"-sectie; desktop-first-clausule herzien +- `docs/architecture.md` beschrijft route group `(mobile)`, manifest landscape, UA-redirect, gedeelde auth-guard +- `npm run lint && npm test && npm run build` slagen +- E2E checklist (11 punten — zie hieronder) +- Bekende limiet: iOS Safari PWA-orientation-lock werkt niet 100% — CSS-overlay als fallback + +**Tasks:** T-334 functional-spec · T-335 architecture-doc · T-336 E2E-verificatie + +## Verificatie (E2E checklist uit T-336) + +1. `npm run lint && npm test && npm run build` slagen +2. DevTools mobile-emulatie iPhone 12 landscape: `/m/products/[id]` rendert tab-mode, geen NavBar, tab-bar onderaan +3. Portrait → rotate-overlay zichtbaar; landscape → overlay verdwijnt +4. Tab-bar 3 iconen werken (Backlog/Solo/Settings) +5. Login phone-UA → redirect `/m/products/[id]/solo`; desktop-UA → `/dashboard` +6. Backlog-flow: + PBI, + Story, + Task in TaskDialog +7. Solo-flow: tap task → TaskDetailDialog full-screen, "Voer uit"-knop bereikbaar +8. TaskDialog full-screen op `<640px` (via shared constant) +9. PWA-installatie test op echte mobile (Android of iOS) +10. `/m/pair` QR-flow intact na route-group-verhuizing +11. Demo op mobile read-only; logout via `/m/settings` werkt; geen Scrum4Me-tekst of AppIcon op `/m/*` + +## Out of scope + +- Tablets (geen Mobi-UA) blijven desktop-flow gebruiken +- iOS PWA full-orientation-lock (CSS-overlay is fallback) +- Avatar/bio editor op mobile-settings +- 1-koloms-kanban (3-koloms blijft, swipe horizontaal)