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>
20 KiB
phase, verified, status, score, gaps, human_verification
| phase | verified | status | score | gaps | human_verification | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-mobile-pwa | 2026-03-26T03:35:51Z | gaps_found | 11/13 must-haves verified |
|
|
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.tsandgateway/routers/push.pyare listed in the plan frontmatter but were deliberately implemented differently. Push subscription management is handled via directfetchinpush-permission.tsxto/api/portal/push/subscribe, which is served byshared/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 (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)