diff --git a/docs/architecture/project-structure.md b/docs/architecture/project-structure.md index bce5d7a..453bb47 100644 --- a/docs/architecture/project-structure.md +++ b/docs/architecture/project-structure.md @@ -15,8 +15,8 @@ scrum4me/ │ ├── (auth)/ │ │ ├── login/page.tsx │ │ └── register/page.tsx -│ ├── (app)/ # Beschermde routes -│ │ ├── layout.tsx # Auth-check + navigatie +│ ├── (app)/ # Beschermde routes (desktop + tablets) +│ │ ├── layout.tsx # Auth-check (requireSession) + navigatie │ │ ├── dashboard/page.tsx # Productenlijst │ │ ├── products/ │ │ │ ├── new/page.tsx @@ -31,6 +31,16 @@ scrum4me/ │ │ └── settings/ │ │ ├── page.tsx # Profiel, account, PB-overzicht, rollen, tokens │ │ └── tokens/page.tsx +│ ├── (mobile)/ # Mobile-shell route group (telefoon-UA) +│ │ ├── layout.tsx # Auth via gedeelde requireSession; geen NavBar/StatusBar +│ │ └── m/ +│ │ ├── settings/page.tsx # Account + product-selector + QR-instructie + logout +│ │ ├── pair/ # QR-pairing (verhuisd uit (app)/ — URL ongewijzigd) +│ │ │ ├── page.tsx +│ │ │ └── pair-confirmation.tsx +│ │ └── products/[id]/ +│ │ ├── page.tsx # Mobile Product Backlog (tab-mode op <1024px) +│ │ └── solo/page.tsx # Mobile Solo (3-koloms-kanban) │ ├── api/ # REST API voor Claude Code │ │ ├── products/ │ │ │ └── [id]/ @@ -54,6 +64,7 @@ scrum4me/ │ ├── sprint/ # Sprint-componenten │ ├── products/ # ProductForm, TeamManager, ArchiveProductButton │ ├── settings/ # RoleManager, ProfileEditor, LeaveProductButton +│ ├── mobile/ # LandscapeGuard, MobileTabBar, LogoutButton │ └── dnd/ # dnd-kit wrappers ├── lib/ │ ├── prisma.ts # Prisma Client singleton @@ -107,6 +118,26 @@ scrum4me/ **Rationale:** De gesplitste schermen met dnd-kit vereisen client-side staat die twee panelen tegelijk aanstuurt. `useState` per component leidt tot prop drilling; Context API veroorzaakt onnodige re-renders bij 60fps drag-events. Zustand's selector-gebaseerde subscriptions updaten alleen de componenten die de gewijzigde slice observeren. De gouden regel: Zustand beheert uitsluitend ephemere UI-staat — nooit server-data. Server-data blijft in Server Components en wordt opgehaald via Prisma. **Trade-off:** Extra abstractielaag die geïnitialiseerd moet worden vanuit server-data. Opgelost via een patroon waarbij het Server Component de initiële ids doorgeeft aan een Client Component dat de store hydrateert. +### Beslissing: Eigen route group `(mobile)` voor mobile-shell (PBI-11) +**Keuze:** Telefoon-routes leven onder `app/(mobile)/m/*` met eigen `layout.tsx`, niet als nested directory in `(app)/m/*`. +**Rationale:** Next.js layouts erven naar binnen — een nested layout in `(app)/m/` zou de NavBar/StatusBar/MinWidthBanner/SoloRealtimeBridge/NotificationsBridge erven van `(app)/layout.tsx` zonder die te kunnen onderdrukken. De mobile-shell heeft die chrome niet nodig (alleen bottom-tab-bar). Een eigen route group geeft een schone parent-layout. De auth-check is geëxtraheerd naar `lib/auth-guard.ts` `requireSession()` zodat `(app)/layout.tsx` en `(mobile)/layout.tsx` dezelfde guard delen. +**Trade-off:** Twee layouts om te onderhouden, maar elk met een duidelijk afgebakende verantwoordelijkheid. Content-componenten (PbiList, StoryPanel, TaskPanel, SoloBoard, alle entity-dialogen) blijven volledig gedeeld — geen dubbele implementatie. + +### Beslissing: UA-redirect via `Mobi`-substring (PBI-11) +**Keuze:** `lib/user-agent.ts` `isPhoneUA()` test op `Mobi` in de UA-string. `loginAction` (`actions/auth.ts`) leest de header na `session.save()`; phone-UA → `/m/products/[active]/solo` (zonder actief product → `/m/settings`); tablet-UA en desktop → `/dashboard`. +**Rationale:** `Mobi` is de standaard-heuristiek — aanwezig in iPhone Safari Mobile en Android Chrome op telefoons, afwezig op iPad en Android-tablet. Exact wat we willen: alleen telefoons krijgen de mobile-shell, tablets behouden de desktop-flow. +**Trade-off:** Heuristieken zijn nooit 100%; wie via een mobile-emulatie (DevTools) wil testen kan UA spoofen. + +### Beslissing: Gedeelde `entityDialogContentClasses` voor mobile-fullscreen (PBI-11) +**Keuze:** Eén Tailwind-class-string in `components/shared/entity-dialog-layout.ts` met `max-sm:w-screen max-sm:h-screen max-sm:max-w-none max-sm:rounded-none` dekt alle entity-dialogen (PbiDialog, StoryDialog, TaskDialog, TaskDetailDialog). +**Rationale:** Dialog-fullscreen op mobile op vier plekken bewaken zou drift introduceren. De gedeelde constant geeft één bron van waarheid. Het regressie-vangnet (`__tests__/components/shared/entity-dialog-layout.test.ts`) verifieert dat elke dialog deze constant blijft gebruiken. +**Trade-off:** Eén dialog kan niet afwijken zonder de constant te verlaten — bewuste keuze voor consistentie. + +### Beslissing: Gescheiden SplitPane cookie-key voor mobile (PBI-11) +**Keuze:** `BacklogSplitPane` op `app/(mobile)/m/products/[id]/page.tsx` gebruikt `cookieKey={\`backlog-${id}-mobile\`}` (versus desktop `backlog-${id}`). +**Rationale:** Op mobile rendert de `SplitPane` in tab-mode (`<1024px`), waar split-percentages niet aangepast worden. Zonder gescheiden key zou dezelfde cookie hergebruikt worden — telefoon-rotaties of orientatie-wisselingen hadden anders ongewenste interactie met de desktop-split-state. +**Trade-off:** Gebruikers die zowel mobile als desktop gebruiken hebben twee onafhankelijke split-instellingen, wat juist gewenst is. + --- ## Zustand stores diff --git a/docs/specs/functional.md b/docs/specs/functional.md index 4674dd7..f405ce0 100644 --- a/docs/specs/functional.md +++ b/docs/specs/functional.md @@ -27,7 +27,7 @@ v1 is een desktop-first fullstack webapplicatie waarmee een solo developer of kl - Integratie met externe tools (GitHub Issues, Linear, Jira) — v2 - Notificaties en reminders — v2 - Native mobiele app — web-first; een toekomstige mobiele variant richt zich uitsluitend op taken afvinken -- Responsive layout voor schermen smaller dan 1024px — desktop-first in v1 +- Responsive layout voor schermen smaller dan 1024px — desktop-first hoofdpad. Voor telefoons (UA met `Mobi`) is er een aparte mobile-shell onder `/m/*` met drie schermen — zie sectie *Mobile shell* hieronder. --- @@ -534,10 +534,44 @@ De app is deployable op Vercel + Neon PostgreSQL en lokaal draaibaar met een Neo /todos (todo-lijst) /settings (profiel, account, product backlogs, rollen, API-tokens) /settings/tokens (API-tokenbeheer) + +# Mobile-shell (telefoon-UA) +/m/settings (account + product-selector + QR-instructie + logout) +/m/products/:id (Product Backlog — tab-mode op <1024px) +/m/products/:id/solo (Solo Paneel — 3-koloms-kanban met horizontal scroll) +/m/pair (QR-pairing bevestiging — verhuisd uit (app)/ naar (mobile)/) ``` --- +## Mobile shell + +**Prioriteit:** v1 — voor on-the-go gebruik (PBI-11) +**Persona:** Lars onderweg / tussendoor + +**Omschrijving:** +Telefoon-gebruikers (UA met `Mobi`-substring) krijgen een minimale mobile-shell met drie schermen onder `/m/*`. Tablets (iPad, Android-tablet zonder `Mobi`) en desktop blijven het bestaande `/dashboard`-pad volgen. De mobile-shell hergebruikt zoveel mogelijk content-componenten van de desktop-app (PbiList, StoryPanel, TaskPanel, SoloBoard, alle entity-dialogen) — er is geen aparte mobile-implementatie van de business-logica. + +**Architectuur in één regel:** eigen route group `app/(mobile)/` met eigen `layout.tsx` (zonder NavBar/StatusBar/MinWidthBanner) — een nested layout in `(app)/m/*` zou de NavBar erven. Auth via gedeelde `lib/auth-guard.ts` `requireSession()`. Zie [`docs/architecture/project-structure.md`](../architecture/project-structure.md) voor de volledige architectuur. + +**Acceptatiecriteria:** +- [ ] Phone-UA bij login → `/m/products/[active]/solo` (zonder actief product → `/m/settings`) +- [ ] Tablet-UA en desktop-UA blijven naar `/dashboard` +- [ ] `/m/*` rendert geen NavBar, AppIcon, MinWidthBanner of StatusBar — alleen tab-bar onderaan +- [ ] Portrait-modus toont rotate-overlay; landscape verbergt overlay +- [ ] PWA-manifest verzoekt `landscape`-orientatie (iOS Safari kan dit niet 100% afdwingen — CSS-overlay als fallback) +- [ ] Tab-bar onderaan: Backlog (ListTree), Solo (Activity), Settings — alleen iconen, geen labels, tap-target ≥44×44px +- [ ] Backlog op `<1024px` rendert in tab-mode (tabs: PBI's | Stories | Taken) met click-cascade auto-switch +- [ ] Entity-dialogen (PBI, Story, Task, Task-detail) renderen full-screen op `<640px` via gedeelde `entityDialogContentClasses` +- [ ] Solo-paneel behoudt 3-koloms-kanban met horizontal scroll (geen 1-koloms-mode) +- [ ] Settings: account-info read-only, product-selector activeert + redirect, QR-instructie naar desktop, logout met bevestiging +- [ ] `/m/pair` (QR-pairing-bevestiging) blijft werken — alleen filesystem-locatie verhuisd, URL onveranderd +- [ ] Demo-user op mobile: read-only werkt; logout staat toe + +**Bekende limiet:** iOS Safari respecteert `manifest.orientation` niet altijd in PWA-modus — de CSS-overlay (``) is de feitelijke afdwinging. + +--- + ## Datamodel (schets) | Entiteit | Sleutelvelden | Relaties / opmerkingen |