Repository navigation
iOS parity rounds 3+4 combined - #266
Conversation
* WIP: restructure generated debate cases Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test the new case shape, old saved cases and every case export Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Type the PDF stand-in in the case export test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Accept null for the new optional case fields Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Open the folded Researched sources panel before checking its links Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * R2 backs up the team's arguments instead of adding new ones R2 now follows Adam's own R2 speech (Debate print 1, 2025-03-26): who is speaking, about 2 minutes backing up the team's earlier arguments (reinforce), then the bulk in "If they say…" answers, and a closing line. Length sets R2's answer count (5/6/8) instead of an argument count. The prompt, case labels and exports no longer use framework, burden of proof, weighing, warrant, impact, thesis or counterpoint. Old saved cases keep those sections under plain names. Sparring's school-role guidance and the agentic test context describe the same structure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Sparring's school-role guidance matches the new case structure in all three languages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hold R2 to the length of Adam's own R2 speech Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * R2 answers each of their 2-3 arguments in full The other side usually runs only 2-3 arguments, so R2's "If they say…" holds one full answer per likely argument (about 90-120 words at standard length), built the way Adam writes his own refutations in Debate print 1: a direct reply, a fact with its source, what it means, a tie back to our argument, and a closing question or firm line. He underlines the ones the other side used and builds on them in the round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * R2 answers push back first, as Adam's do, and keep to his length Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * R2 prepares a fuller answer to everything they are likely to say Adam asked for more "If they say…" blocks, each with more content, like his own debates. R2 now writes 6 blocks at standard length (5 brief): their main arguments, the points they use to back them up, and their answers to our case. Each answer pushes back at once, gives at least two specific facts with source and year, says what they mean, asks a question that turns their point around, and ties back to our argument by name. Numbers must come from the attached research and every cited source must be in the sources list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Give a case room to reason so a long R2 is not cut off Claude Haiku reasoned for 6,000-10,000 tokens on an R2 case at low effort, past the 3,000 that a 15,000 max_tokens left it. One reply was cut off and its retry finished at 128 s of the 130 s budget. Generate now keeps at least 10,000 tokens for reasoning (max_tokens 22,000 at low) while its reply cap stays at 12,000, so every cap still fits its retry. Also keep R2's backing-up paragraphs short beside the longer answers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Describe the new case format on the public guides The guides still described the old case: thesis, framework, burden of proof, contentions with warrants and impacts, and a predicted opponent case. Generate now writes an opening, arguments (point, explanation, evidence, why it matters), "If they say… we answer…" replies, a closing line and sources (#244). The copy changes in seo_guides.py, seo_answers.py and the PAGES meta in generate_seo_content.py, and the pages and llms.txt are regenerated from them. The LD, PF, Policy and weighing format guides are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Match the case guide to the shipped R2: no new arguments, five or six If they say blocks --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The staging project was renamed from debate-trainer-staging to DebateOwl Staging, and the sync refused to run since 2026-10-28 11:18 UTC. The target check now keys on the ref (brifeyypjauyusmkptkx), which never changes. The SUPABASE_PROJECT_ID override check, the staging != production check and the explicit --project-ref on every CLI call are unchanged.
The case summary cut backing-up paragraphs and why-we-win reasons to 240 characters and kept only 3 If-they-say answers at 200. They now pass in full (2,000-character safety bound each, up to 8 answers). A case may contribute 12,000 characters, and all saved results 16,000.
Main's #244 changed the website's copies of code that round 1 moved into packages/core. The merge keeps the website's re-exports and ports main's changes into core: the v2 live draft sections, the transform, the regenerate merge (opening, reinforce, whyWeWin, closing, merged sources), the R2 and Conclusor argument check (now hasExpectedArguments, shared by both apps) and the flowchart built from readCase. The app renders cases with readCase the way the website's GenerateResults does: opening, arguments with explanation, evidence and why it matters, backed-up arguments, why we win, "If they say… we answer…", the closing line and sources, plus older cases' sections under their plain names. The PDF, copied text, Discuss message and regenerate list follow it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… core The app's timer, speech-time calculator and motion generator need the same logic as the website's free tools. Each old src/lib path re-exports the core module; the custom format's URL codec stays in the website, and core gains parseCustomFormat for formats kept on a device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Timer now runs the free timer's formats (BP, WSDC, AP, PF, LD, Policy and the school format), each speech's points-of-information window with the knock, prep banks for both sides, overtime and grace, and custom formats saved on the device. The round is kept in AsyncStorage and its signals run from the root layout, so closing the timer leaves a speech running. With an App Group (DEBATELAB_APP_GROUP) the development build also shows the clock as a Live Activity in the Dynamic Island and on the Lock Screen, and as a lock-screen widget, drawn by iOS from SwiftUI timer texts. Expo Go and builds without the group skip all of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full-screen iPad window now shows the app's own sidebar beside the root stack: the Current Debate switcher and New debate on top, then Debate (Dashboard, History), Prepare (Generate, Attack, Brainstorm, Factoids, Ask), Practice (Sparring, Crossfire, Flowchart, Timer), Library (Sources, Paste a link, Write a note, Import a file) and You. Sections open and close from their headers, which VoiceOver reads as headings with their expanded state, and the closed ones are kept on the device. The row for the current screen is highlighted, including a tool's saved documents; a closed section holding it shows a dot. Rows grow with Dynamic Type and wrap to two lines; the sidebar widens with the text size up to a cap. The native tab bar hides while the sidebar shows. Split View, Slide Over, Stage Manager windows and widths under 700 points keep the tab bar. Screens lay out for the room beside the sidebar through ReservedWidth, which sheets reset. The screenshot script waits for either shell, opens tools from the sidebar on iPad, gains a `sidebar` group with its checks, and can relay Edge Function calls with an allowed Origin so it runs on any port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-session notice
The first run opens as one sheet: Ollie's welcome, line by line as on the
website (Skip animation, Reduce Motion shows it settled), then the
website's four tour steps as pages with Back, Next, Skip tour and Get
started, and Start debate on the first step before a debate exists. The
welcome opens once per phone and the tour once per account, under the
website's keys; swiping the sheet away counts as done.
The Debate tab shows the website's trial reminder ("Free trial: 2 days
left.") while a trial runs, with See plan and Hide; the day count moves
to packages/core so both apps share it.
When the server ends a session (a refused refresh, on launch or later),
the sign-in screen says "Your session expired. Sign in again." A sign-out
the student asks for, or Delete account, does not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, text-size caps - Every text field is AppTextInput, with one Done bar above the keyboard on iOS (InputAccessoryView, drawn once in the root layout), so a multi-line answer can always be put away. - VoiceOver reads a finished Ask answer, Crossfire reply and Sparring speech once, as "Answer ready" and its first sentences, never per token; a finished tool result announces its title. - Section titles in documents, Ask's answers, the claim panel, live drafts, empty states and Sparring cards are headings for the rotor. - Text grows with Dynamic Type everywhere; only fixed chrome is capped: floating action-bar buttons and segmented controls at 1.6x, the sidebar at 1.35x, the preview's tab labels at 1.3x. The web preview can draw a text size (font-scale.web.ts) so the screenshots show it. - iPad keyboard shortcuts through one hook: ⌘N new debate, ⌘K switcher, ⌘↩ sends in Ask, Crossfire and Sparring. They run in the web preview; the native app needs a UIKeyCommand module in the development build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sparring and Crossfire get the website's voice rounds: the student speaks each turn through Apple's speech recognition (expo-speech-recognition, restarting when iOS ends a request, as the website's capture does), the words fill the draft, and the turn's speaking time, pace, fillers and long pauses are measured with the shared delivery code. The opponent's replies play from the voice function's signed ElevenLabs segments with expo-audio, falling back to the iOS voice. The first-use privacy notice keeps the website's wording, with Apple in place of the browser. Generate's motion and Ask's question take dictation. Voice needs the development build; in Expo Go the switch stays off with the existing note and the dictation buttons do not show. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the keyboard Screen, DocumentScreen (both layouts) and the Flowchart's scroll views adjust their insets for the keyboard, keep taps on buttons while it is up and put it away when dragged, as the form sheets already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scorecard's menu exports the website's Sparring PDF (set-up, totals, category scores and feedback, key moments, the transcript) through expo-print, with each spoken speech's measured delivery. Word export is left out: the docx package could not be shown to run in Hermes here. A saved Crossfire round now opens from History and the Debate tab: the debrief, the spoken delivery of a voice round and the transcript with its signals, read from the crossfires row the website saves, with an offline copy like every other read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both are the website's free tools, from the same core logic and the website's tools catalog: the calculator counts a speech's words, times it at four paces and shows which slots of the seven formats it fits; the generator filters the practice motion bank by topic, level and type and draws one motion or three. A drawn motion can be copied, shared or opened as a new debate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Share links: a saved case, attack, brainstorm or factoids document has Share link in its menu. It calls the existing `share` Edge Function with the student's session and opens the share sheet; a link made this session for the same unchanged result is reused, since every call makes a new one and counts toward the daily limit. Links open in a read-only viewer (share/[code]) through the public get_shared_item RPC, signed in or out, with Open on the website when a link cannot be shown. Universal links, app side: +native-intent.tsx maps https://debateowl.com/share/<code> and /app/<page> (also www) to app routes and sends any other web address to the Debate tab. The mapping is a hand-written parser at a trust boundary and is tested against other hosts, ports, user info, encoded segments and javascript: URLs. Word export: the website's DOCX builders move to packages/core/src/export/docx.ts unchanged (every part of the generated files is identical before and after, in English and Hungarian), and the app packs them with Packer.toBase64String and shares the .docx. The `docx` package runs without Node or DOM globals and compiles with hermesc. The screenshot script gains a `share` group that makes one real link on staging and opens it in the viewer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt build - A Home Screen widget (small and medium, expo-widgets with @expo/ui): the motion, sources, results and rounds, a Spar link, and Start a debate when there is none. The debate context and the Debate tab publish to it; in Expo Go and on the web that is a no-op. - Quick actions (New debate, Start timer, Ask your coach) and Spotlight indexing are written behind nativeCapabilities() and switch on when expo-quick-actions or expo-core-spotlight is installed. Neither is installed: neither has been tried on SDK 57 on a device yet. - The development build gets the Associated Domains entitlement for debateowl.com and www, and, with DEBATELAB_APP_GROUP, the CurrentDebate widget in the existing expo-widgets entry. Without DEBATELAB_BUILD the config is unchanged (compared with `npx expo config --json`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New notes, source edits (title, folder, text) and Active switches now go through one queue kept on the phone under the account's user id. Online it sends them in order as before. With no connection, or no answer from the server, the change shows at once, stays in the Library's saved copies with a "Waiting to sync" badge, and the offline notice counts what is waiting. The queue replays on launch, when the connection returns, on foreground and every 30 seconds while anything waits, then says "N changes synced". A change the server refuses is dropped and reported once. Several switches or edits of one source are merged; nothing is reordered across sources. A queued note has a local id until it syncs; a lost answer to its insert is found by its id, so it is never saved twice. Another account never replays this account's queue; signing out drops it, as it drops the saved copies. AI, deletes and everything else stay online only. The root layout also starts quick actions and names the share viewer's route after the sign-in group; the Library's panes scroll fields clear of the keyboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Share links and universal links, the first run, the trial and session notices, the accessibility rules, the offline queue, Word export and the development-build widget, quick actions and Spotlight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A no-op until the development build has expo-core-spotlight. Comments no longer point at the removed native-config file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wide iPad's outline rail is a menu too, so the first menu item was the rail's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
supabase-js refreshes the stored session as soon as its module loads and removes it when the server refuses the refresh token, which can happen before the auth provider mounts, so the provider missed it (the web run showed no notice). The session storage now tells a removal nobody asked for from the student's own sign-out (session-expiry.ts), counting a removal only when a session was stored: supabase-js also removes the key on every signed-out launch. Tests cover a refused refresh, a signed-out launch, a sign-out, other keys, and a new sign-in clearing the notice. The web preview's menus cap their text at 1.6x, as iOS menus stop growing; at the largest size the Library's sort menu broke words and hid its lower items. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it leaves the view From the Codex review of 9e2ceb4: - When a note's insert reached the server but its answer did not, changes folded into the note meanwhile were dropped: the replay found the row and called it synced. It now saves the fields that differ first (tested). - An edit queued from an open source wrote the draft into the view's own state, so a refused replay left the unsaved text on screen with no marker. A queued edit now shows only through the queue, and the view reads the row again whenever sources change, so it ends on what the server holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing forever From the Codex review: the brainstorm transform read fields before the schema checked them, so public share data such as a title whose toString is not a function threw inside the viewer's promise and left it loading. readBrainstorm now treats that as not a brainstorm (tested with that payload), and the viewer catches any reader error and any failed read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the Codex review: a cold link straight to a tool (a share link, the widget, a quick action, /app/generate) had no tabs beneath it, so the iPad sidebar found no stack to close and piled tools up, and Back had nowhere to go. The root layout anchors on the tabs and the tabs on the Debate tab; the sidebar also starts the stack again from the tabs if they are ever missing. The sidebar screenshots check that case: a cold /generate, then Ask and Attack from the sidebar, then Back reaches the Debate tab. The screenshot script gains KEEP_GOING=1, which records a failed group and runs the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Words heard after a Sparring Reset no longer start the reset turn's clock: dictated words append to the draft without the start path. - One capture uses iOS's recognizer at a time; starting another cancels the first, so two screens never end or hear each other's sessions. - An opponent clip that never reports its end gives up after its length plus slack, so the device voice reads the rest; clips fetched for a reply that stopped are deleted. - Sending a spoken turn with nothing heard returns quietly. - Generate's dictation keeps words that arrive between renders. - A custom format saved before the stored list loads no longer replaces it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oject The app is com.pistack.debateowl, its widget extension com.pistack.debateowl.ExpoWidgetsTarget, and both carry the App Group group.com.pistack.debateowl, the only value DEBATELAB_APP_GROUP accepts. The config links the EAS project @pi-stack/debateowl that eas init created, and eas.json gains a development-simulator profile: an unsigned development client for the iOS Simulator, with the widget extension. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keeps its open edit From the Codex review of 27ab8f5: - A queued change is written into the saved copies so it shows offline. When the server refused it on replay only open screens re-read, so after a relaunch an offline Library could show the refused change again. The replay now drops the copies the change touched (tested). - The iPad Library keyed its source pane by the note's current id, which changes from local:<uuid> to the row's id when the note syncs, so the pane remounted and lost an edit in progress. It is keyed by the id that was opened; the view follows the new id itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EAS build b8e4ba30 failed in the Xcode phase that bundles the widget runtime: expo-widgets' metro.config.js makes the package its project root, and Expo CLI's resolver then cannot reach Metro's empty module in the workspace root's node_modules. An eas-build-post-install hook gives that bundle an empty module inside the package; it fails loudly if a later expo-widgets changes the config it edits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ource closes its pane From the Codex review of e946150: - Dropping the Library's saved list on a refused replay also hid notes still waiting to sync if the connection went again, because waiting notes show over a list that could be read. A refusal is the server's answer, so the replay now reads the debate's sources and the affected source back into the copies; a refused new note leaves the list copy (tested). - On iPad, deleting a synced note from the list's row action left its pane open on "source missing", because the synced note still counted as a row. The list now closes the pane when it deletes the open source, as the pane's own Delete does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A screen opened straight from a URL now has the Debate tab mounted beneath it, unfocused, and that tab loads only once shown, so its hidden spinners kept every such shot waiting. The expired-session page's refused refresh is expected and no longer reported as a problem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ollie moves as the website's owl does (src/index.css, "The owl's motion"), ported to Reanimated on the UI thread: lids blink over each pose's eyes on the owl's own clock, a slow breath from the feet, the pose's wave, cheer or hop when it arrives or changes pose, and a pop on the website's spring. Avatars blink only where asked, the mark from 28 points, and the loader bobs as the website's owl-bob. - The tactile buttons spring down onto their ledge as a finger lands and bounce back on release; main and primary buttons tap. - SuccessCheck: the mint badge pops on the overshoot curve and the tick draws in (.success-pop, check-draw), in the toast, the sign-in code notice, document notices, Ask's Saved and the source's saved line. - theme/motion.ts holds the website's duration, easing and spring tokens; Skeleton (the website's shimmer) and PressScale come next. - Everything stays still under Reduce Motion. The screenshot script prefers reduced motion so shots show screens at rest; MOTION=1 plays the motion and RECORD_DIR records videos. It also fixes a NodeList spread the scripts' type check refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The iPad sidebar's chevron turns on the pop spring, its rows slide in and out and the sections below glide into place (Reanimated layout animations, replacing LayoutAnimation), and rows press in. A live draft's sections pop in as each is started. The owl avatars that stand for work in progress (Ask thinking, a live draft, Regenerate) blink, as the website's present avatars do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…colours
The live round passed no formatted number, so it read {{formatted}} words. The lock-screen widget now uses the timer screen colours for side B and both sides.
…ote is listed once From the Codex review of 45c8d9f: - A refused change was only cleared from the saved copies by reading the server again, so if that read failed (the connection went again) an offline relaunch showed the refused value. The queue now remembers what the copies showed before each queued change, field by field and kept with the queue, and puts it back on refusal before reading the server; a field a newer queued write still changes keeps that newer value. - A synced note kept its temporary row in the saved list next to its real row, so a failed list read afterwards showed it twice. The saved list now swaps the temporary row for the row. Both have tests that fail without the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Loading lists and documents show skeletons in their own shape (the website's shimmer) instead of spinners: the Debate tab (page, counts, recent work and sources), Library, History, Prepare, Ask's chat list, saved maps, the context Review, the switcher, the You plan card and document loading. Each is the progressbar for VoiceOver. Spinners stay only inside buttons and inline busy controls. - Rows and tiles fade up into place 40 ms apart on first appearance (capped at eight steps), and rows slide into the gap a deleted one leaves. Cards, tiles and rows press in on a spring; Link previews, menus and swipe actions are unchanged. Choosing a list item taps. - The Spar card's owl blinks. - Reanimated layout animations follow the system's Reduce Motion. Also from the Codex review of 45c8d9f: deleting a source closes its iPad pane only if that source is still the one open when the delete lands, so a source opened meanwhile keeps its pane and its draft. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finishing a Sparring round, a Crossfire round or a freshly generated case opens a short celebration over the result: Ollie cheers, confetti bursts in the brand colours (one Reanimated progress value, every piece's flight a worklet), the mint check pops, and the stats that already exist there count up (Sparring: your score, speeches, time used; Crossfire: concessions secured, time used; a case: arguments and sources), with the existing labels. It leaves after 2.4 s, on a tap or on Continue (debate.continue), shows once per finish and never on reopening, and is one button for VoiceOver that announces its content once and stays until dismissed when a screen reader is on. The Sparring scorecard's category bars fill from empty on a spring and its cards pop in staggered; the Crossfire debrief's sections pop in the same way, after the celebration leaves. Under Reduce Motion: no confetti, cheer or count-up, only a short fade. The screenshot script shoots each celebration, taps it away and carries on with the existing shots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…text The celebration's backdrop was 95% opaque, so the case behind it ghosted through its title and stats; it is now solid, as Duolingo's are. The screenshot script expects the Attack prefill to start with the v2 case's OPENING, and the case-edits group taps a new case's celebration away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the Codex review of 958f143: - Reanimated's useReducedMotion reads the setting once at launch, so turning Reduce Motion on in Settings left the owls blinking and celebrations counting up. Every animation now uses one useReduceMotion (theme/motion.ts) that starts from that reading and follows the setting. - The values a refused change goes back to were noted while the copies updated, so a refusal arriving first restored nothing; the queue now waits for pending copy updates before settling (tested). - A saved write did not move the restore point when a newer write to the same field was still queued, so refusing that one went back past the saved value; it now goes back to what the server holds (tested). - The saved-list swap for a synced note (from round 4) superseded the list refresh addSource starts, leaving no list to read offline. It is gone; the duplicate it addressed came from the Library's own merge, which now replaces the note's temporary row with its row. The queue publishes its new state before the copies are put back, so a caller that hears a refusal already sees the write gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ir buttons The iPad sidebar drew every section open until the stored choice was read, so a closed section flashed open and shut on launch (the motion recording caught it). Rows now wait for that read, and rows shown when the sidebar first appears are simply there; a tap still slides them in. The web preview's Library menus are anchored under their buttons rather than a fixed 50 points down, so at the largest text size they no longer cover the button that closes them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fake answered a PATCH without applying it, so the list read that follows every write brought the old Active value back and the switch check failed or passed by timing. It now keeps the change, as the server does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts row offline From the Codex review of db35dab: - Reanimated's layout animations follow the setting it read at launch, so turning Reduce Motion on later still slid list rows, sidebar rows, live draft sections and toasts in. Each now takes the live setting (motionSetting with useReduceMotion). - A note whose insert landed but whose answer was lost synced through the duplicate path, which skips addSource's refresh, so the saved copies kept it under its temporary id and it could not open offline after a restart. That path now gives the copies the row (tested; the test fails without the change). - Turning Reduce Motion on during a celebration's count-up froze the number short of its value; it now shows the final number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #265: the Practice tab was the only empty state without Ollie, and the judging ring showed a blue check where success is always mint with a check mark.
…e; older R2 cases regenerate From the Codex review of 0c22ab7: - Clear all left notes and changes still queued, so they came back and were inserted after the clear. It now lets a running replay finish and drops the debate's queued writes before clearing (tested). - When duplicate recovery also saved folded edits, updateSource dropped the saved Library list and nothing read it back unless the Library was open, so it could not load offline. Recovery now reads the list back from the server (tested). - Regenerating Arguments on an older R2 or Conclusor case took only the new run's empty contentions and dropped its reinforce or whyWeWin, so the section went empty. The shared merge keeps them (tested in core; the website uses the same merge). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…by position From the Codex review of deb28ba: - Clear all dropped queued notes and changes before the server cleared, so a clear that failed lost an unsynced note for good. The queue now holds its replay while the clear runs and drops the debate's writes only once it succeeds; a failed clear keeps them and the queue resumes (tested). - The v2 case document keyed arguments by the model's id, so two arguments with the same id opened and closed together; they are keyed by position again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tten From the Codex review of 48dd067: - A change made offline was reported as queued before the queue holding it was saved, so if storage failed the note was gone after a restart while its draft had closed. It is now confirmed only once saved (with one retry); if it cannot be kept it leaves the queue, the copies go back, and the caller hears it, so the draft stays open (tested). - If the stored queue could not be read at launch, the next save wrote over it and lost earlier unsynced work. Nothing is written over it until a read succeeds; each new write reads it again first, and stored writes go ahead of newer ones once read (tested). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore the queue is read From the Codex review of df0db7d, which found that taking a change back out when its save failed could delete earlier work, leave it stored, or reject a change that then synced: - A change the phone cannot store now stays in the queue and syncs while the app is open. The student is told once ("Not saved on this phone yet", keep the app open) and the save is tried again shortly. Nothing is taken back (tested). - Until the stored queue has been read, every write is refused, online ones too, so none goes ahead of older unsynced work and Clear all cannot leave stored notes behind (tested); ownership is checked again after the read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…early failed session read keeps it From the Codex review of fd3cf3d: - A change sent while the queue could not be stored could not be retired on the phone, so after a relaunch it was sent again over newer work. While storage fails, the queue sends nothing (a replay stops after a save that failed) and refuses new writes with a clear error; it carries on once a save works (tested). - The "Not saved on this phone yet" warning came before the caller's own "saved on this phone" message, which replaced it. It now comes after, and again for each change made while storage keeps failing. - No session before auth had settled (an early read that failed) counted as signed out, which removes stored queues. The queue's owner now changes to nobody only once auth has settled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A queue save whose account changed before it ran wrote nothing but still resolved as a success: its caller heard the change was kept on the phone, and the storage-failure flag was cleared. It now rejects those callers with the account-changed error (the switch removes that account's queue, as before) and leaves the flag alone (tested; the test fails without it). A concurrency test pins that a change whose retirement save fails is sent once, however often replay is asked: the sent change has left the queue before that save, and replay is single-flight, so clearing inFlight there cannot cause a second send. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings native-ios/parity-r4-polish (#265) into native-ios/parity-r3-practice (#262) so both rounds ship and build together. Conflicts kept both sides: r3's voice rounds, dictation, timer runner, Practice free tools and @expo/ui 57.0.23, with r4's motion, keyboard Done bar, iPad ⌘↩, VoiceOver announcements, celebrations, sidebar, share links, offline queue and first run. In a voice round the opponent speaks its turn aloud; otherwise VoiceOver reads it. The iPad sidebar now ends Practice with the speech time calculator and the motion generator, and keeps Crossfire and Timer highlighted on a saved round and the custom format sheet. Spotlight domains use com.pistack.debateowl. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
… bar Round 3's custom words-per-minute field was a plain TextInput, so after the merge it missed round 4's Done bar. Its number pad has no Return key, so the keyboard could not be put away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces #262 (round 3) and #265 (round 4), so both rounds can be reviewed and built together. Those two PRs stay open for reference; close them once this one is accepted.
This branch starts at #262's head (5f3e22f) and merges #265's head (346d6a2) with a normal merge commit. Both rounds are kept in full.
What's in it
com.pistack.debateowlbundle id, the App Groupgroup.com.pistack.debateowl, the EAS project@pi-stack/debateowland thedevelopment-simulatorprofile.How the conflicts were resolved
Twelve files conflicted. Where both sides added something (imports, locale keys, routes in
AGENTS.md, the screenshot groups), both are kept.toolscatalog._layout.tsx. Round 3'sTimerRunnerand round 4'sKeyboardDoneBarare both mounted.@expo/ui. Kept at round 3's~57.0.23.export.*andgenerateResults.*keys that Generate writes cases the way school debaters prepare them #244 (case v2) removed stay removed; nothing in the app still uses them.Follow-ups to the merge
com.pistack.debateowl.*instead ofcom.adam.debatelab.*(spotlight.ts).git grep 'com\.adam'now finds the old id only undersupabase/: App Store product ids in a shipped migration, and a test bundle constant. Those were left alone on purpose, as iOS parity round 3: voice rounds, the free round timer with a Live Activity, and practice tools #262 noted.Expo config of this build
I read the development-simulator config with
expo config --type introspect(DEBATELAB_BUILD=dev,DEBATELAB_APP_GROUP=group.com.pistack.debateowl):com.pistack.debateowl, App Groupgroup.com.pistack.debateowl,applinks:debateowl.comandapplinks:www.debateowl.com;ExpoWidgetsTargetextension (com.pistack.debateowl.ExpoWidgetsTarget, same group) carrying bothDebateTimerWidget(round 3) andCurrentDebate(round 4);NSSupportsLiveActivities, set by the expo-widgets Info.plist mod at prebuild.Quick actions and Spotlight stay off, as in #265: they turn on only once
expo-quick-actionsorexpo-core-spotlightis installed.Verification
Local only, at head 3ad823c.
NODE_OPTIONS=--max-old-space-size=3072 npm run verifyexited 0: docs check, lint, typecheck (web, agentic, core, mobile app and scripts), 877 unit tests in 134 files (the mobile tests included), 30 collaboration tests, 553 Edge tests, the build with prerender and site checks, the service worker offline test, and 47 desktop Chromium@smoketests. An earlier run died partway through the smoke tests when the VPS ran short of memory; the run reported here is a clean full re-run. No CI has run on this PR: every GitHub Actions job ends within 2 to 3 seconds with no logs, which means the account is out of Actions minutes, as on #262 and #265. Treat this as a local pass, not a CI pass. On the review fix (61e9953), I ran eslint on the file and the mobiletsconly.Not run:
verify:full, the database gate, WebKit and staging. This merge changes no migrations.EAS build
One
development-simulatorbuild of head 3ad823c: b96a66c7. Finished in 8.5 minutes, on the first attempt. Download the simulator app (.tar.gz). I unpacked it to check:DebateOwl.appiscom.pistack.debateowl, withNSSupportsLiveActivitiesand the App Group, andPlugIns/ExpoWidgetsTarget.appex(com.pistack.debateowl.ExpoWidgetsTarget) contains both widgets,DebateTimerWidgetandCurrentDebate. The later commit 61e9953 changes JS only, so this binary still matches the head's native side.Review
One Codex review round on 3ad823c found one issue, now fixed in 61e9953. Round 3's custom words-per-minute field in the speech time calculator was a plain
TextInput, so it missed round 4's keyboard Done bar. A number pad has no Return key, so the keyboard could not be put away. That field was the last plainTextInputin the app.Screenshots (web export, not the native app)
Taken with
apps/mobile/scripts/screenshots.ts, signed in on staging, with model calls faked. Groupsfirst-run,results,sidebarandsparring, on iPhone, iPad portrait and iPad landscape, in English light mode: 91 shots. They include Ollie's welcome and the tour, the case and its celebration, the Sparring celebration, and the iPad sidebar with Speech time calculator and Motion generator at the end of Practice.The run exited 1, and not because of the merge. The script's outline-jump step looks for a "Case structure" heading that case v2 (#244) removed, so on a wide iPad it times out after the case is shot. The step is the same in the base, #262 and #265. One shot,
iphone-light-en-sparring-your-turn, is listed inclipped.json: it is the opponent's speech preview, which is cut to a few lines on purpose.EAS Simulator is not enabled for the
pi-stackaccount, so no native screenshots were taken.Needs Adam (carried over from #265, corrected for the new id)
https://debateowl.com/.well-known/apple-app-site-association(and the same on www) asapplication/json, with no redirect, and make surevercel.json's SPA fallback doesn't catch it. Replace TEAMID with the team ID:{ "applinks": { "details": [ { "appIDs": ["TEAMID.com.pistack.debateowl"], "components": [ { "/": "/share/*", "comment": "Shared results" }, { "/": "/app/*", "comment": "App pages" } ] } ] } }group.com.pistack.debateowlfor bothcom.pistack.debateowlandcom.pistack.debateowl.ExpoWidgetsTarget, with Live Activities enabled.expo-quick-actions,expo-core-spotlight@^2, a UIKeyCommand module) and the device tests from both PRs still apply.🤖 Generated with Claude Code