Skip to content

feat(mobile): one surface for capture and ask (no second screen) #331

Description

@jayesh-keychain

Goal

One surface for capture and ask. The existing capture screen gains an ask mode — same screen, same composer, no second route. Capture stays the default on every launch.

Supersedes #321, which drifted toward a separate /ask screen. There is no separate screen.

Why now

Mobile implements one of the four verbs. api.ask() landed in #330 but nothing calls it — a user can put memories in from their phone and ask nothing of them. Recall works end-to-end on the API and in Admin; this is purely the missing mobile surface.

Design constraints (learned the hard way — do not rediscover)

The vertical axis is taken. apps/mobile/app/index.tsx drives BOTH hold-to-talk and slide-up-to-type from a single vertical Pan (beginHold, slidingRef, dragY). Mode switching MUST be horizontal, with activeOffsetX set so a mostly-vertical drag never switches mode and a mostly-horizontal one never starts a recording. This is not theoretical: adb vertical swipes during testing repeatedly started voice recordings instead of opening the composer, and the emulator has no mic so it wedged (pcm_writei failed flooding logcat).

One composer, two intents. Reuse the existing text input rather than building a second one. The mode changes the placeholder, the submit action, and what renders below — not the input itself.

Capture is the default, always. Do not persist the last mode. Capturing is reflexive; asking is deliberate. A user who opens the app to dump a thought must never land in ask mode.

A grounded miss is not an error. The server returns 200 with an empty citations array and floored confidence when nothing clears the §05.6 miss band (documented at api.ask in src/lib/api.ts). Render it as a calm "nothing on this yet" — visually distinct from the failure state, which is reserved for a thrown ApiError.

How to do it

  • Mode state in index.tsx; horizontal Pan (or Gesture.Race with the existing one) with a clear activation threshold.
  • A visible, non-chrome mode indicator consistent with the monochrome ui-spec (Part 1A) — one field, white ink, no second palette.
  • Ask submits via api.ask(), which already inherits fix(mobile): refresh the expired session instead of losing the capture #318's one-shot refresh-and-retry, so a question asked long after sign-in re-authenticates instead of being lost.
  • Render answer + citations inline on the same surface.

Out of scope

Acceptance criteria

  • The app opens in capture mode on every launch, including after force-stop.
  • Hold-to-talk and slide-up-to-type behave EXACTLY as before — no regression in capture.
  • A horizontal swipe switches to ask and back, with a visible indicator of the current mode.
  • A mostly-vertical drag never switches mode; a mostly-horizontal drag never starts a recording or opens the capture composer.
  • Asking calls POST /memory/ask and renders the answer with its citations, on the same screen.
  • A grounded miss renders as an honest "no memory" state, visually distinct from an error.
  • A 401 mid-ask refreshes and retries once rather than dropping the question.
  • There is NO second route/screen for ask.
  • Verified on a running emulator, not only type-check. Gesture surfaces are hard to drive via adb — pair this with test(mobile): verify the #318 token-refresh fix on-device + add a gesture-free capture seam #324's gesture-free test seam.

Metadata

Metadata

Assignees

No one assigned

    Labels

    module: recallAsk verb / read path.priority: P1Important. Needed for the phase, but not blocking the loop.scope: mobileExpo mobile app.size: L~1 week.type: featureNew capability.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions