Files
konstruct/.planning/phases/08-mobile-pwa/08-VERIFICATION.md
Adolfo Delorenzo a9077e3559 docs(phase-8): complete Mobile + PWA phase execution
Fixed uuid() recursion bug, updated MOB-02 requirement text.
All 8 phases complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:39:04 -06:00

197 lines
20 KiB
Markdown
Raw 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.
---
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: `<ServiceWorkerRegistration />` 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 (320px480px) 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)_