Skip to content

ai-partner: first-use privacy opt-in flow #1458

@CraigBuckmaster

Description

@CraigBuckmaster

Parent epic: #1446 (Amicus — AI Study Partner v1)
Phase: 2 · Size: S

Before the first time a user sends a query to Amicus, show a one-time modal that clearly discloses what leaves the device and why. Required for ethical P2 positioning and App Store review defensibility.


Files to create

  • app/src/components/amicus/AmicusFirstUseModal.tsx — the modal component
  • app/src/components/amicus/__tests__/AmicusFirstUseModal.test.tsx — component tests

Files to modify

  • app/src/services/amicus/chat.ts — check the opt-in flag before the first stream attempt; surface the modal via a callback
  • app/src/screens/AmicusThreadScreen.tsx — mount the modal; handle accept/decline
  • app/src/db/user.ts (or preferences helpers) — add amicus_opt_in_accepted_at preference read/write helpers

Opt-in state

Stored as a user preference (existing user_preferences table):

  • Key: amicus_opt_in_accepted_at
  • Value: ISO timestamp when user accepted, or absent if not yet accepted

Declining does NOT write a negative value — declining simply closes the modal without proceeding. The user can try again later and will see the modal again. This is deliberate: we never want to silently lock users out without a clear action they can take.


Modal UX

Visual treatment matches existing important modals (spacing, typography, safe area). Content:

Header

  • Icon: MessageSquare or similar (lucide, gold)
  • Title: "Meet Amicus" (Cinzel, 22px)
  • Subtitle: "Before we get started" (EB Garamond italic, textMuted)

Body (EB Garamond, comfortable line-height)

Amicus is your scholarly study companion. It answers questions by drawing on the curated Companion Study corpus — our 72 scholars, word studies, debates, and cross-references. It never fabricates scholar positions.

What stays on your device

  • Your notes, highlights, and bookmarks
  • Your full reading history
  • Your Amicus conversations

What gets sent to our AI provider when you ask a question

  • Your question text
  • An abstract summary of your reading patterns (e.g., "recently focused on Pauline epistles, engages with Wright")
  • The retrieved scholarly content your question is answered from

You can inspect exactly what gets sent in Settings → Amicus → Show My Profile.

Our AI provider has a zero-retention commitment. Your data is never used to train models.

Actions

  • Primary button: "I understand, let's begin" — gold pill, full-width
    • On tap: write amicus_opt_in_accepted_at = now(), close modal, proceed with the pending send action
  • Secondary button: "Not now" — text button, textMuted
    • On tap: close modal, abort the pending send action (does NOT write the pref)

Link

  • Small text link under actions: "Read our full privacy commitment" → opens existing privacy policy screen/URL

Integration flow (chat.ts)

async function sendMessage(threadId, query) {
  const acceptedAt = await getPreference('amicus_opt_in_accepted_at');
  if (!acceptedAt) {
    // Trigger modal via UI callback; resolve when user accepts or rejects
    const accepted = await requestFirstUseConsent();
    if (!accepted) return;   // user chose "Not now"
  }
  // ...proceed to streamChat
}

The requestFirstUseConsent hook is provided by the screen that mounts the modal. Implementation suggestion: a React context that exposes requestAmicusConsent(): Promise<boolean> — the provider resolves the promise when user acts on the modal. Keeps consent concerns out of the screens that just need to call it.


Conventions to follow

  • Modal pattern: reuse any existing modal infrastructure; if none, use standard Modal from react-native with fade animation
  • Colors via useTheme(); typography via fontFamily from theme
  • Scrollable body if content exceeds screen height (tablet / small phone compat)
  • Accessibility: modal should trap focus, have accessibilityLabels, and be dismissible via hardware back on Android
  • Analytics events: amicus_opt_in_shown, amicus_opt_in_accepted, amicus_opt_in_declined — fire via existing analytics service if present; otherwise leave stubbed with a TODO comment referencing ai-partner: aggregate privacy-safe analytics #1469 (aggregate analytics)

Acceptance criteria

  • Modal appears the first time a user tries to send an Amicus query
  • Accepting writes amicus_opt_in_accepted_at and proceeds with the send
  • Declining closes the modal without writing the pref; user can try again later and see modal again
  • Modal does NOT appear on subsequent queries after acceptance
  • Modal body content matches spec verbatim (legally-reviewed copy)
  • "Read our full privacy commitment" link opens the privacy policy
  • Android hardware back dismisses the modal (same effect as "Not now")
  • Component tests cover: first render, accept action, decline action, link tap
  • No any types; lint clean

Out of scope

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions