// Home screen — the reactive end-to-end loop, on a phone. // // The SAME hooks as the web frontend: `useSubscription('notes.list')` streams // the server-side query; `useMutation('notes.create')` writes it. When the // mutation lands, the server pushes a delta and this list re-renders — no // refetch, no polling. // // Both come from `../lib/api`, which binds them ONCE to the api's generated // procedure map — so the tag autocompletes, a typo is a compile error, and the // row type is inferred rather than declared here. import { useState } from 'react' import { Link } from 'expo-router' import { FlatList, Pressable, Text, TextInput, View } from 'react-native' import { useMutation, useSubscription } from '../lib/api' // The dev AuthMiddleware resolves the tenant from the `x-tenant` header, // defaulting to 'acme'. `notes.create` guards that the submitted tenantId // matches the caller's (a typed TenantMismatch otherwise). const TENANT = 'acme' export default function Home() { // No row `interface` and no ``: the shape comes from `notes.list`'s own // output schema, including the client's `optimistic` marker. const notes = useSubscription('notes.list') const createNote = useMutation('notes.create') const [title, setTitle] = useState('') return ( { if (title.trim().length === 0) return // Auto-optimistic: the list updates instantly, reconciles on ack. // `tenantId` and `body` are REQUIRED by `notes.create`'s input // schema — the old hand-written `useMutation<{ title: string }>` // annotation hid that, so this call decoded-failed at runtime. createNote.mutate({ tenantId: TENANT, title: title.trim(), body: '' }) setTitle('') }} style={{ backgroundColor: '#2563eb', borderRadius: 8, paddingHorizontal: 16, justifyContent: 'center' }} > Add n.id} ListEmptyComponent={No notes yet — add one above.} renderItem={({ item }) => ( {item.title} )} /> Settings & sync status ) }