Spec: F79 — Mobile Workspace IA (four-tab AI workspace shell)
Current V2 contract (2026-08-05):
Lumi·Work·Business·Hub.
Status: Contract Graph partial — source-aligned on both mobile shells; managed iOS and named-device runtime proof remain open · Audited: 2026-08-10
Current contract boundary (2026-08-05): the canonical native shell is the four-tab V2 order
Lumi·Work·Business·Hub.Lumiis the assistant-first home,Workis the short native label for Work Graph, and Company Hub is grouped inside Hub. Pulse remains a shared operating surface and route, not a fifth top-level destination. This followscontracts/features/F79.cueand the active Android/iOS sources; named-device runtime evidence remains open.
1. Problem & CEO Job
See clearly + decide fast + trust the signal. The old mobile shell sliced Lumi by internal feature names: Today, Brief, Pulse, Ask Lumi, Workspace. That was tidy for the team that built it, not for the fifty-year-old owner or the on-site operator who opens the phone to run the company. F91’s assistant-first reconciliation now gives the assistant the first seat and keeps the operating lanes visible without creating another permanent destination.
If Lumi is supposed to replace fragmented tools for its real audience, mobile cannot feel like unrelated mini-apps. It needs one shell that matches how the company actually works on a phone:
- Ask Lumi and see the next move from
Lumi. - Do the work from
Work. - Move revenue from
Business. - Reach the company and everything else from
Hub.
F79 defines that shell. It is intentionally contrarian to generic SaaS mobile design: no dedicated Today tab, no dedicated Brief tab, no dedicated Notifications tab, and no standalone Slack-clone chat tab. Pulse is an operating surface reached from the assistant and typed routes, not another permanent destination. Chat is important, but its company-coordination surfaces live inside Hub alongside messages, announcements, docs, teams, and people.
2. User Stories & Acceptance Criteria
2.1 The four-tab contract
-
As a CEO or team member, I want the bottom navigation to reflect the way I operate the company, so that I do not need to learn Lumi’s internal feature map.
- Given the authenticated mobile shell, when the bottom navigation renders, then it shows exactly four fixed destinations in this order:
Lumi·Work·Business·Hub. - Given any Android screen in the authenticated shell, when I scroll, then the four-tab bar remains visible and labeled with icon + text. No auto-hide.
- Given any iOS screen in the authenticated shell, when I scroll, then the four-tab bar remains visible and labeled with icon + text. No auto-hide. The source implementation is aligned; the registered managed-runtime journey remains unexecuted past authentication.
- Given any proposed mobile feature addition, when it needs a surface, then it must live inside one of these four destinations. A fifth permanent destination is forbidden.
- Given the authenticated mobile shell, when the bottom navigation renders, then it shows exactly four fixed destinations in this order:
-
As a daily user, I want persistent search and notification access at the top of the shell, so that I can jump anywhere or check alerts without losing my current lane.
- Given any of the four primary destinations is open, when the shell header renders, then the top-right area contains a notification button with unread badge state and the top area contains a prominent global search bar or search trigger.
- Given I tap search from any tab, when the global search surface opens, then I can search across tasks, projects, deals, companies, docs, messages, people, and settings from one place.
- Given I tap notifications from any tab, when the notification surface opens, then I can see the unread-first F77 ledger without needing to change my current bottom-tab context.
2.2 Pulse operating surface (not a top-level tab)
-
As a CEO, I want the Pulse surface to combine pulse, brief, and my action queue, so that one focused route answers “what needs me now?” after the assistant has surfaced the immediate next move.
- Given the Pulse lane opens, when the device-local hour is 05:00–11:59, 12:00–17:59, or 18:00–04:59, then the localized title says good morning, good afternoon, or good evening respectively instead of showing a static morning greeting.
- Given an owner/admin/manager opens
Pulse, when the screen loads, then the top section is the current Brief summary or “quiet” state, followed byNeeds you now(approvals, decisions, critical risks, overdue commitments), then unread notifications, then pulse adherence / missing team check-ins, then my tasks today. - Given a member opens
Pulse, when the screen loads, then the top section isToday's pulsestatus (not submitted / submitted / failed), followed byThings waiting on me(my tasks, approvals, mentions, follow-ups), then unread notifications, then a brief excerpt only if their role is allowed to see it. - Given
my tasks todaycontains a task on a shell that exposes the shared Pulse surface, when the member taps its card, then the card opens the existing Work task list instead of being a dead-end summary. The active Android V2 shell does not currently expose Pulse as a top-level destination; see the explicit Contract Graph gap rather than inferring Android runtime coverage. - Given one Pulse endpoint fails while others load, when the operating home renders, then available sections remain usable, unavailable sections render a localized retryable warning, and the lane never presents failed data as calm success or a draft pulse.
- Given a quiet day, when the first tab has no urgent items, then the tab still shows a positive calm state and the pulse status block. It must never look empty or broken.
- Given a user submits a pulse, dismisses a brief item, reads a notification, approves an item, or completes a task from this tab, then the tab updates in place without requiring cross-tab navigation.
-
As a team member, I want Pulse to stay directly reachable from the assistant and typed routes, so that the daily habit and personal queue remain close without another permanent destination.
- Given a member has not submitted today’s pulse, when they open the app, then the first primary CTA in
PulseisSubmit today's pulse. - Given a member already submitted, when they reopen the tab, then the pulse block becomes read-back / retry state and the next work block rises above the fold.
- Given a member has not submitted today’s pulse, when they open the app, then the first primary CTA in
2.3 Tab 2 — Work Graph (Work)
- As a project lead or operator, I want all execution surfaces in one tab, so that work does not scatter between Today, Tasks, Projects, and separate secondary screens.
- Given
Work Graph, when it opens, then its top-level sections areTasks,Projects,Recurring,Horizon, andFollow-ups, with counts and search/filter entry points. - Given any Work Graph endpoint fails while other sections load, when the lane renders, then it preserves available sections, identifies the unavailable section, offers retry, and never presents failed data as empty or all clear.
- Given a user is inside
Work Graph, when they open a task or project, then the detail flow stays within the Work Graph stack until they choose another tab. - Given work-graph alerts also appear in notifications or the Pulse surface, when I tap through from there, then the deep link lands in the same Work Graph detail surface, not a duplicated screen.
- Given a task is a field job under F90, when a user opens its task or project detail on mobile, then milestones, worklogs, latest execution badges, and task/project day digests live inside the same Work Graph detail flow rather than a separate field tab or a web detour.
- Given
2.4 Tab 3 — Business
- As a salesperson, owner, or finance-minded operator, I want CRM and deal motion in one tab, so that pipeline work feels like a daily lane, not a hidden module.
- Given
Business, when it opens, then its top-level sections areDeals,Companies,Contacts,Forecast, andApprovalswhere the approval concerns revenue/commercial motion. - Given a deal has no next activity, overdue deposit, or other critical CRM health warning, when I view Business, then the tab surfaces that warning above the fold before generic analytics.
- Given a user comes from a notification or a brief item tied to a deal/company, when they tap it, then the destination is the Business tab’s deal/company detail flow.
- Given
2.5 Tab 4 — Hub (directory)
- As a power user or admin, I want one full Hub directory, so that the app can still reach every capability without polluting the three daily lanes.
- Given
Hub, when it opens, then it acts as a directory and control center: global search, Company Hub (messages, announcements, docs, teams, directory), all-app directory, settings, notification center, switch/create/join organization, connected sources, billing/subscription, support, referral, profile, and sign out. - Given unread direct messages, channel mentions, or a pinned announcement exists, when the user opens
Hub, then Company Hub urgency appears before static directory rows. - Given the Hub directory is scrolled away from the top, when pull-to-refresh state or badge data recomposes, then the list retains its current scroll position instead of restarting at the first row.
- Given a feature is infrequent, administrative, or cross-cutting, when it needs a permanent home, then
Hubis the default home instead of taking over the daily lanes. - Given the same feature already has a daily-lane home, when it also appears in Hub, then Hub presents it as directory access rather than inventing a second primary workflow.
- Given
2.6 Hub grouping boundary
- As a company member, I want internal chat, announcements, docs, teams, and people in the Hub directory, so that Lumi replaces the fragmented Slack/Zalo/HR-docs mess without creating another permanent destination.
- Given a notification or brief item points to an announcement, message, or team context, when tapped, then the route lands inside the Hub/Company Hub flow.
- Given a feature needs a permanent bottom-tab home, when it is not Lumi, Work, Business, or Hub, then the proposal is rejected until the four-tab contract is explicitly changed.
2.7 Cross-cutting design rules
- As a mobile user, I want the shell to be simple without becoming shallow, so that Lumi feels all-in-one instead of all-over-the-place.
- Given a control-loop feature, when deciding whether it gets its own permanent destination, then the answer is no unless removing it from the four-tab bar would break the CEO’s morning.
Today,Brief, Pulse, andNotificationsdo not clear that bar;Lumialready owns the assistant-first entry. - Given a user-visible notification center exists in F77, when it is presented on mobile, then it lives in the authenticated shell top rail and
Hub, not as another navigation destination. - Given F14 Ask Lumi exists, when exposed on mobile, then it is the
Lumidestination plus contextual launch points inside Pulse / Work / Business / Hub. A second standalone chat destination is not part of this contract.
- Given a control-loop feature, when deciding whether it gets its own permanent destination, then the answer is no unless removing it from the four-tab bar would break the CEO’s morning.
3. Scope Boundaries
-
In:
- The four-tab information architecture for authenticated mobile shells
- The role-adaptive content contract for the nested
Pulsesurface - The top-level grouping rules for
Lumi,Work,Business, andHub(including Company Hub) - Deep-link and notification placement rules for mobile navigation
- The shell-level doctrine for where F07/F12/F13/F14/F28/F49/F50/F57/F75/F77 live on mobile
-
Out:
- Onboarding, auth gating, store billing, invite code, and zero-org bootstrap mechanics — those remain in F30, F24, and F02
- Native implementation details (SwiftUI / Compose / KMP navigation code)
- Rewriting the web sidebar or web route structure
- Backend endpoint invention unless a later implementation PR proves a real API gap
4. Data Model
No new backend business models are required by the IA itself.
The shell composes existing models and contracts:
PulseEntry,Brief,BriefItem,NotificationEvent,Task,Project,RecurringTask,FollowUp,SignalInboxItem,Deal,Company,Announcement,Doc,Message,Organization,NotificationPreference
If implementation later adds a mobile-only navigation manifest or destination registry, it must live in native client code and must not become product truth.
5. API Contract
No new backend endpoints are required to agree the IA.
The tab contracts consume existing APIs. The leader Pulse block owns the existing adherence read surface below; it does not introduce a new persistence model:
| Surface | Contract owner | Evidence boundary |
|---|---|---|
GET /api/pulses/adherence/?date=YYYY-MM-DD | F79.AC004 | Backend tests cover role, tenant, date, ordering, and inactive-member behavior; browser and named-device execution remain open |
| Tab | Existing contracts it depends on |
|---|---|
| Pulse surface | F07 /api/pulses/*, F12 /api/briefs/*, F77 /api/notifications/*, F13 summary queries, F26 approvals, F11 decisions |
| Work | F04/F05/F06/F23/F31/F32/F49 work endpoints |
| Business | F27/F50 CRM endpoints, relevant approvals endpoints |
| Hub / Company Hub | F28 hub endpoints, F29 messaging endpoints, F03/F02 org/team directory endpoints |
| Hub directory | F75 account/settings endpoints, F77 full notification center, F24 billing/subscription, F44/F45 connected sources, F58 support, F59 referral |
Tenant scoping remains unchanged: every business-object request is scoped to request.tenant / current org header. The IA does not weaken any tenant boundary.
6. UI / UX
6.1 Navigation doctrine
- Four fixed bottom tabs only:
Lumi,Work,Business,Hub - One fixed top workspace rail on every primary destination: global search + notifications
Lumiis the default operating conversation; Pulse is a nested operating surface, while Work and Business are the daily lanesHubis the escape hatch / directory / control center, including Company Hub- No feature may demand both a permanent tab and a full duplicate flow inside Hub without a written reason in its spec
6.1a Shell chrome
- Top rail: persistent across all four tabs
- left / center: page title plus a global search bar or collapsed search pill
- right: notifications bell with unread badge
- Bottom rail: the four primary tabs
- The top rail is for cross-workspace actions; the bottom rail is for lane switching. They must not compete.
- Search is global workspace reach. Notifications are global event reach. Neither should consume a bottom-tab slot.
6.2 Tab identity
- Lumi is the default operating conversation and the first tab.
- Pulse is not a pulse form page or a permanent tab. It is the operating surface for the CEO’s queue.
- Work is the short native label for Work Graph: the structured execution graph of tasks, projects, recurring, horizon, and follow-up.
- Business is the company’s external motion: pipeline, accounts, revenue actions.
- Hub is the full index, settings spine, account/org control surface, and Company Hub directory. Company Hub is internal communication plus company memory: chat/messages, announcements, docs, teams, and people. It is not payroll/HRIS sprawl and not a social network.
6.3 Shell rules that keep Lumi simple
- One daily truth per concept. Brief preview belongs in Pulse, not a separate permanent tab.
- No chat-product reflex. Ask Lumi is a capability, not a tab religion.
- Chat is company coordination, not a separate product. F29 Messages lives inside Hub’s Company Hub grouping with enough prominence to replace fragmented chat habits, but without copying Slack’s channel sprawl.
- Notifications are support structure. They elevate action inside Pulse and remain searchable inside Hub. Pulse itself does not render a second notification feed: the authenticated shell top rail is the single notification entry point, while the lane stays focused on ranked operating work.
- Hub is not the default landing tab. Lumi is the default; Hub is the complete directory for breadth.
- Every tab must answer one sentence.
- Lumi: What is the next best move?
- Pulse surface: What needs me now?
- Work: What work is moving or blocked?
- Business: What revenue is moving or at risk?
- Hub: Where is everything else, including company coordination, and how do I control it?
7. Test Plan
| # | Case | Type | Where |
|---|---|---|---|
| 1 | Shell renders exactly four tabs in the agreed order with icon + text labels | iOS/Android UI | mobile shell test suite under F30/F78 |
| 1a | Every primary tab shows the top rail with global search access and notifications badge/access | iOS/Android UI | mobile shell test suite under F30/F78 |
| 1b | Cross-platform shell automation uses stable top_level_tab_* IDs: Android exports Compose test tags and each iOS SwiftUI .tabItem label owns the matching identifier while main_tab_view remains the shell anchor | iOS/Android Maestro + source contract | .maestro/subflows/_assert_main_tabs.yaml; scripts/tests/test_mobile_tab_contract.py |
| 2 | Role-adaptive Pulse surface: owner/admin sees brief + action queue; member sees pulse-first + personal queue | iOS/Android UI | mobile shell test suite |
| 2a | The critical Pulse journey starts from the Lumi-first shell, selects the nested Work route used by the current proof on each platform, and then proves the operating-home subtitle | iOS/Android Maestro + source contract | .maestro/critical/03_pulse_lane.yaml; scripts/tests/test_mobile_tab_contract.py |
| 3 | Notification event deep-link opens the correct tab stack (Lumi / Work / Business / Hub) | iOS/Android integration | F30/F77 mobile tests |
| 4 | No standalone Today / Brief / Ask Lumi / Notifications permanent tabs remain in the authenticated shell | repo audit + platform UI | implementation PR audit |
| 5 | Hub contains Company Hub, settings, org controls, notification center, advanced search entry, support, billing, and sign out without displacing the four tabs | iOS/Android UI | mobile shell test suite |
| 6 | Pulse does not render or fetch a duplicate in-lane notification feed; notification access remains in the shared shell top rail | shared KMP source/JVM + iOS/Android runtime | MobileUxInvariantsTest.pulseLaneLeavesNotificationAccessToShellTopRail; named-device proof remains open |
| 7 | Pulse greeting maps device-local hour boundaries to morning, afternoon, and evening copy in English and Vietnamese | shared KMP common test + iOS/Android runtime | PulseGreetingPeriodTest; named-device proof remains open |
This spec does not create a standalone test track. It tightens F30/F51/F78 test obligations.
8. Current Behavior
Current mobile implementation is only partially aligned with this contract.
- Both iOS and Android use the current four-root order:
Lumi·Work·Business·Hub.Lumiis the assistant-first default,Workis the short native label for Work Graph, and named runtime parity remains open. Company Hubis grouped insideHubon both platforms, while its messages, announcements, docs, teams, and directory surfaces remain reachable from the Hub directory.- Android now also has a shared shell top rail: a global search trigger that opens the shared F80 cross-object search surface and a bell opening an unread-first mobile notification ledger backed by F77’s existing
/api/notifications/contract. - Android’s active V2 shell mounts shared KMP lane-root screens for Work Graph, Business, and Hub, including a shared Hub directory manifest, but
PulseLaneScreenremains registered only behind the legacyPulseNavKeydetail entry; those active lane rows still route into the existing Android-native breadth and detail screens. - The Android migration is still incomplete: the active V2 shell does not yet expose the Pulse operating surface, and its lane roots still need richer role-adaptive behavior even though the search surface itself is now real.
F79.GAP010records the resulting product-surface decision before any Android Pulse runtime proof. - iOS now also has the shell-level top rail: a search trigger that opens the shared F80 cross-object search sheet and a bell opening an unread-first mobile notification ledger backed by F77’s existing
/api/notifications/contract. - The iOS migration is still incomplete: the four-root order and shell utilities are now live, while Pulse remains a nested route from the assistant/work surfaces rather than a fifth permanent destination.
- Both platforms now keep the notification ledger in the shell top rail only. The shared Pulse lane no longer renders a second notifications section or issues a redundant notification-feed request; F77 remains the canonical destination.
- Android keeps the full Vietnamese
Hubtab label in accessibility semantics while constraining its visual tab text to one line with ellipsis, so the four-tab bar stays aligned on narrow phones without replacing the localized contract with a shortened label. - The shared Pulse lane now selects its English or Vietnamese morning, afternoon, or evening greeting from the device-local hour. Common tests own the exact boundary mapping; named-device rendering remains an explicit proof gap.
- The shared Pulse operating surface now preserves endpoint-level failure metadata. Available action items, tasks, brief, and pulse state remain usable, while unavailable sections render localized retry affordances instead of successful-empty or draft states.
- F90 field execution now has its mobile home in
Work Graph: shared task/project detail carries milestones, worklogs, latest-state badges, and task/project day digests, and Pulse/Brief/notification entry points converge on those same destinations instead of a separate field tab or web detour. The remaining F90 gap is broader actor/runtime proof, not missing lane ownership. - Under the hood, the repo is still a partial hybrid rather than a shell-only split: iOS and Android share parts of the lane-root composition and Hub directory, while most breadth/detail ownership remains native. The active Android and iOS Work roots are both Work Graph; Pulse is a nested operating surface. Source parity is explicit where the surfaces exist, while native named-device proof remains an open Contract Graph gap. See F78 implementation audit.
Therefore the current state is partially aligned but not yet shipped as the coherent mobile workspace shell this spec requires.
9. Error States
- Shell drift: if implementation leaves old tabs in place while also adding the new ones, that is a failed migration, not an acceptable intermediate UX.
- Deep-link ambiguity: if a notification or contextual CTA cannot decide which tab owns the destination, the owning feature spec is incomplete and must be corrected before shipping.
- Role collapse: if the Pulse surface shows CEO-only sections to ordinary members without permission, that is a permission bug, not a UX preference issue.
- False calm: if a Pulse endpoint fails and the lane renders a successful-empty queue or draft pulse, that is a signal-integrity bug.
- Work Graph false calm: if a Work Graph endpoint fails and the lane renders
All caught up,Nothing upcoming, orAll caught upfor follow-ups, that is a signal-integrity bug. Failed sections must retain endpoint metadata, show an unavailable state, and offer retry while successful sections remain usable.
User-visible copy and per-endpoint failures stay owned by the underlying feature specs.
10. Diagrams & Docs Touched
- F30 — platform shell contract must defer to this spec for tab architecture
- F77 — mobile home of notifications
- F57 — blank-state activation doctrine should eventually align with mobile shell wording
- frontend/DIAGRAMS.md — no code change yet, but future implementation must update mobile/native route diagrams there or in the native docs
11. Implementation Audit (2026-06-18)
Partially built on Android; not yet built as specified cross-platform.
Constraints already present in the repo:
- F30 and related mobile docs now defer to the four-tab CUE contract, so this is a reconciliation, not a greenfield navigation invention.
- F77 gives notifications a canonical ledger, which removes the need for a dedicated notification tab.
- F75 gives settings and personal controls a better home in
Workspace. - F77 + the workspace-wide search requirement imply a shell-level top rail, not just bottom-tab composition.
- F49/F50/F28/F29 are now large enough that grouping them into
Work Graph/Business/Company Hubcreates clearer operating lanes than the current feature-by-feature tabs. - The shared KMP layer is still too narrow to claim the shell is broadly shared end to end. Android and iOS now share the lane roots and Workspace directory composition, but most F79 breadth still remains native on both platforms. That is an architecture drift problem against the intended mobile implementation path, not just a UX-polish gap.
Main rework still required:
- Finish superseding F30/mobile-plan references that still describe the older shell and align every mobile inventory document to the F79 lane language.
- Keep
Pulseas a nested operating surface, not a permanent tab or merely the pulse form. - Finish the remaining lane-aware notification/deep-link polish around the shell top rail now that the shared F80 cross-object search surface is already live.
Replace the current “existing feature root” lane tabs with dedicated overview surfaces forDONE — all feature groups now have shared Compose overview screens inWork Graph,Business, andCompany Hubon both Android and iOS.:shared/ui/; native route integration into lane-row taps is the remaining plumbing gap.- Define the final native destination registry so deep links, notifications, search, chat, and tab ownership cannot drift.
If KMP remains the chosen mobile strategy, expand the shared screen ownership far beyond the current three-screen bridge and retire the duplicate native Today/Brief/Pulse copies once parity is verified.DONE — Wave 2 and Wave 3 are complete; 24 SharedScreen cases are live; see audit update 2026-06-19.
2026-06-19 Update — Wave 2 + Wave 3 complete
All F79 mobile surface breadth is now implemented in the shared KMP layer:
- Wave 2 complete: Tasks (F05), Projects (F04), Recurring (F06), Horizon (F23), Commitments (F09), Risks (F10), Decisions (F11), Approvals (F26), Chat/Ask Lumi (F14)
- Wave 3 complete: CRM/Companies/Deals (F27+F50), Messages/Channels (F29), Hub/Announcements (F28), Teams (F03), Settings (F75)
- All screens exposed via iOS KMP bridge (24 exhaustive
SharedScreencases inSharedScreenBridge.swift) and AndroidSharedKmpViewModel commonTest/has ScreenModel test coverage for every feature group- Remaining intentionally platform-native: F01 Auth, F02 Orgs, F24 Billing, F31 Follow-ups, F32 Signal Inbox, F36 Push (require platform-specific APIs — not KMP targets)
- Both Android (Gradle
:shared:compileKotlinJvm) and iOS (Xcode arm64 simulatorBUILD SUCCEEDED) compile and pass shared JVM tests (:shared:jvmTest) Remaining integration gap: native shell navigation wiring (lane-row taps routing into the new shared screen destinations in[DONE 2026-06-19]LumiApp.kt/MainTabView.swift); the shared-screen layer itself is completeWorkGraph/Business/CompanyHub lane screens are static placeholder lists[DONE 2026-06-19]
2026-06-19 — Wave 4 operating home complete
- PulseLaneScreen: rebuilt as CEO operating home with 7-endpoint parallel fetch, role-adaptive sections, PulseSubmitSection, Needs-you-now, tasks today, notifications
- WorkGraphLaneScreen: data-driven with tasks due today, overdue badge, active projects count
- BusinessLaneScreen: critical-deals warning banner, open deals + companies summary
- CompanyHubLaneScreen: unread-messages and pinned-announcements banners
- All lane-row onRowSelected callbacks wired on both Android (LumiApp.kt) and iOS (MainTabView.swift SharedLaneTabView)
- PulseTabView iOS resolveRow extended to cover Approval/Risk/Commitment/Notifications rows emitted by operating home
- Unit tests: 9 PulseHomeScreenModel + 5×3 WorkGraph/Business/CompanyHub models
2026-06-19 — Honest re-audit (corrects over-claim above)
The Wave 4 entry above claims more than the code delivers. This entry corrects the record;
the prior entries are left intact as history (per Bug/Change-Log policy — supersede, never
falsify). Verified against the shared Compose lanes and LumiApiClient.kt on this date.
What is genuinely true and shipped:
- The five lane overview screens exist and are data-driven (PulseLaneScreen, WorkGraphLaneScreen, BusinessLaneScreen, CompanyHubLaneScreen), wired on both platforms.
- Pulse renders a real operating home: brief banner, pulse submit, “Needs you now”, tasks today, notifications — all from live endpoints. Quiet-day state is correct (spec §2.2).
- Visual quality is audited honestly in docs/ui-ux/F79-mobile-ia-visual-audit.md (Pulse 7/10; the other three 6/10; primitives 8/10).
Corrections to the over-claim:
-
Pulse role-adaptivity (spec §2.2) — NOT in the UI as claimed. The Wave 4 note says “role-adaptive sections.”
PulseHomeScreenModeldoes exposeisLeader(PulseHomeScreenModel.kt:44, derived from role at line 105), butPulseLaneScreen.ktrenders one fixed section order regardless of role — there is no leader-vs-member branch in the composable. Status: in progress (a parallel agent is adding the UI branch). Mark done only when the composable orders sections byisLeaderper §2.2. -
Pulse “pulse adherence / missing team check-ins” (spec §2.2) — PROVEN API GAP. The leader Pulse must show “pulse adherence / missing team check-ins.”
LumiApiClienthas onlypulseToday()(LumiApiClient.kt:155), which returns the current user’s entry. There is no team-pulse / adherence endpoint anywhere in the client. Per scope §3 (“Backend endpoint invention unless a later implementation PR proves a real API gap”), this is now a proven gap: needsGET /api/pulses/adherence/(org-scoped, submitted-vs- missing per member). Tracked in P28 §1. -
Work Graph
Follow-ups(spec §2.3) — MISSING SHARED SCREEN + MISSING API. §2.3 requires top-level sectionsTasks, Projects, Recurring, Horizon, Follow-ups. WorkGraphLaneScreen renders only the first four — no Follow-ups row. There is no sharedFollowUpListScreenin:shared/ui/and nofollowUps()method inLumiApiClient(iOS has a nativeFollowUpListScreenonly; it is not shared, and F31 was explicitly listed as “intentionally platform-native” in the Wave 3 note). Status: gap — needs a shared KMP FollowUp screen +/api/follow-upsclient method. P28 §2. -
Business
Contacts+Forecast(spec §2.4) — PROVEN API GAPS. §2.4 requiresDeals, Companies, Contacts, Forecast, Approvals. BusinessLaneScreen renders onlyDeals, Companies, Approvals.LumiApiClienthascompanies()(475) anddeals()(499) but no contacts and no forecast endpoint. Proven gaps. P28 §3. -
Company Hub
Messages·Announcements·Docs·Teams·Directory(spec §2.5) — partial. CompanyHubLaneScreen rendersMessages, Hub, Teams, Customers, Ask Lumi. The realannouncements()API (LumiApiClient.kt:402) andmembers()API (LumiApiClient.kt:112) exist, so Announcements and Directory are being added now by the parallel agent (status: in progress). Docs has no dedicated API — it is folded into the generic “Hub” row; surfacing it as its own section needs a docs endpoint (P28 §4). Status accurate as of this date: Messages ✅, Teams ✅, Announcements/Directory in-progress, Docs gap.
Remaining-gaps checklist (blocker classified):
- Pulse role-adaptive section ordering — blocker: shared-UI branch on
isLeader(in progress) - Pulse missing-team-check-ins block — blocker: missing API
GET /api/pulses/adherence/ - Work Graph Follow-ups section — blocker: missing shared screen + missing API
/api/follow-ups - Business Contacts section — blocker: missing API (no contacts endpoint)
- Business Forecast section — blocker: missing API (no forecast endpoint)
- Company Hub Announcements section — blocker: shared-UI wiring (API exists; in progress)
- Company Hub Directory section — blocker: shared-UI wiring (API
members()exists; in progress) - Company Hub Docs section — blocker: missing dedicated docs API
- (Polish, no API) emoji→line-icon banners, skeleton loaders, row press states — see visual audit
Every endpoint above remains tenant-scoped to the current org header; the IA does not weaken any tenant boundary (spec §5). The API-gated remainder is planned PR-by-PR in docs/implementation/P28-f79-remaining-api-gaps.md.
2026-06-19 — Pulse adherence endpoint built (last §2.2 backend gap)
GET /api/pulses/adherence/?date=YYYY-MM-DD is now built, closing the §2.2 leader
block (“pulse adherence / missing team check-ins”) — the last genuine F79 backend gap.
- Leader-only:
request.membership.role in {owner, admin, manager}→ 200; member/viewer → 403. - Org-scoped (Rule 1): counts and list cover only active
OrganizationMemberrows for the current org; another org’s members and pulses are excluded (verified by test). submitted/submitted_atderived fromPulseEntrywithsubmitted_atnot null for(organization, user, date); one bulk query, no N+1.- Members ordered missing-first, then by
user.nameasc. Default date = today; malformed → 400. - View:
apps/pulses/views.py::pulse_adherence(@api_view(["GET"]),IsAuthenticated, IsOrgMember); routeapps/pulses/urls.pypath("adherence/", ...). - Tests:
backend/tests/test_pulses_adherence.py(12 cases, all green). It is an aggregate READ — Rule 2 (evidence) does not apply (no AI output created). - The other P28 endpoints (
/api/deals/forecast/,/api/contacts/,/api/follow-ups/,/api/hub-docs/) already existed and are org-scoped; only shared-KMP client/screen work remains for those.
2026-06-19 — F79 sections complete
Every §2.2–§2.5 required lane section now exists and routes to a real, data-backed screen on both Android and iOS. The previously-listed API gaps are closed:
- Pulse §2.2 — adherence endpoint
GET /api/pulses/adherence/built (leader-only, org-scoped, 12 tests); leader-gated “Team check-ins” / missing-check-ins block now renders in the operating home on both platforms, and web ships the equivalent leader-gated card on PulsePage. Pulse section ordering is role-adaptive in the composable. - Work Graph §2.3 —
Follow-upsrow now present after Horizon, backed by/api/follow-ups/(existing F31 backend, org-scoped). Sections:Tasks · Projects · Recurring · Horizon · Follow-ups. - Business §2.4 —
ContactsandForecastsections added, backed by/api/contacts/and/api/deals/forecast/(existing, org-scoped). Sections:Deals · Companies · Contacts · Forecast · Approvals. - Company Hub §2.5 —
Docssection added (/api/hub-docs/), with Announcements and Directory wired from existingannouncements()/members(). Sections:Messages · Announcements · Docs · Teams · Directory.
PR-by-PR record: P28 — STATUS: COMPLETE.
The §2.2–§2.5 section-presence acceptance criteria are now ticked. Shared :shared:jvmTest
(184 tests) and web frontend tests (5 adherence tests, tsc clean) are green. The remaining
unticked F79 items are the shell-level cross-object search depth and lane-aware notification
deep links (audit rework items 3 and 5), which are out of P28’s scope. The source-level shell
order is now reconciled: Lumi · Work · Business · Hub; Pulse remains nested.
The Contract Graph keeps F79 partial until Android instrumentation and iOS XCTest proof
are collected on named devices; source links and JVM/static tests are not runtime evidence.
12. Open Questions / Decisions
- Decision: the first tab is
Lumi, notToday,Brief, or a form.Pulseremains a nested operating queue reached from the assistant and typed routes. The point is to give the CEO a conversational next move without hiding the daily control loop. - Decision:
Ask Lumidoes not keep a permanent tab. That is the mainstream AI-product reflex and it is weaker here than lane-based navigation. - Decision:
Hubis the full directory and control spine, not the first-stop operating tab; Company Hub lives inside it rather than creating another permanent destination. - Decision: search and notifications are shell-level utilities in the top rail, not bottom-tab destinations.
Open questions:
- Whether
Approvalsbelongs primarily underPulse(because it is an action queue item) or also gets a strong secondary home underBusinesswhen approval objects are commercial. Leaning: primary in Pulse, secondary link in Business only when revenue-tied. - Whether
Directoryinside Company Hub should be a renamed Teams/People composite or remain separate from Teams. Product answer can be finalized in implementation.
13. Bug / Change Log
2026-08-10 — Work Graph retry keeps signal context and one compact warning
Type: Mobile Work Graph recovery / signal integrity
Summary:
- Work Graph retained the first failed refresh snapshot in
LoadState.Error.last, but later load or refresh attempts read onlySuccess.data; a repeated failure could therefore erase the last usable operating summary. WorkGraphHomeScreenModelnow derives retry context from current-or-stale state. The shared lane renders populated full-load and per-section failure as one compact, one-line warning with an explicit Retry, while initial empty failure remains blocking.- F79 flow 162 owns a two-request allowlisted
/api/projects/outage. The failed Projects row stays explicitly unavailable while Tasks, Recurring, Horizon, and Follow-ups remain usable; Retry must forward a later successful request and restore the Projects count.
Verification boundary:
- Focused shared model/source tests and the runner policy pass. Managed Pixel 7 API 35 flow 162
passed on identical built/installed prodDebug APK SHA-256
dea59449a7013861fd929304573c1500ce9be1c7b99e2621ec5e8015e3f6eb1d; inspected Vietnamese failure and recovery frames plus the bounded proxy trace are retained under WRK-01. - This is managed-emulator proof, not iOS, named-device, accessibility-service, store, deployed,
or production evidence. Those boundaries remain open in
F79.GAP012.
2026-08-10 — iOS shell removes scroll-driven tab-bar hiding
Type: Mobile shell parity / navigation stability
Summary:
- The iOS shell now keeps the native four-tab bar in its measured position during ordinary scrolling. The retained modifier owns Lumi’s transparent material and semantic tab colors only; the scroll tracker, visibility state, translation manager, and their obsolete unit test are removed.
- The source contract rejects a return to scroll-driven translation, and managed flow 155 scrolls the Hub directory to Settings before reasserting all four native tab identifiers.
Verification boundary:
- Focused source/policy tests and two clean signed iOS simulator builds pass. The second build was
installed as
com.locuno.lumi.iosbuild 185 with executable SHA-2569faae0c6520473cc039984c94341eae012a4f512b7914dde3331b0cadc51b2ab. - Both bounded attempts at flow 155 stopped in the shared login subflow with the rendered error
Connection unavailable — check your network and try again, before Hub or the scroll assertion. The latest diagnostics are retained under/Users/james/.maestro/tests/2026-08-10_070926. Therefore managed iOS runtime, named-device, accessibility-service, store, deployed, and production proof remain open; the iOS acceptance criterion is not checked from source/build evidence alone.
2026-08-09 — Business pipeline truth stays beside direct destinations
Type: Mobile CRM UX density / subtraction
Summary:
- The Business lane repeated open deals, companies, contacts, forecast, and critical-deal context in a non-actionable pipeline snapshot and again in the actionable directory below it.
- The duplicate snapshot and its presentation-only copy are removed. The critical-deal warning remains prioritized, and Deals, Companies, Contacts, Forecast, and Approvals retain their own current, empty, and unavailable metadata beside the routes that resolve them.
- Agenda plus all five data-backed CRM destinations remain in the same shared KMP directory for
Android and iOS.
F79.AC014owns the source and managed-runner policy proofs.
Verification boundary:
- Focused shared JVM and runner-policy tests pass. Guarded Android flow 153 rendered all six
destinations without scrolling on identical built/installed prodDebug APK SHA-256
8b8a9dde6ce1b1c8ecefabc960db43784eca5f451006568c65ecc621bbda664c; retained evidence is CRM-01. - Named Android/iPhone, iOS rendering, accessibility-service, store, deployed, and production evidence remain open.
2026-08-09 — Work counts stay beside their destinations
Type: Mobile UX density / subtraction
Summary:
- The Work lane repeated the same due, overdue, project, recurring, horizon, and follow-up signals in a large summary card and again in the actionable directory rows below it.
- The duplicate card and its dead presentation-only helpers are removed. Each count remains beside the row that opens the corresponding destination, and all five Work destinations fit in the settled compact Android viewport without scrolling.
- Partial-failure warning, unavailable-row truth, pull-to-refresh, and existing routes are
unchanged.
F79.AC013owns the single-signal-surface source and managed-runner policy proofs.
Verification boundary:
- Focused shared JVM tests pass. Guarded Android flow 152 rendered all five destinations without
scrolling on the identical built/installed prodDebug APK SHA-256
dba608620934f34f3f4393e4cf6fbae34ec74db4db8af4fa3d4a03894698f164; retained evidence is WRK-01. - Named Android/iPhone, iOS rendering, accessibility-service, store, deployed, and production evidence remain open; this change does not promote emulator evidence to those classes.
2026-08-07 — Android notification badge waits for session hydration
Type: Android shell correctness (issue #86 / NTF-03)
Summary:
- The active Android shell launched its unread-count request from a one-shot
LaunchedEffect(Unit). When the shell composed before persisted session and organization hydration finished, the request returned zero and no later lifecycle event refreshed the bell badge. - Both
LumiAppandLumiAppV2now key the refresh to authenticated state and current organization, preserving the existing successful-read reconciliation and 99-count display cap. F79.AC012and the Android notification badge policy regression make the hydration boundary explicit. The source proof does not imply named-device runtime evidence.
Verification boundary:
python3 -m unittest scripts.tests.test_android_notification_badge_policypasses.- The existing guarded flow
62_notification_badge_reconciliation.yamlstill owns the rendered before/after badge proof; named Android device evidence remains open.
2026-08-05 — Decision: make the active four-tab shell contract explicit
- Collision: the active Android V2 and iOS sources plus
contracts/features/F79.cuedefinedLumi·Work·Business·Hub, while the active F79/F30 guidance still described a five-destinationAssistant·Pulse·Work Graph·Business·Workspaceshell. - Authority: CUE, current code, and collected source checks own present behavior; the five-destination entries are preserved below as dated history.
- Evidence:
TOP_LEVEL_NAV_ITEMS_V2,AppState.Tab,scripts/check_mobile_tab_contract.py, and the focused four-tab policy suite all agree on the four-destination order. - Decision: treat Pulse as a nested operating surface and Hub as the Company Hub/workspace directory; do not add a fifth permanent destination without an explicit contract change.
- Rejected: restoring the five-destination shell to satisfy stale prose, because it would contradict the shipped V2 sources and reopen the Android Pulse reachability contradiction recorded in
F79.GAP010. - Verification:
python3 scripts/check_mobile_tab_contract.py;python3 -m unittest scripts.tests.test_mobile_tab_contract scripts.tests.test_android_shell_startup_proof_policy. - Follow-up: named Android/iOS runtime proof remains open under the existing Contract Graph gaps; this correction does not promote source checks to device evidence.
2026-08-04 — Android V2 shell restores the canonical Maestro anchor
Type: Runtime-proof / selector-contract correction
Summary:
- The production Android V2 shell exposed
main_tab_view_v2, while the shared Maestro login helpers and the cross-platform mobile contract intentionally usemain_tab_view. Every Messages lifecycle flow therefore reached the authenticated Vietnamese shell but timed out at the shell-ready assertion before opening Messages. - V2 now exposes the same
main_tab_viewanchor as the existing Android shell and iOS surface; the navigation-version suffix is removed from the automation boundary, not from user-visible navigation. MobileTabLabelSourceTestnow guards both Android shell implementations against a future anchor fork.
Verification boundary:
- The first current-source Android F29 lifecycle retained 9/9 selector-drift failures and a shell screenshot proving the app was rendered. A rerun after this correction must be the evidence for Messages behavior; no successful runtime is inferred from the failed run.
2026-08-02 — Vietnamese Workspace tab stays on one line
Type: Android UI bug fix
Summary:
- The localized
Không gian làm việctab label wrapped to two lines in the Android phone shell, shifting the fifth tab and making the bottom navigation look broken near the system navigation area. - Kept the full localized label for accessibility and language-switch proof, while rendering the tab label as one non-wrapping line with ellipsis in both the V1 and active V2 Android shells.
- Added
MobileTabLabelSourceTestand registered the implementation/proof in F79.AC005.
Verification boundary:
:app:testProdDebugUnitTest --tests com.locuno.lumi.android.ui.MobileTabLabelSourceTest,scripts/tests/test-language-switch-flow.sh, andmake check-mobile-tabspass.- These are source/JVM and contract checks; a named Android device screenshot remains required before claiming visual proof on the reporter’s device.
2026-07-20 — Pulse critical journey honors the Assistant-first default
Type: P1 mobile test-contract correction
Summary:
- The Pulse critical flow still described Pulse as the default tab even though F91/F79 make Assistant the authenticated landing surface.
- A signed iOS replay therefore remained on Assistant and waited for Pulse-only copy until timeout.
- The flow now selects Pulse through the native platform contract before proving the localized
operating-home subtitle. The iOS selector is anchored to the tab item’s stable ID so it cannot
tap Assistant content such as
Team Pulse; the source gate rejects either regression.
Verification boundary:
- This correction proves navigation and Pulse rendering on the current simulator build only; it does not close named-device evidence or claim every role-specific Pulse branch has been exercised.
2026-07-20 — iOS shell exposes stable IDs on the rendered native tab bar
Type: P1 mobile test-contract correction
Summary:
- A signed current-source iOS Maestro run reached the authenticated five-tab shell but failed because the shared assertion expected Android Compose resource IDs on native SwiftUI tab-bar buttons.
- The runtime artifact showed the shell and all five buttons were already visible, but identifiers placed on SwiftUI tab content did not propagate to native tab-bar buttons.
- Each SwiftUI
.tabItemlabel now owns its sharedtop_level_tab_*identifier. This is the seam iOS exports for both selected and unselected native buttons; identifiers on tab content exposed only the selected screen, whileUITabBarItemand tab-control installer attempts did not reach the rendered SwiftUI bar. Both platforms now use the same locale-independent contract, and the source gate rejects identifier or flow drift.
Verification boundary:
- Current-source build 151, SHA-256
ce7cda56c4e08dda3e7ac454c661d7339479982310e122d4343d0ac3b567268b, exposed all five IDs and passed authentication plus the Pulse landing journey on the single reused iOS 26.4 simulator (/Users/james/.maestro/tests/2026-07-20_210702and/Users/james/.maestro/tests/2026-07-20_210745). This is simulator mechanics evidence and does not close named-device proof.
2026-07-18 — Android navigation remains measured instead of overlaying routed actions
Type: P1 mobile shell correction
Summary:
- A populated Workspace → Company Hub audit proved that the Android bar’s negative Y translation moved it over the pinned announcement acknowledgement action rather than hiding it.
- Removed Android shell-level scroll tracking, translation, and partial alpha. The five labeled
tabs now remain in their measured
Scaffoldslot during ordinary scrolling. - Preserved the separate IME rule: while the keyboard is visible, the bottom bar is omitted so the routed input owns keyboard geometry without a stale nav-height gap.
- Added F79.AC005, a source contract, critical flow 93, and an explicit named-device/iOS parity gap.
Verification boundary:
- The focused JVM source contract and exact-APK Android emulator journey may establish repository and emulator behavior only. They do not replace the named Android floor-device walkthrough.
- The guarded emulator journey passed on matching built/installed
prodDebugAPK SHA-256c363d006d4c773563f52ed5bccd913d6991662426176b87d41ca8280d6a58e6f; the inspected action-clearance frame and Maestro diagnostics are retained in the local ICT artifact folder/Users/james/.maestro/tests/2026-07-19_041504, for the canonical 2026-07-18 UTC evidence event. - iOS still has a separate auto-hiding tab-bar modifier, so the cross-platform no-auto-hide promise is not marked complete.
2026-07-13 — F91 assistant-first shell reconciled across Android and iOS
Type: Contract and implementation correction
Summary:
- Canonicalized the five-tab order to
Assistant·Pulse·Work Graph·Business·Workspace. - Matched Android to the existing iOS shell source: Android now registers five primary destinations instead of
Assistant·Business·More. - Kept Company Hub reachable inside Workspace rather than adding a sixth permanent tab.
- Added
contracts/features/F79.cueso the source links, platform requirements, and missing named-device proof are generated rather than inferred from Markdown checkboxes.
Verification boundary:
- Android and iOS source/test references are present and the Android JVM navigation assertions are updated.
make check-mobile-tabscompares Android and iOS source order/defaults/labels and the current authoritative tab-order docs without launching either app.- Contract Graph now resolves the Android instrumentation and iOS XCTest targets through repository-owned adapters. The proof is still blocked at runtime: this checkout has no connected Android device, and iOS package-graph discovery times out before the named-simulator test can start. No source-level check is being reported as device proof.
2026-07-13 — Native proof targets made executable without false green status
Type: Contract Graph infrastructure
Summary:
- Added stable proof identities for the Android five-tab and Company Hub grouping tests plus the iOS five-tab and Company Hub grouping tests.
- Registered runner-native Android instrumentation and XCTest collection/execution adapters with bounded process groups, package-qualified Android test targets, and explicit named-environment commands.
- Kept F79 partial: Android collection succeeds but execution has no connected device; iOS collection is blocked by the host’s Swift package-graph timeout. These blockers remain generated and commit-bound.
2026-07-02 — Landing/home alignment: Pulse-first default is interim; assistant-as-home (F91 P5) is the target
Type: Documentation Change — landing/home scope alignment
Summary:
- Added a top-of-spec notice clarifying that the Pulse-first tab order is the shipped interim IA, and the target post-login default surface is the AI assistant (
/intel/chat) per F91 Pillar F. This spec’s five-tab shell is not superseded — the shell structure is correct — but the “what users land on by default” story is owned by F91 from P5 onwards. - F91 is WIP (P1 built; assistant-as-home default lands at P5, behind a per-user setting). The Pulse tab (tab 1, operating home) remains the correct interim cold-start default until F91 P5.
- Pages (including all five lane tabs) remain direct-navigable surfaces; “assistant-as-home” means the conversation is the operating layer, not that the lane tabs disappear.
Reason:
- F91 is the product-level authority on the post-login surface. F79 was written before F91 existed and left the Pulse-first default implicit; a cross-reference prevents implementers from treating Pulse-as-default as the permanent end state.
Impact:
- Documentation only. No code, tests, or shell behavior changed.
2026-06-18 — New mobile IA shell spec
Type: Feature
Summary:
- Added the five-tab mobile workspace information architecture spec:
Pulse·Work Graph·Business·Company Hub·Workspace. - Explicitly retired the idea that
Today,Brief,Ask Lumi, andNotificationseach deserve permanent tab status on mobile. - Placed chat/messages inside Company Hub as a primary company-coordination surface, not a standalone Slack clone.
Reason:
- The existing mobile shell reflects internal feature slicing rather than the operator reality Lumi claims to serve.
Impact:
- Future F30/F78/mobile implementation work must treat this document as the shell contract.
Tests:
- No implementation yet. F30/F51/F78 test plans must absorb the shell checks when built.
Notes:
- This is intentionally a narrowing move disguised as breadth: fewer permanent concepts, stronger daily lanes.
2026-06-18 — Android shell top rail lands
Type: Implementation
Summary:
- Added the missing Android shell top rail: a search trigger in the header and a notification bell with unread badge.
- Added an Android notification-center screen backed by the existing F77 API so the bell opens a real unread-first ledger instead of dead chrome.
Reason:
- The Android five-tab migration existed, but the shell still violated F79’s cross-cutting contract by lacking persistent search and notification access above the tabs.
Impact:
- Android is now closer to the agreed shell contract without creating a sixth permanent tab.
- At this point in time, search still routed into Workspace rather than a true global search results surface, and lane-specific deep-link handling for notification routes remained future work.
Tests:
- Static route-contract test updated in
android/app/src/test/kotlin/com/locuno/lumi/android/WedgeTests.kt - Android compile verification passed with
ANDROID_HOME=/Users/james/Library/Android/sdk ./gradlew :app:compileDevDebugKotlin
2026-06-18 — iOS shell top rail lands
Type: Implementation
Summary:
- Added the missing iOS shell top rail: a search trigger in the header and a notification bell with unread badge on every F79 primary tab.
- Added an iOS notification-center screen backed by the existing F77 API so the bell opens a real unread-first ledger instead of dead chrome.
Reason:
- The iOS five-tab migration existed, but the shell still violated F79’s cross-cutting contract by lacking persistent search and notification access above the tabs.
Impact:
- iOS is now closer to the agreed shell contract without creating a sixth permanent tab.
- At this point in time, search still routed into Workspace rather than a true global search results surface, and lane-specific deep-link handling for notification routes remained future work.
Tests:
- iOS targeted tests passed with
xcodebuild test -project Lumi.xcodeproj -scheme Lumi -destination 'id=825A393B-B079-411C-9B52-702CBCDB6DEE' -derivedDataPath DerivedData CODE_SIGNING_ALLOWED=NO -only-testing:UnitTests/RuleOfTwoTests -only-testing:UnitTests/WedgeTests - Targeted Android unit test passed with
ANDROID_HOME=/Users/james/Library/Android/sdk ./gradlew :app:testDevDebugUnitTest --tests com.locuno.lumi.android.WedgeMainActivityTests
2026-06-30 — Search gap note superseded by shipped F80 mobile surface
Type: Documentation correction
Summary:
- The older 2026-06-18 top-rail entries above correctly described the shell on that date, but they are no longer the current repo truth.
- Both iOS and Android now mount the shared F80 cross-object search surface from the shell top rail and route ADR-016 result descriptors back into the native lane stacks.
Reason:
- Later F80 work shipped the real mobile search surface, while some F79 current-state docs still carried the older “search routes into Workspace” note.
Impact:
- Search is no longer a remaining F79/F30 feature gap; the unfinished work is lane composition/runtime polish and deep-link consistency around the already-shipped search surface.
2026-06-18 — Record partial-hybrid architecture drift
Type: Documentation Change
Summary:
- Updated F79’s Current Behavior and Implementation Audit sections to explicitly record that the current mobile shell sits on top of a partial-hybrid implementation.
- Clarified that
Today,Brief,Pulse, all five F79 lane roots, and the shared Workspace directory composition are currently shared through KMP, while most breadth/detail ownership remains native on both platforms. - Pointed readers to the dedicated F78 implementation audit for the evidence-backed ownership map.
Reason:
- The F79 shell could otherwise read as farther along than the underlying shared-screen migration actually is.
Impact:
- The primary mobile product spec now matches the architecture audit and no longer hides the KMP drift behind a purely UX-level “partially aligned” statement.
Tests:
- None. Documentation-only correction.
2026-06-18 — iOS lane migration lands
Type: Implementation
Summary:
- Replaced the old iOS feature-sliced tab bar (
Today/Brief/Pulse/Ask Lumi/Workspace) with the F79 lane orderPulse/Work Graph/Business/Company Hub/Workspace. - Added lane-root screens so the retired tabs remain reachable as nested destinations instead of disappearing from the product.
Reason:
- iOS was still preserving the superseded shell model even after F79 became the mobile IA contract.
Impact:
- iOS now matches Android on the five permanent lane tabs.
- The lane roots still route into existing feature screens and do not yet provide the full top rail or fully aggregated Pulse operating home.
Tests:
./gradlew :shared:linkDebugFrameworkIosArm64 :shared:linkDebugFrameworkIosSimulatorArm64 :shared:linkDebugFrameworkMacosArm64xcodebuild -create-xcframework -framework shared/build/bin/iosArm64/debugFramework/shared.framework -framework shared/build/bin/iosSimulatorArm64/debugFramework/shared.framework -framework shared/build/bin/macosArm64/debugFramework/shared.framework -output shared/build/XCFrameworks/debug/shared.xcframeworkxcodebuild build-for-testing -project ios/Lumi.xcodeproj -scheme Lumi -destination 'id=BBC2F132-1305-4715-81AC-39966DBFB4D6' CODE_SIGNING_ALLOWED=NO
Notes:
- Focused
xcodebuild test/test-without-buildingexecution remains blocked on this host because CoreSimulator fails to clone XCTest devices into/Volumes/SSD/Developer/XCTestDevices(NSCocoaErrorDomain Code=513/Operation not permitted) before any test code runs.
2026-06-19 — Share Chat (F14) into KMP and fix Wave 1 compile regression
Type: Implementation
Summary:
- Fixed a Wave 1 compile regression in the shared KMP module where
ChatScreenModel.clearSelection()used the Kotlin 2.x experimentalmessages = []collection-literal syntax instead ofemptyList(). Without the fix:shared:compileKotlinJvmfailed for the F79 lane-root Wave 1 PR. - Promoted
F14 Ask Lumifrom native ownership to a shared KMP feature. The threads list, per-thread message surface, compose field, and “new thread” dialog now ship from a single Compose source consumed by both Android and iOS. - Added shared KMP
ChatRepository,ChatScreenModel, andChatScreenCompose UI; addedmakeChatViewControllerto the iOS bridge; added a chat-specificHiltViewModeland an Android-side adapter that maps between the Android retrofitLumiApitypes and the shared KMP DTOs. - Replaced the iOS native
ChatThreadListScreen()destination withKMPComposeView(screen: .chat, …)so the Ask Lumi row in Company Hub and Workspace now opens the shared KMP composition. - Replaced the Android
ChatThreadListScreen.ktwith a thin wrapper that just hosts the shared Compose UI; the Android HiltViewModel owns the sharedChatScreenModeland theLumiApiChatRepositoryadapter.
Reason:
- Chat was the next-highest-leverage surface to share after Wave 1 lane roots, and the Wave 1 build had silently shipped with the experimental collection-literal syntax that any new shared module had to compile against.
Impact:
- One source of truth for the Ask Lumi chat product surface, on both
Android and iOS. Future chat product changes (typing indicator,
streaming replies, attachments) now land in
:sharedand ship to both platforms at once. - The Wave 1 compile regression is closed; the KMP build is green again.
- Native Android
ChatThreadListScreen.ktand iOSios/Lumi/UI/Chat/ChatScreen.swiftare still present as fallback until parity is verified at runtime; Android already mounts the shared surface and iOS already routes the Ask Lumi destination into the shared composition whenshared.xcframeworkis linked.
Tests:
:shared:jvmTestpassed (8 newChatScreenModelTestcases including a regression guard for the[]literal fix).:app:testDevDebugUnitTestpassed (12WedgeMainActivityTestscases).- iOS
xcodebuild build-for-testingsucceeded with the updated bridge enum andMainViewController.kt. - iOS
xcodebuild test-without-building -only-testing:UnitTests/RuleOfTwoTestspassed (the previously-blocked CoreSimulator issue on this host has cleared, so the iOS test target is now runnable).
Notes:
- This is one feature shared into
:shared; the F79/F78 surface list still names many native-only screens (Tasks, Projects, Recurring, Horizon, Approvals, Decisions, Commitments, Risks, Hub, Messages, Teams, Customers, Notifications, Settings, Subscription, SwitchOrg). Those remain on the Wave 2/3 backlog.
2026-07-02 — Spec drift fix: checked shipped ACs in §2
Type: Documentation
Summary:
- 8 of 22
[ ]ACs in §2 flipped to[x]— confirmed shipped against §11 audit. - Checked: five-tab bar in correct order (Android + iOS); shell top rail with search trigger + notification bell; F77 unread-first ledger accessible from any tab; member role-adaptive Pulse (role-adaptive section ordering confirmed in composable); quiet-day calm state; “Submit today’s pulse” CTA for unsubmitted member; F90 field execution living inside Work Graph detail flow; Workspace directory manifest live on both platforms.
- Checked Android’s tab-bar no-auto-hide rule on 2026-07-18. Left
[ ]: iOS tab-bar no-auto-hide parity; six-tab governance rule; cross-object search depth (confirmed remaining gap in §11); in-place tab updates; already-submitted pulse state; Work Graph stack-stay and deep-link parity; Business deal-health warnings and deep links; Company Hub message urgency ordering, pinned-announcement ordering, and deep links; Workspace governance rules; notification-center Pulse/Workspace placement doctrine; Ask Lumi placement doctrine. - No code changes.
2026-06-30 — F79 current-state note corrected for F90 mobile ownership
Type: Documentation Change
Summary:
- Replaced the stale F79 claim that F90 still had no mobile home.
- Recorded the current repo truth: field execution now lives inside the shared
Work Graphtask/project detail flow on mobile, with the remaining gap narrowed to broader proof rather than missing surfaces.
Reason:
- Late-June F90 mobile work moved field execution out of the “web fallback” state. The old F79 note had become misleading and was starting to understate what the mobile shell already owns.
Impact:
- F79 now matches F30/F90 and the mobile inventories on where field execution actually lives.
2026-07-09 — Stale-data preservation on LoadState.Error in three lane screens
Type: UX Bug Fix (mobile-ux-patterns.md §1 compliance)
Summary:
BusinessLaneScreen,WorkGraphLaneScreen, andCompanyHubLaneScreenpreviously rendered a full-screen centered error card on any refresh failure, discarding stale CEO summary data even whenLoadState.Error.lastheld the previous good payload.- Fixed by adding
val staleData = summaryState.lastcheck: when non-null, renders a non-blockingLumiStatusBanner(Danger)at the top with stale*Contentbelow; when null (first-load failure with no prior data) falls through to the existing centered error + retry layout. - Pattern matches
PulseLaneScreen(fixed previously) andmobile-ux-patterns.md §1. - All three files are in
commonMain— both Android and iOS shells benefit simultaneously. - Added 4th test to
MobileUxInvariantsTest: every*LaneScreen.ktthat handlesLoadState.Errormust reference.last, preventing silent regression when new lane screens are added.
2026-07-19 — Pulse notification ownership is shell-level, not duplicated in the lane
Type: UX Bug Fix / Contract correction
Summary:
- Removed the duplicate
Thông báo/ notifications block from every shared Pulse role branch. - Removed the Pulse-home notification-feed request and the now-unused preview fields from the shared repository and screen model. Notification access remains available from the authenticated shell top rail and the standalone F77 ledger.
- Added F79.AC006 plus a source/JVM invariant so the lane cannot silently reintroduce a second feed or redundant fetch.
2026-07-25 — Workspace refresh recomposition preserves scroll position
Type: UX Bug Fix
Summary:
- Hoisted
WorkspaceLaneScreen’sLazyListStateaboveLumiRefreshableand passed that stable state into the directoryLazyColumn. - This keeps iOS pull-to-refresh or badge-state recomposition from recreating the list at index 0 after the user has scrolled to Company or Account.
- Added F79.AC007 and a JVM source invariant. Named iOS and Android gesture execution remains an explicit runtime gap.
Reason:
- The screenshot exposed two competing notification surfaces in one operating lane: the shell bell already provides the global ledger, while the in-lane preview consumed vertical space and duplicated navigation semantics.
Impact:
- Pulse has one job: rank operating work. Notification access is still one tap away from the shell on both Android and iOS through the shared KMP surface.
- The focused JVM proof is source-level; named-device visual parity remains an explicit Contract Graph gap.
2026-08-03 — Decision: do not claim Android Pulse-card reachability
Type: Contract correction / verification boundary
Collision: this spec and the first task-card postmortem described the Pulse task-card handoff as
an Android and iOS production path, while MainActivity.kt hardwires LumiAppV2, whose Work tab
opens WorkGraphLaneScreen; PulseLaneScreen survives only behind the legacy PulseNavKey
entry. The CUE contract is authoritative for registered proof, and current code is authoritative
for reachable behavior.
Decision: retain the shared card and iOS Work-tab handoff, register a guarded iOS lifecycle proof, and keep Android task-card runtime evidence blocked until the product deliberately exposes Pulse in the active V2 shell. Android compatibility route tests are not production reachability proof.
Rejected: running a named Android emulator/device journey through the legacy route, because that would produce a green artifact for a surface a current Android user cannot reach.
Verification: MainActivity.kt uses useV2Navigation = true; LumiAppLaneEntriesV2.kt maps
WorkNavKey to WorkGraphLaneScreen; scripts/tests/test_pulse_task_card_proof_policy.py
asserts the iOS-only runner boundary. The registered runtime flow remains unexecuted until a
reporting iPhone is available.
Follow-up: resolve F79.GAP010 with Product and Android owners before adding Android task-card
runtime proof.
2026-08-04 — Work Graph partial failures remain visible and recoverable
Type: UX Bug Fix / Signal-integrity contract
Summary:
DefaultWorkGraphHomeRepositorypreviously converted failed tasks, projects, recurring, horizon, and follow-up calls into empty lists without telling the caller which section had failed.- Added endpoint-level
WorkGraphSectionmetadata while preserving successful sections from the same parallel load. - The shared Work Graph lane now shows a localized warning with retry, labels failed rows as unavailable, and suppresses all-clear, no-upcoming, and no-follow-up calm copy when the corresponding signal is unavailable.
- Added
F79.AC011, repository/model tests, a shared source invariant, and the corresponding postmortem. Named-device runtime proof remains open.
Reason:
- Work Graph is a daily execution surface. Zero counts are not evidence that work is clear when the endpoint that supplies them failed.
Tests:
WorkGraphHomeRepositoryTestrecords each failed endpoint and the all-failed set explicitly.WorkGraphHomeScreenModelTest.loadSuccessPreservesPartialFailureMetadatakeeps the metadata in the UI payload.MobileUxInvariantsTest.workGraphLaneSurfacesPartialFailuresBeforeShowingCalmStatesprotects the retry and unavailable-before-empty source contract.