Repository navigation
iOS parity round 4: case v2, iPad sidebar, share links, offline queue, first run, accessibility and Duolingo-style motion - #265
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>
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>
…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>
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>
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>
…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>
…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>
…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>
|
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
|
…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>
|
Replaced by #266, which combined rounds 3 and 4 and is now merged. |
Round 4 of iOS parity (
~/.t3/scratch/ios-parity/PARITY.md, "Round 4" and section 2), plus the grouped iPad sidebar Adam asked for. Everything runs in Expo Go except the parts listed under "Needs a development build", which are written and gated so Expo Go is unaffected.What changed
Case format v2 (#244).
origin/mainis merged in with a normal merge. Main's #244 changed website files that round 1 had moved intopackages/core, so the merge keeps the website's re-exports and ports main's changes into core: the live draft sections, the transform, the regenerate merge, the R2/Conclusor argument check (nowhasExpectedArguments, shared) and the flowchart built fromreadCase. The app renders cases withreadCaseas the website's GenerateResults does: opening (hook, definition, "What we will show"), arguments with explanation, evidence and why it matters, R2's backed-up arguments, Conclusor's "Why we win", "If they say… / We answer", closing line and sources. Older cases show their sections under the website's plain names. PDF, copied text, Discuss and the Regenerate list follow it.iPad sidebar (Adam's addition). A full-screen iPad window shows the app's own sidebar beside the root stack, so it stays while a tool is open:
ReservedWidth.sidebar-items.ts.Share links. Saved cases, attacks, brainstorms and factoids have Share link in their menu:
shareEdge Function and opens the share sheet. A link made this session for the same unchanged result is reused, because every call makes a new link and counts toward the 50-a-day limit.share/[code]is a read-only viewer that works signed in or out, through the publicget_shared_itemRPC.PUBLIC_WEB_APP_ORIGIN, which defaults tohttps://debateowl.com(supabase/functions/_shared/cors.ts). That holds unless staging setsPUBLIC_WEB_APP_ORIGIN, a config change not made here.Universal links, app side.
+native-intent.tsxmapshttps://debateowl.com/share/<code>and/app/<page>(and www) to app routes; any other web address opens the Debate tab. The parser is tested against other hosts, ports, user info, encoded segments andjavascript:URLs. Development builds get the Associated Domains entitlement. The website's apple-app-site-association file is a follow-up for Adam (below).Accessibility.
Home Screen widget, quick actions, Spotlight (development build).
nativeCapabilities(). They switch on only onceexpo-quick-actionsorexpo-core-spotlightis installed. I didn't install either: neither has been tried on SDK 57 on a device.Offline queue.
First run and notices.
packages/core/src/trial.ts.Word export. The website's DOCX builders moved unchanged to
packages/core/src/export/docx.ts. Every part of the generated files (534 files across English and Hungarian, every tool) is identical before and after. The app packs them withPacker.toBase64Stringand shares the .docx.docxcompiles with hermesc and runs with no Node or DOM globals; it hasn't run on a device.Motion, the Duolingo pass (animations only; no new features, every label kept).
MOTION=1plays motion andRECORD_DIRrecords video.Screenshot script.
sidebar,first-runandshare. Thesharegroup makes one real link on staging and opens it in the viewer.FONT_SCALEdraws a Dynamic Type size in the web preview.FUNCTIONS_ORIGINlets the script use any port while relaying Edge Function calls with an allowed Origin, so parallel worktrees don't collide on 18080.Verification
npm run verifypasses locally on346d6a29: docs check, lint (eslint and knip), typecheck (website, agentic, core, mobile), vitest 870 tests, collaboration 30, Edge Function tests 553, build, service-worker test, and Playwright smoke 47 passed. GitHub Actions is billing-blocked, so this is a local pass, not CI.Round 12 (on
4638ab5d) hung and was cancelled.346d6a29:npx expo config --jsonwithoutDEBATELAB_BUILDis byte-identical to before, so the Expo Go config is unchanged. The development build gains Associated Domains, and withDEBATELAB_APP_GROUPthe CurrentDebate widget.sharegroup makes a real share link and opens it in the in-app viewer.local:note.Screenshots
In
~/.t3/scratch/ios-parity/r4-shots/(on the VPS):ax/: 115 shots at the largest accessibility text size (FONT_SCALE 3.1), iPhone and iPad.motion/: 26 screen recordings (WebM) of the animations with motion on, includingipad-sidebar-sections.webm.New states worth opening first:
ipad-light-en-sidebar-expanded.pngandipad-air-landscape-dark-en-sidebar-collapsed.png: the grouped iPad sidebar.iphone-light-en-case.png: a case in the v2 format.iphone-light-en-case-celebration.png,iphone-light-en-sparring-celebration.pngandiphone-light-en-crossfire-celebration.png: the celebrations.iphone-light-en-welcome.png,iphone-light-en-tour-debate.pngandiphone-light-en-tour-ready.png: Ollie's welcome and the tour.iphone-light-en-trial-banner.pngandiphone-light-en-session-expired.png: the two notices.iphone-light-en-shared-case.png: a share link opened in the app.iphone-light-hu-case.png: a case in Hungarian.ax/iphone-light-en-debate.pngandax/iphone-light-en-library.png: the largest text size.Needs Adam
applinks:debateowl.com,applinks:www.debateowl.com);DEBATELAB_APP_GROUP) for the widget, which round 3's Live Activity also needs;com.adam.debatelab, and the AASAappIDsbelow depend on it.https://debateowl.com/.well-known/apple-app-site-association(and 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.adam.debatelab"], "components": [ { "/": "/share/*", "comment": "Shared results" }, { "/": "/app/*", "comment": "App pages" } ] } ] } }PUBLIC_WEB_APP_ORIGINon staging so staging share links open the staging site.expo-quick-actions(its compatibility table stops at SDK 54).expo-core-spotlight@^2, which supports SDK 56–57 but whose plugin patchesAppDelegate.swift.apps/mobile/AGENTS.md.local:note;🤖 Generated with Claude Code