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
feat(mobile): Tasks + Approvals inbox — decision-on-the-go (core#2697 Phase 1)
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 6s
CI / Python Lint & Test (pull_request) Successful in 5s
sop-checklist / review-refire (pull_request_target) Has been skipped
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Harness Replays / detect-changes (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 11s
Harness Replays / Harness Replays (pull_request) Successful in 2s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 10s
reserved-path-review / reserved-path-review (pull_request_target) Successful in 7s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
CI / Detect changes (pull_request) Successful in 17s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 17s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 5s
E2E Chat / detect-changes (pull_request) Successful in 18s
gate-check-v3 / gate-check (pull_request_target) Failing after 14s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 16s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Platform (Go) (pull_request) Successful in 2s
E2E API Smoke Test / detect-changes (pull_request) Successful in 21s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 3s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request_target) Successful in 18s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 29s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 28s
CI / Canvas (Next.js) (pull_request) Successful in 3m43s
CI / Canvas Deploy Status (pull_request) Successful in 1s
CI / all-required (pull_request) Successful in 4s
reserved-path-review / reserved-path-review (pull_request_review) Successful in 7s
qa-review / approved (pull_request_target) Approved via pull_request_review trigger
security-review / approved (pull_request_target) Approved via pull_request_review trigger
qa-review / approved (pull_request_review) Successful in 9s
security-review / approved (pull_request_review) Successful in 10s
audit-force-merge / audit (pull_request_target) Successful in 7s
d12b88e007
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