diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index d1765f5..797692e 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -84,7 +84,7 @@ Requirements for beta-ready release. Each maps to roadmap phases. ### Mobile + PWA - [x] **MOB-01**: All portal pages render correctly and are usable on mobile (320px–480px) and tablet (768px–1024px) screens -- [x] **MOB-02**: Sidebar collapses to a hamburger menu on mobile with smooth open/close animation +- [x] **MOB-02**: Sidebar collapses to a bottom tab bar on mobile with smooth navigation and More sheet for secondary items - [x] **MOB-03**: Chat interface is fully functional on mobile — send messages, see streaming responses, scroll history - [x] **MOB-04**: Portal installable as a PWA with app icon, splash screen, and service worker for offline shell caching - [x] **MOB-05**: Push notifications for new messages when PWA is installed (or service worker caches app shell for instant load) diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index caa6bcb..6910d75 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -142,7 +142,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 | 5. Employee Design | 4/4 | Complete | 2026-03-25 | | 6. Web Chat | 3/3 | Complete | 2026-03-25 | | 7. Multilanguage | 4/4 | Complete | 2026-03-25 | -| 8. Mobile + PWA | 4/4 | Complete | 2026-03-26 | +| 8. Mobile + PWA | 4/4 | Complete | 2026-03-26 | --- diff --git a/.planning/STATE.md b/.planning/STATE.md index 3caed77..f8928fc 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,7 +4,7 @@ milestone: v1.0 milestone_name: milestone status: completed stopped_at: Completed 08-mobile-pwa 08-04-PLAN.md — Phase 08 and v1.0 milestone complete -last_updated: "2026-03-26T03:33:24.019Z" +last_updated: "2026-03-26T03:38:45.402Z" last_activity: 2026-03-23 — Completed 03-02 onboarding wizard, Slack OAuth, BYO API keys progress: total_phases: 8 diff --git a/.planning/phases/08-mobile-pwa/08-VERIFICATION.md b/.planning/phases/08-mobile-pwa/08-VERIFICATION.md new file mode 100644 index 0000000..a24daed --- /dev/null +++ b/.planning/phases/08-mobile-pwa/08-VERIFICATION.md @@ -0,0 +1,196 @@ +--- +phase: 08-mobile-pwa +verified: 2026-03-26T03:35:51Z +status: gaps_found +score: 11/13 must-haves verified +gaps: + - truth: "Streaming responses (word-by-word tokens) work on mobile" + status: failed + reason: "uuid() in chat-window.tsx line 18 calls itself recursively instead of crypto.randomUUID() — infinite recursion and stack overflow on every HTTPS/localhost chat message send where crypto.randomUUID is available" + artifacts: + - path: "packages/portal/components/chat-window.tsx" + issue: "Line 18: return uuid() should be return crypto.randomUUID() — calls itself instead of the native function" + missing: + - "Fix line 18: change `return uuid()` to `return crypto.randomUUID()`" + - truth: "MOB-02 requirement text satisfied — sidebar collapses to bottom tab bar (requirement text says hamburger menu)" + status: partial + reason: "REQUIREMENTS.md MOB-02 specifies 'hamburger menu' but implementation uses a bottom tab bar. The PLAN, SUMMARY, and codebase all consistently implement a tab bar which is a superior mobile UX pattern. The REQUIREMENTS.md description is outdated. This is a documentation mismatch, not a code gap — the implemented UX exceeds the requirement intent." + artifacts: + - path: ".planning/REQUIREMENTS.md" + issue: "MOB-02 text says 'hamburger menu' but codebase implements bottom tab bar per PLAN. REQUIREMENTS.md should be updated to reflect the actual design." + missing: + - "Update REQUIREMENTS.md MOB-02 description to say 'bottom tab bar' instead of 'hamburger menu'" +human_verification: + - test: "Navigate all portal pages at 320px and 768px viewports" + expected: "No horizontal scroll, no overlapping elements, readable text at both widths" + why_human: "Visual layout correctness on real or emulated viewports cannot be verified by grep" + - test: "Tap each bottom tab bar item at 320px width, then open More sheet" + expected: "Active indicator shows, correct page loads, More sheet slides up with RBAC-filtered items" + why_human: "Touch tap target size (44px minimum), animation smoothness, and RBAC filtering require real device or browser DevTools interaction" + - test: "Open chat, tap a conversation, send a message and wait for streaming response on mobile" + expected: "Full-screen chat with back arrow, tokens appear word-by-word, back arrow returns to list" + why_human: "Streaming animation and touch navigation flow require browser runtime" + - test: "Open Chrome DevTools > Application > Manifest" + expected: "Manifest loads with name=Konstruct, icons at 192/512/maskable, start_url=/dashboard" + why_human: "PWA installability requires browser DevTools or Lighthouse audit" + - test: "Enable push notifications via More sheet > bell icon, then close browser tab" + expected: "Push notification appears when AI Employee responds; tapping it opens the correct conversation" + why_human: "Push notification delivery requires a running server, VAPID keys, and a real browser push subscription" +--- + +# Phase 8: Mobile PWA Verification Report + +**Phase Goal:** The portal is fully responsive on mobile/tablet devices and installable as a Progressive Web App — operators and customers can manage their AI workforce and chat with employees from any device +**Verified:** 2026-03-26T03:35:51Z +**Status:** gaps_found +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|----|-----------------------------------------------------------------------------------------|---------------|---------------------------------------------------------------------------------------------| +| 1 | Desktop sidebar is hidden on screens < 768px; bottom tab bar appears instead | VERIFIED | `layout.tsx:44` `hidden md:flex` wraps Nav; `MobileNav` has `md:hidden` class | +| 2 | Bottom tab bar has 5 items: Dashboard, Employees, Chat, Usage, More | VERIFIED | `mobile-nav.tsx:28-33` TAB_ITEMS array + More button at line 78 | +| 3 | More sheet opens with Billing, API Keys, Users, Platform, Settings, Sign Out (RBAC) | VERIFIED | `mobile-more-sheet.tsx:32-38` SHEET_ITEMS with allowedRoles + signOut button at line 107 | +| 4 | Main content has bottom padding on mobile to clear the tab bar | VERIFIED | `layout.tsx:50` `pb-20 md:pb-8` applied to max-w container | +| 5 | Portal is installable as a PWA with manifest, icons, and service worker | VERIFIED | `manifest.ts` exports valid manifest; `sw.ts` uses Serwist; all 5 icon PNGs exist | +| 6 | Offline banner appears when network is lost | VERIFIED | `offline-banner.tsx` returns amber banner when `useOnlineStatus()` is false | +| 7 | All existing pages remain functional on desktop (no regression) | HUMAN NEEDED | Sidebar visible at md+ in `hidden md:flex` div — automated check passes; visual needs human| +| 8 | On mobile, tapping a conversation shows full-screen chat with back arrow header | VERIFIED | `chat/page.tsx:271-276` MobileChatHeader rendered when mobileShowChat is true | +| 9 | Back arrow returns to conversation list on mobile | VERIFIED | `mobile-chat-header.tsx:24-29` onBack callback; `chat/page.tsx:275` sets mobileShowChat=false | +| 10 | Desktop two-column chat layout is unchanged | VERIFIED | `chat/page.tsx:244-262` md:w-72 md:block classes preserved on sidebar panel | +| 11 | Chat input stays visible when iOS virtual keyboard opens | VERIFIED | `chat-window.tsx:266-269` keyboardOffset from useVisualViewport applied to input paddingBottom | +| 12 | Streaming responses (word-by-word tokens) work on mobile | FAILED | `chat-window.tsx:17-18` uuid() calls itself recursively — infinite recursion on send | +| 13 | User can grant push notification permission from the portal | VERIFIED | `push-permission.tsx` full state machine; embedded in MobileMoreSheet | + +**Score:** 11/13 truths verified (1 failed, 1 human-needed) + +--- + +### Required Artifacts + +| Artifact | Provides | Exists | Substantive | Wired | Status | +|--------------------------------------------------------|--------------------------------------------------|--------|-------------|--------|-------------| +| `packages/portal/components/mobile-nav.tsx` | Bottom tab bar for mobile | YES | YES | YES | VERIFIED | +| `packages/portal/components/mobile-more-sheet.tsx` | Bottom sheet secondary nav with RBAC | YES | YES | YES | VERIFIED | +| `packages/portal/app/manifest.ts` | PWA manifest with K monogram icons | YES | YES | YES | VERIFIED | +| `packages/portal/app/sw.ts` | Serwist SW + push + notificationclick handlers | YES | YES | YES | VERIFIED | +| `packages/portal/components/sw-register.tsx` | Service worker registration | YES | YES | YES | VERIFIED | +| `packages/portal/components/offline-banner.tsx` | Offline status indicator | YES | YES | YES | VERIFIED | +| `packages/portal/components/mobile-chat-header.tsx` | Back arrow + agent name for mobile chat | YES | YES | YES | VERIFIED | +| `packages/portal/lib/use-visual-viewport.ts` | Visual Viewport hook for iOS keyboard offset | YES | YES | YES | VERIFIED | +| `packages/portal/components/install-prompt.tsx` | Second-visit PWA install banner (Android + iOS) | YES | YES | YES | VERIFIED | +| `packages/portal/components/push-permission.tsx` | Push opt-in with permission state machine | YES | YES | YES | VERIFIED | +| `packages/portal/lib/message-queue.ts` | IndexedDB offline message queue | YES | YES | YES | VERIFIED | +| `packages/portal/app/actions/push.ts` | Server actions for push (planned artifact) | NO | — | — | MISSING | +| `packages/gateway/routers/push.py` | Push API endpoints (planned path) | NO* | — | — | MISSING* | +| `packages/shared/shared/api/push.py` | Push API (actual path — different from plan) | YES | YES | YES | VERIFIED | +| `migrations/versions/012_push_subscriptions.py` | push_subscriptions table migration | YES | YES | YES | VERIFIED | +| `packages/portal/public/icon-192.png` | PWA icon 192x192 | YES | YES | — | VERIFIED | +| `packages/portal/public/icon-512.png` | PWA icon 512x512 | YES | YES | — | VERIFIED | +| `packages/portal/public/icon-maskable-192.png` | Maskable PWA icon | YES | YES | — | VERIFIED | +| `packages/portal/public/apple-touch-icon.png` | Apple touch icon | YES | YES | — | VERIFIED | +| `packages/portal/public/badge-72.png` | Notification badge icon | YES | YES | — | VERIFIED | + +> NOTE: `app/actions/push.ts` and `gateway/routers/push.py` are listed in the plan frontmatter but were deliberately implemented differently. Push subscription management is handled via direct `fetch` in `push-permission.tsx` to `/api/portal/push/subscribe`, which is served by `shared/api/push.py` (consistent with all other API routers in this project). The plan's artifact list is outdated; no functional gap exists for push subscription flow. These are documentation mismatches, not code gaps. + +--- + +### Key Link Verification + +| From | To | Via | Status | Details | +|-----------------------------------------------|---------------------------------------------|------------------------------------------|------------|-----------------------------------------------------------------------| +| `app/(dashboard)/layout.tsx` | `components/mobile-nav.tsx` | `hidden md:flex` / `md:hidden` pattern | WIRED | Line 44: `hidden md:flex` on Nav div; MobileNav at line 57 is always rendered (md:hidden internally) | +| `next.config.ts` | `app/sw.ts` | withSerwist wrapper | WIRED | Lines 7-11: withSerwistInit configured with swSrc/swDest | +| `app/layout.tsx` | `components/sw-register.tsx` | Mounted in body | WIRED | Line 47: `` in body | +| `app/(dashboard)/chat/page.tsx` | `components/mobile-chat-header.tsx` | Rendered when mobileShowChat is true | WIRED | Lines 271-276: MobileChatHeader rendered inside mobileShowChat block | +| `components/chat-window.tsx` | `lib/use-visual-viewport.ts` | keyboardOffset applied to input | WIRED | Line 32: import; line 77: `const keyboardOffset = useVisualViewport()`; line 268: applied as style | +| `app/(dashboard)/chat/page.tsx` | `mobileShowChat` state | State toggle on conversation select | WIRED | Line 154: useState; lines 173-181: handleSelectConversation sets true | +| `app/sw.ts` | push event handler | `self.addEventListener('push', ...)` | WIRED | Line 24: push event listener; lines 35-46: showNotification call | +| `app/sw.ts` | notificationclick handler | `self.addEventListener('notificationclick', ...)` | WIRED | Line 48: notificationclick listener; deep-link to /chat?id= | +| `gateway/channels/web.py` | `shared/api/push.py` | asyncio.create_task(_send_push_notification) | WIRED | Line 462: asyncio.create_task; line 115: imports _send_push | +| `lib/use-chat-socket.ts` | `lib/message-queue.ts` | enqueue when offline, drain on reconnect | WIRED | Line 17: imports; line 105: drainQueue in ws.onopen; line 208: enqueueMessage in send | + +--- + +### Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|-------------|------------------------------------------------------------------------------|----------------|------------------------------------------------------------------| +| MOB-01 | 08-01 | All portal pages render correctly on mobile (320px–480px) and tablet | HUMAN NEEDED | Layout wiring verified; visual correctness requires browser test | +| MOB-02 | 08-01 | Sidebar collapses (spec: hamburger menu, impl: bottom tab bar) | VERIFIED* | Tab bar implemented — superior to spec'd hamburger; REQUIREMENTS.md text is stale | +| MOB-03 | 08-02 | Chat interface fully functional on mobile — send, stream, scroll history | FAILED | mobileShowChat toggle wired; MobileChatHeader present; BUT uuid() recursive bug in chat-window.tsx blocks message send in secure contexts | +| MOB-04 | 08-01 | Portal installable as PWA with icon, splash, service worker | VERIFIED | manifest.ts, sw.ts, all icons, next.config.ts withSerwist all confirmed | +| MOB-05 | 08-03 | Push notifications for new messages when PWA installed | VERIFIED | Full pipeline: PushPermission -> push.py -> 012 migration -> web.py trigger -> sw.ts handler | +| MOB-06 | 08-02 | All touch interactions feel native — no hover-dependent UI on touch devices | HUMAN NEEDED | `active:bg-accent` classes present on all interactive items; 44px tap targets in all components; needs real device confirmation | + +> *MOB-02: REQUIREMENTS.md says "hamburger menu" — implementation delivers a bottom tab bar per the PLAN design. The tab bar is the intended and superior design; requirement text is stale documentation. + +--- + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|-------------------------------------------------------|------|--------------------------------|----------|--------------------------------------------------------------------------| +| `packages/portal/components/chat-window.tsx` | 18 | `return uuid()` — recursive call | BLOCKER | Infinite recursion when `crypto.randomUUID` is available (all HTTPS + localhost). Chat messages never send; browser tab crashes or hangs. | + +--- + +### Human Verification Required + +#### 1. Responsive Layout at Multiple Viewports (MOB-01) + +**Test:** Open Chrome DevTools, toggle device toolbar, test at 320px (iPhone SE), 768px (iPad), 1024px (iPad landscape). Navigate to Dashboard, Employees, Chat, Usage, and Billing pages. +**Expected:** No horizontal scrolling, no overlapping elements, readable text at all widths. Sidebar visible at 768px+, tab bar visible at 320px. +**Why human:** Visual layout correctness (overflow, overlap, text truncation) requires rendered browser output. + +#### 2. Bottom Tab Bar Navigation and More Sheet (MOB-02) + +**Test:** At 320px, tap each tab (Dashboard, Employees, Chat, Usage). Tap More — verify sheet slides up. Check with `customer_operator` role that Billing/API Keys/Users are hidden in the sheet. +**Expected:** Active indicator on tapped tab; correct page loads; More sheet shows RBAC-filtered items; LanguageSwitcher and push permission toggle visible. +**Why human:** Touch tap feedback, animation smoothness, and RBAC filtering in the rendered session context cannot be verified by static analysis. + +#### 3. Mobile Chat Full-Screen Flow (MOB-03) + +**Test:** At 320px, navigate to Chat. Tap a conversation — verify full-screen mode with back arrow header and agent name. After fixing the uuid() bug (see Gaps), send a message and verify streaming tokens appear word-by-word. Tap back arrow — verify return to list. +**Expected:** WhatsApp-style navigation; streaming tokens render incrementally; back arrow works. +**Why human:** Streaming animation and back navigation flow require browser runtime. The uuid() bug MUST be fixed first. + +#### 4. PWA Installability (MOB-04) + +**Test:** Open Chrome DevTools > Application > Manifest. Verify manifest loads with name=Konstruct, icons at 192/512/maskable, start_url=/dashboard, display=standalone. Check Application > Service Workers for registration. +**Expected:** Manifest loads without errors; service worker registered and active; Lighthouse PWA audit score >= 90. +**Why human:** PWA install flow requires a browser with HTTPS or localhost, Lighthouse, or Android device. + +#### 5. Push Notifications (MOB-05) + +**Test:** Open More sheet, tap bell icon, grant notification permission. Close the browser tab. Trigger an AI Employee response (via API or second browser window). Tap the push notification. +**Expected:** Notification appears on device; tapping notification opens the portal at the correct conversation URL. +**Why human:** Push notification delivery requires running backend services, VAPID keys, and a real browser push subscription. Cannot simulate with grep. + +#### 6. Touch Interaction Native Feel (MOB-06) + +**Test:** At 320px, tap all buttons, links, and interactive elements throughout the portal. Test tab bar items, More sheet links, chat send button, conversation list items. +**Expected:** Immediate visual feedback on tap (active state); no hover-stuck states; all targets reachable with a finger (>= 44px). +**Why human:** Touch interaction feel, hover-stuck detection, and tap target perception require a real touch device or touch simulation in DevTools. + +--- + +### Gaps Summary + +**One blocker gap, one documentation gap.** + +**Blocker: Infinite recursion in `uuid()` (chat-window.tsx line 18)** + +The `uuid()` helper function in `chat-window.tsx` was written to fall back to a manual UUID generator when `crypto.randomUUID` is unavailable (e.g., HTTP non-secure context). However, the branch that should call `crypto.randomUUID()` calls `uuid()` itself recursively. In any secure context (HTTPS, localhost), `crypto.randomUUID` is always available, so every call to `uuid()` immediately recurses infinitely — causing a stack overflow. Chat messages in the portal require `uuid()` to generate stable IDs for optimistic UI updates (lines 100, 148, 158, 198). The fix is one character: change `return uuid()` to `return crypto.randomUUID()` on line 18. + +**Documentation gap: REQUIREMENTS.md MOB-02 text is stale** + +The REQUIREMENTS.md describes MOB-02 as "hamburger menu" but the design (defined in the PLAN and implemented in the codebase) uses a bottom tab bar — a more native mobile pattern. This is a documentation-only mismatch; the codebase correctly implements the intended design. Updating REQUIREMENTS.md to say "bottom tab bar" would bring the documentation in sync with the actual implementation. + +--- + +_Verified: 2026-03-26T03:35:51Z_ +_Verifier: Claude (gsd-verifier)_ diff --git a/packages/portal b/packages/portal index 77bd305..a9fc540 160000 --- a/packages/portal +++ b/packages/portal @@ -1 +1 @@ -Subproject commit 77bd305fd14274a74d3516ab1ce88e84ed6bcddc +Subproject commit a9fc5407b3a097c3ead62be224a84b1d2354c4ba