Store readiness: in-app account deletion, privacy policy, terms, listing copy

Closes the remaining store-compliance gaps before App Store / Play
submission:

- Account deletion (App Store guideline 5.1.1): DELETE /me removes the
  user row (usage cascades). The app exposes it through one quiet
  'Account' caption link under the chat, visible only when signed in,
  driving two native dialogs (Sign out / Delete account with a
  destructive confirm) — no new views, no menus, minimalism intact.
  Deletion signs out and resets the app; copy notes that store
  subscriptions are cancelled in App Store / Play settings.
- docs/store/privacy-policy.md: the complete data inventory (matching
  the actual schema), transient OpenAI processing with no training, no
  profiling or ads, GDPR legal bases and rights, in-app erasure.
- docs/store/terms-of-service.md: not-therapy positioning with crisis
  guidance, AI-generated-content caveat, 18+ eligibility,
  auto-renewal/cancellation terms, liability, Swedish governing law.
- docs/store/listing.md: App Store and Play copy written to the
  honest-claims rule (subtitle 'Think Beyond Thought', keywords,
  descriptions), plus App Privacy and Data safety questionnaire
  mappings.
- LAUNCH.md updated: host the policy/terms, set store URLs, use the
  prepared listing copy.
- Tests: 30 passing (adds DELETE /me coverage).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118DaxZR36RpnY524vRqx3z
This commit is contained in:
Claude
2026-08-03 22:37:25 +00:00
parent bf5c4e5f00
commit e5b81f46b0
11 changed files with 314 additions and 8 deletions
+5
View File
@@ -89,6 +89,11 @@ export async function fetchMe(): Promise<Me> {
return request<Me>('/me', {}, await authHeader());
}
/** Permanently deletes the account and its usage data (App Store 5.1.1). */
export async function deleteAccount(): Promise<void> {
await request<{ deleted: boolean }>('/me', { method: 'DELETE' }, await authHeader());
}
export async function verifyPurchase(body: {
platform: 'ios' | 'android';
productId: string;
+52 -2
View File
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import {
Alert,
FlatList,
KeyboardAvoidingView,
Platform,
@@ -9,7 +10,8 @@ import {
TextInput,
View,
} from 'react-native';
import { fetchSuggestions, sendChat, type ChatMessage } from '../api/client';
import { deleteAccount, fetchSuggestions, sendChat, type ChatMessage } from '../api/client';
import { signOut } from '../auth/session';
import { colors, spacing, type } from '../theme';
interface Props {
@@ -22,6 +24,9 @@ interface Props {
/** Set right after unlock: fetch the promised analysis automatically. */
deliverAnalysis: boolean;
onAnalysisDelivered: () => void;
/** Shows the quiet Account affordance (sign out / delete account). */
signedIn: boolean;
onSignedOut: () => void;
}
const SUGGESTIONS_SHOWN = 4;
@@ -39,6 +44,8 @@ export function ChatScreen({
onContinueWithPremium,
deliverAnalysis,
onAnalysisDelivered,
signedIn,
onSignedOut,
}: Props) {
const [suggestions, setSuggestions] = useState<string[]>([]);
const [draft, setDraft] = useState('');
@@ -92,6 +99,42 @@ export function ChatScreen({
const showSuggestions = messages.length === 0 && suggestions.length > 0;
// No menus, no settings view: account management lives in two native
// dialogs behind one quiet link (in-app deletion per App Store 5.1.1).
function openAccount() {
Alert.alert('Account', undefined, [
{
text: 'Sign out',
onPress: () => {
signOut().then(onSignedOut);
},
},
{
text: 'Delete account',
style: 'destructive',
onPress: () =>
Alert.alert(
'Delete account?',
'This permanently deletes your account and usage data. Conversations are never stored. An active subscription is cancelled in your App Store or Google Play settings.',
[
{
text: 'Delete',
style: 'destructive',
onPress: () => {
deleteAccount()
.then(() => signOut())
.then(onSignedOut)
.catch(() => setError('Something went wrong. Please try again.'));
},
},
{ text: 'Cancel', style: 'cancel' },
],
),
},
{ text: 'Cancel', style: 'cancel' },
]);
}
return (
<KeyboardAvoidingView
style={styles.container}
@@ -150,6 +193,11 @@ export function ChatScreen({
</Pressable>
</View>
)}
{signedIn ? (
<Pressable style={styles.accountLink} onPress={openAccount}>
<Text style={styles.accountText}>Account</Text>
</Pressable>
) : null}
</KeyboardAvoidingView>
);
}
@@ -191,8 +239,10 @@ const styles = StyleSheet.create({
alignItems: 'flex-end',
gap: spacing.s,
paddingHorizontal: spacing.l,
paddingBottom: spacing.xl,
paddingBottom: spacing.m,
},
accountLink: { alignItems: 'center', paddingBottom: spacing.l },
accountText: { ...type.caption },
input: {
flex: 1,
...type.body,