|
1 | 1 | # DebateOwl mobile app |
2 | 2 |
|
3 | | -The iOS app DebateOwl (`@debatelab/mobile`; the bundle id is still `com.adam.debatelab`): Expo SDK 57, React Native 0.86, React 19, Expo Router. The plan is [plans/2026-10-08-native-ios-app.md](../../plans/2026-10-08-native-ios-app.md). Read the root [AGENTS.md](../../AGENTS.md) first; this file adds only what is specific to the app. |
| 3 | +The iOS app DebateOwl (`@debatelab/mobile`; bundle id `com.pistack.debateowl`, EAS project `@pi-stack/debateowl`): Expo SDK 57, React Native 0.86, React 19, Expo Router. The plan is [plans/2026-10-08-native-ios-app.md](../../plans/2026-10-08-native-ios-app.md). Read the root [AGENTS.md](../../AGENTS.md) first; this file adds only what is specific to the app. |
4 | 4 |
|
5 | 5 | ## Rules |
6 | 6 |
|
@@ -45,7 +45,7 @@ The iOS app DebateOwl (`@debatelab/mobile`; the bundle id is still `com.adam.deb |
45 | 45 | - One-off mode is session-only and never written, as on the website; only maps saved in it are kept, with no debate, as the website keeps them. The switcher turns it on with or without a Current Debate, and `OneOffNotice` shows it on the Debate tab, Prepare, Ask, Library and the Generate, Attack, Brainstorm, Factoids, Crossfire and Sparring forms (through `WorkingIn`) and the Flowchart. The Debate tab, Prepare and Library still list the Current Debate's work in one-off mode, as the website's home does. Students prepare both sides of a motion, so a debate has no side: the Debate tab's In favor / Against toggle only chooses which side's work it shows, using the website's rule (`'both'` counts for either side, neutral and side-less work for neither). It starts on In favor and is never stored. |
46 | 46 | - Offline reading: `offline-cache.ts` keeps a copy of every read in `debates.ts`, `sources.ts`, `results.ts`, `flowcharts.ts`, `chats.ts`, `sparring.ts` and `history.ts` (the debates list, counts and recent work, Library sources with their text, saved results, maps, chats, judged Sparring rounds and History's lists) in AsyncStorage under the user id. A read goes to the network first and saves its answer; when no answer comes back (supabase-js status 0: no connection, DNS, timeout), or `network-status.ts` (expo-network) says the device is offline, the copy answers and `OfflineNotice` shows at the top of `Screen`, `DocumentScreen` (including its iPad outline rail), the Flowchart and the iPad Library list until each copy shown is read from the server again or the screen showing it closes, moves on or leaves view (a screen passes `useCopyHolder().mark()` with its reads). A server error (an expired session, a policy, a 5xx) is thrown, never answered from a copy; a read made of several queries runs them through `readAll` (or `pickFailure`), so one query's refusal wins over another's missing answer. A slower answer never replaces a newer copy, each group keeps its most recently fetched copies only (`LIMITS`), and the auth provider calls `setCacheOwner` before it adopts a session, so another account's copies are removed before its first read. Nothing is written offline: AI and saving fail with their network errors, and there is no write queue. A read added to those modules goes through `cachedRead`, takes an optional `CopyMark`, and throws `readFailed(error, status)`. Every write that succeeds calls `afterWrite` with the copies it changed (`debateListScopes` names a debate's counts and lists; a delete uses `deletedListScopes`, which drops every debate's when no row came back), which drops them and stops older reads from saving over them, or `updateCopy` when it knows the new value. `debateListScopes` also names History's copy (`one-off` for work outside every debate); History's add-to-debate and filing a judged Sparring round into its debate both drop the item's copies on both sides, and Delete all my data drops every copy (`ALL_COPIES`). |
47 | 47 | - Push: `push.ts` asks for permission and files the Expo push token under the signed-in user with the `register_push_token` RPC; the You tab's Notifications switch turns it on and off, the root layout registers again on launch and when the language changes, and sign-out releases the registration with the account's own access token, after any registration still in flight. Every registration carries the ID of the local record, and `release_push_token` withdraws that ID and deletes only the rows it wrote, so a registration the app gave up on is refused if it reaches the database after the release. Once a release is sent for an ID, the record is marked released and the next registration gets a new ID, so a late release cannot delete it. The local record is cleared only once a release succeeds; otherwise the next change on the phone, from any account, releases it first. The server side is in [supabase/README.md](../../supabase/README.md#push-notifications). `push.web.ts` registers nothing and only flips the switch for the screenshot script. |
48 | | - - No build can get a token until `app.config.ts` has `extra.eas.projectId`, from `eas init` on build day (see [docs/development.md](../../docs/development.md#build-day)). Until then the switch shows as unavailable, on Expo Go too. Expo Go on Android has no remote push since SDK 53, and Expo recommends a development build for remote push on iOS as well. |
| 48 | + - A token needs `app.config.ts`'s `extra.eas.projectId`, which `eas init` set to the `@pi-stack/debateowl` project (see [docs/development.md](../../docs/development.md#build-day)); without it the switch shows as unavailable, on Expo Go too. Expo Go on Android has no remote push since SDK 53, and Expo recommends a development build for remote push on iOS as well. |
49 | 49 | - Prebuild applies the `expo-notifications` config plugin automatically once the package is installed, even though `app.config.ts` does not list it, and the plugin sets the `aps-environment` entitlement. `DEBATELAB_BUILD=dev` removes it again (`withoutPushEntitlement`), because the borrowed Signulous profile has no push, so a development build cannot get a token even with a project ID: turning the switch on there fails. Real push needs a provisioning profile with the Push Notifications capability and an APNs key uploaded to EAS, both from Adam's own Apple Developer account; only then can `withoutPushEntitlement` go. |
50 | 50 | - iPad layouts (iPad mock A, with mock C's docked claim) come from one hook, `useLayoutClass()` in `src/lib/layout.ts`, which also holds every width they use: `phone` on every iPhone and in iPad windows under 700 points, `regular` from 700, `wide` from 1100. Phone layouts must not change, so each screen keeps its phone JSX and branches before it; compare iPhone screenshots before and after. NativeTabs is `sidebarAdaptable` (the iPadOS 18 sidebar; `app-tabs.web.tsx` draws a sidebar or a top tab bar for the preview). The Library opens a source beside its list (`SourceView`, shared with the source sheet), Ask lists its conversations beside the chat (`ask-chat-list.tsx`), the Debate tab is a two-column dashboard, saved documents get an "On this page" rail from 900 points (`DocumentScreen`'s `outline`, `components/outline.tsx`), and the Flowchart docks a card's claim beside the map (`ClaimPanel` in `claim-panel.tsx`, shared with the claim sheet). A docked claim or a source open beside its list is dropped when that map, debate, account or row is no longer the one on screen. All are plain views sized from the window, so they run in Expo Go. |
51 | 51 | - `src/theme/`: the website's DebateOwl design tokens (`src/styles/tokens.css` and the roles in `src/index.css`) in light and dark. `primary` is the blue that passes as text; `primaryFill` with `primaryLedge` is the button face and the ledge it sits on. Success is mint and always comes with a check mark. Fredoka (`weight="display"`) is for titles, the motion and button labels, Nunito for everything else, loaded with expo-font from `@expo-google-fonts`. Buttons are the website's tactile ones (`components/buttons.tsx`), which sink onto their ledge while pressed. Cards and fields have 2pt edges. |
|
0 commit comments