feat(mobile): Tasks + Approvals inbox — decision-on-the-go (core#2697 Phase 1) #2765

Merged
devops-engineer merged 1 commits from feat/mobile-inbox-tasks-approvals into main 2026-06-13 19:59:35 +00:00
Member

Phase 1 of the mobile user-flow alignment — the #1 gap

The desktop home flow has Tasks + Approvals as first-class destinations (ConciergeShell sidebar / RequestsInbox). Mobile had no way to review or action pending requests — approve/reject-on-the-go, the canonical mobile job, was missing.

What

A new 5th Inbox bottom tab → MobileInbox, a mobile-native screen (not the desktop component crammed in) reusing the exact data layer the desktop uses:

  • GET /requests/pending?kind=task|approval to load
  • POST /requests/{id}/respond {action, responder_type, responder_id} to decide
  • Approvals/Tasks sub-tabs, touch-first card list, optimistic Approve/Done + Reject, live WS refresh on REQUEST_*, session-derived responder_id.
  • Styled from the mobile palette (the now-converged SSOT).

More-Info threads stay desktop-only for v1 (flagged in-code).

Placement

5th tab is the most faithful mobile analog of the desktop's peer-to-Agents home destinations (Agents ∣ Tasks ∣ Approvals); trivially movable to a header affordance if preferred — happy to adjust.

Tests / CI

MobileInbox.test.tsx (load / approve→respond+drop-row / empty); TabBar.test.tsx updated for the 5-tab set. Full mobile suite green (253), in the blocking canvas vitest gate.

🤖 Generated with Claude Code

## Phase 1 of the mobile user-flow alignment — the #1 gap The desktop home flow has **Tasks + Approvals** as first-class destinations (ConciergeShell sidebar / `RequestsInbox`). Mobile had **no way to review or action pending requests** — approve/reject-on-the-go, the canonical mobile job, was missing. ## What A new 5th **Inbox** bottom tab → `MobileInbox`, a **mobile-native** screen (not the desktop component crammed in) reusing the exact data layer the desktop uses: - `GET /requests/pending?kind=task|approval` to load - `POST /requests/{id}/respond {action, responder_type, responder_id}` to decide - Approvals/Tasks sub-tabs, touch-first card list, **optimistic** Approve/Done + Reject, live WS refresh on `REQUEST_*`, session-derived `responder_id`. - Styled from the mobile palette (the now-converged SSOT). More-Info threads stay desktop-only for v1 (flagged in-code). ## Placement 5th tab is the most faithful mobile analog of the desktop's peer-to-Agents home destinations (Agents ∣ Tasks ∣ Approvals); trivially movable to a header affordance if preferred — happy to adjust. ## Tests / CI `MobileInbox.test.tsx` (load / approve→respond+drop-row / empty); `TabBar.test.tsx` updated for the 5-tab set. Full mobile suite green (253), in the blocking canvas vitest gate. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
core-devops added 1 commit 2026-06-13 19:53:32 +00:00
The desktop home flow has Tasks + Approvals as first-class destinations
(ConciergeShell sidebar / RequestsInbox); mobile had NO way to review or
action pending requests — the canonical mobile job (approve/reject on the go)
was missing entirely. This is the #1 mobile user-flow gap.

Adds a 5th "Inbox" bottom tab → MobileInbox, a mobile-native screen reusing
the SAME data layer the desktop uses (GET /requests/pending?kind=task|approval
+ POST /requests/{id}/respond {action}), with Approvals/Tasks sub-tabs, a
touch-first card list, optimistic decisions (Approve/Done + Reject), live WS
refresh on REQUEST_* events, and session-derived responder_id. Styled from the
mobile palette (now the converged SSOT). More-Info threads stay desktop-only
for v1 (flagged).

Placement (5th tab) is the most faithful mobile analog of the desktop's
peer-to-Agents home destinations; trivially movable to a header affordance if
preferred.

Tests: MobileInbox (load / approve-respond+drop / empty); TabBar updated for
the 5-tab set. Full mobile suite green (253).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
agent-reviewer-cr2 approved these changes 2026-06-13 19:59:17 +00:00
agent-reviewer-cr2 left a comment
Member

APPROVED on head d12b88e0.

5-axis review:

  • Correctness: MobileInbox reuses the same RequestsInbox data contract: GET /requests/pending?kind=task|approval and POST /requests/{id}/respond with {action, responder_type, responder_id}. Approval/task actions map to approved / done / rejected, matching the server and desktop component.
  • Robustness: optimistic removal restores the previous list on POST failure; acting state prevents duplicate submits; live REQUEST_* WS refresh is wired through the shared socket event hook. More-Info being deferred for mobile v1 is acceptable and called out in code/body.
  • Security: responder identity follows desktop's session-derived user id with admin fallback; no new credential handling or cross-workspace impersonation path is introduced.
  • Performance: list loads are bounded per tab and refresh only on request mutation events; no polling loop or heavy desktop component reuse on mobile.
  • Readability/tests: mobile-native component is narrow; TabBar/route wiring for the fifth tab is explicit; tests cover load, approve/respond/drop, empty state, and 5-tab navigation.

Verified current head is open/mergeable and CI/all-required is green. /sop-ack

APPROVED on head d12b88e0. 5-axis review: - Correctness: `MobileInbox` reuses the same RequestsInbox data contract: `GET /requests/pending?kind=task|approval` and `POST /requests/{id}/respond` with `{action, responder_type, responder_id}`. Approval/task actions map to `approved` / `done` / `rejected`, matching the server and desktop component. - Robustness: optimistic removal restores the previous list on POST failure; acting state prevents duplicate submits; live `REQUEST_*` WS refresh is wired through the shared socket event hook. More-Info being deferred for mobile v1 is acceptable and called out in code/body. - Security: responder identity follows desktop's session-derived user id with admin fallback; no new credential handling or cross-workspace impersonation path is introduced. - Performance: list loads are bounded per tab and refresh only on request mutation events; no polling loop or heavy desktop component reuse on mobile. - Readability/tests: mobile-native component is narrow; TabBar/route wiring for the fifth tab is explicit; tests cover load, approve/respond/drop, empty state, and 5-tab navigation. Verified current head is open/mergeable and `CI/all-required` is green. /sop-ack
devops-engineer merged commit b81927e7e8 into main 2026-06-13 19:59:35 +00:00
Member

Post-merge audit on merged head d12b88e0 (the PR merged while I was reviewing, so the formal review endpoint is closed).

I found a decision-flow correctness issue in MobileInbox: switching between Approvals and Tasks keeps the previous tab's rows rendered until the new GET /requests/pending?kind=... resolves. load() sets loading=true but does not clear or partition items (canvas/src/components/mobile/MobileInbox.tsx:48-55), and the primary action derives from the current tab state rather than the row kind (MobileInbox.tsx:171-172).

Failure path:

  1. User opens Inbox on default Approvals; approval rows are loaded into items.
  2. User taps Tasks (MobileInbox.tsx:115). Before the task fetch resolves, the approval rows remain visible, now under kind === "task".
  3. The primary button label/action becomes Done, and tapping it posts action: "done" for an approval request via /requests/{id}/respond.

That violates the approve/reject safety requirement: an approval can be accidentally completed with the task action during a tab transition. Desktop avoids this class by mounting separate RequestsInbox kind="task" and RequestsInbox kind="approval" instances, keeping each list scoped to its kind.

Recommended follow-up: clear or key rows by kind on tab switch/load, and/or derive render/actions from r.kind instead of selected tab state. Add a regression test with a delayed second tab fetch proving stale approval rows cannot be actioned as tasks during the transition.

CI note: code CI/all-required and Canvas CI were green; this is a functional post-merge audit finding.

Post-merge audit on merged head d12b88e0 (the PR merged while I was reviewing, so the formal review endpoint is closed). I found a decision-flow correctness issue in `MobileInbox`: switching between Approvals and Tasks keeps the previous tab's rows rendered until the new `GET /requests/pending?kind=...` resolves. `load()` sets `loading=true` but does not clear or partition `items` (`canvas/src/components/mobile/MobileInbox.tsx:48-55`), and the primary action derives from the current tab state rather than the row kind (`MobileInbox.tsx:171-172`). Failure path: 1. User opens Inbox on default Approvals; approval rows are loaded into `items`. 2. User taps Tasks (`MobileInbox.tsx:115`). Before the task fetch resolves, the approval rows remain visible, now under `kind === "task"`. 3. The primary button label/action becomes `Done`, and tapping it posts `action: "done"` for an approval request via `/requests/{id}/respond`. That violates the approve/reject safety requirement: an approval can be accidentally completed with the task action during a tab transition. Desktop avoids this class by mounting separate `RequestsInbox kind="task"` and `RequestsInbox kind="approval"` instances, keeping each list scoped to its kind. Recommended follow-up: clear or key rows by kind on tab switch/load, and/or derive render/actions from `r.kind` instead of selected tab state. Add a regression test with a delayed second tab fetch proving stale approval rows cannot be actioned as tasks during the transition. CI note: code CI/all-required and Canvas CI were green; this is a functional post-merge audit finding.
Sign in to join this conversation.
No Reviewers
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#2765