You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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.
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)
asyncfunctionsendMessage(threadId,query){constacceptedAt=awaitgetPreference('amicus_opt_in_accepted_at');if(!acceptedAt){// Trigger modal via UI callback; resolve when user accepts or rejectsconstaccepted=awaitrequestFirstUseConsent();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
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 componentapp/src/components/amicus/__tests__/AmicusFirstUseModal.test.tsx— component testsFiles to modify
app/src/services/amicus/chat.ts— check the opt-in flag before the first stream attempt; surface the modal via a callbackapp/src/screens/AmicusThreadScreen.tsx— mount the modal; handle accept/declineapp/src/db/user.ts(or preferences helpers) — addamicus_opt_in_accepted_atpreference read/write helpersOpt-in state
Stored as a user preference (existing
user_preferencestable):amicus_opt_in_accepted_atDeclining 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
MessageSquareor similar (lucide, gold)Body (EB Garamond, comfortable line-height)
Actions
amicus_opt_in_accepted_at = now(), close modal, proceed with the pending send actionLink
Integration flow (chat.ts)
The
requestFirstUseConsenthook is provided by the screen that mounts the modal. Implementation suggestion: a React context that exposesrequestAmicusConsent(): 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
Modalfrom react-native with fade animationuseTheme(); typography viafontFamilyfrom themeaccessibilityLabels, and be dismissible via hardware back on Androidamicus_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
amicus_opt_in_accepted_atand proceeds with the sendanytypes; lint cleanOut of scope