Skip to content

iOS round 7: ship-ready release build, parity, speed, animation and a sideloadable IPA - #277

Draft
Programcode wants to merge 17 commits into
feat/native-iosfrom
native-ios/r7-ship-ready
Draft

Programcode wants to merge 17 commits into
feat/native-iosfrom
native-ios/r7-ship-ready

Conversation

@Programcode

@Programcode Programcode commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Round 7 of the iOS app. It fills the parity gaps left after rounds 1–6, makes the app faster and livelier, adds a release build, and produces an unsigned production IPA that Adam can re-sign with a free Apple ID and install.

Draft into feat/native-ios. Don't merge yet.

The IPA

DebateOwl-sideload.ipa (18 MB) is on the prerelease ios-r7-sideload-1. The release notes have the Sideloadly and AltStore steps and the list of what a free Apple ID can't sign.

  • Built by EAS build 2f4e3d5a from 58825f3. EAS keeps its own copy until 2026-11-12.
  • Release configuration, production backend, iOS 16.4+, bundle com.pistack.debateowl 1.0.0 (1).
  • Contains the app, ExpoWidgetsTarget.appex and DebateOwlShare.appex, each signed ad hoc with only the App Group entitlement.
  • SHA-256 68f4c87d274012834414c05325a2f87ed786fa48a3fa1d5937a1697851b98dc3.

What changed

Release build and sideloading.

  • DEBATELAB_BUILD=release in app.config.ts is the development build without the development client, so it runs its built-in JavaScript.
  • Without DEBATELAB_APPLE_ACCOUNT its only entitlement is the App Group. Push, Sign in with Apple and Associated Domains are dropped, because a free Apple ID can't sign them. The Expo Go config is byte-identical; I compared expo config --json before and after.
  • eas.json gains two profiles:
    • release-simulator;
    • sideload, a custom EAS build (apps/mobile/.eas/build/sideload-ios.yml). It runs prebuild and the native patches, pod install, and an unsigned xcodebuild archive. Each bundle is then signed ad hoc with its prebuild entitlements, and the result is packed as DebateOwl-sideload.ipa. A normal EAS device build refuses to run without credentials, which is why this one is custom.
  • push.ts reads extra.remotePush. A build without the push entitlement shows the switch as "This version of the app cannot receive notifications yet" instead of failing when iOS refuses a token.
  • scripts/patch-app-group-lookup.mjs makes expo-widgets and expo-share-intent read AltStore's ALTAppGroups first. AltStore renames the App Group to <group>.<TEAMID>; with the patch, Live Activities, widgets and the share extension still work in an AltStore install. Other builds are unchanged.

Parity. The full checklist is below. New this round:

  • Sparring Word export;
  • deleting a saved Crossfire round;
  • Generate's "Any role", plus the debate's role filled in with its motion;
  • a Sparring model picker;
  • the usage reset time;
  • the sign-in method line in You;
  • a "manage on debateowl.com" link for Stripe plans;
  • custom timer formats shared as the website's ?custom= link, and opened from one;
  • a language menu on the sign-in screen;
  • the printable flow sheet as a PDF.

Performance.

  • Saved copies first. Tabs paint their saved copy at once and refresh in the background (cachedRead's onCopy). A server refusal still surfaces, and another account's copy never shows.
  • Fewer re-renders:
    • Streamed tool drafts live in a LiveValue, so only the draft re-renders, not the whole screen, 20 times a second.
    • Sparring's clock ticks in its own component.
    • React Compiler skips went from 34 files to 2: Sparring's judge request and the one-shot celebration.
  • Startup:
    • The splash hides on layout.
    • Settings load in parallel with the session.
    • The unused language catalogs load only when needed.
    • date-fns imports by path.
  • Long lists: the debate switcher, saved maps and long sources render virtualized.

Animation. Brand-consistent, with no sounds, lessons, leagues or XP.

  • Spring presses on chips, rows and segmented controls.
  • Celebrations for Attack, Brainstorm and Factoids results and at the end of a timer round.
  • Ollie keeps time beside the timer's ring, which now sweeps smoothly.
  • Ollie reacts to errors and to each step of a run.
  • Ollie waves once for a new debate.
  • Cards enter staggered on first view.
  • Everything respects Reduce Motion.

Fixed after merging the three strands (found by the screenshot run and the Codex reviews):

  • The sign-in language menu bypassed the lazy catalog loader, so it stayed in English.
  • A stale debates refresh could hide a debate the student had just switched to.
  • The five-day "Still preparing?" check could open from a stale saved copy. It now waits for the server's list.
  • A row's entrance animation covered the Library's web menu.
  • An offline copy, or a copy from an earlier refresh, could still count as the server's list (review round 2).

Parity checklist (website → iOS app)

Legend: ✅ in the app and wired to the same tables and functions as the website · 🆕 added in this round · ➖ not applicable on iOS (reason given).

Sign-in

  • ✅ Email code, demo code, session-expired notice (sign-in.tsx, auth.tsx)
  • ✅ Google (social-sign-in.ts; native builds only, needs debatelab://auth/callback in the project's redirect list)
  • ✅ Sign in with Apple (an iOS extra; builds signed by DebateOwl's own Apple account)
  • 🆕 Language menu on the sign-in screen (the website's LanguageSwitcher)

Debate and Current Debate

  • ✅ Switcher, new, edit settings (+ judge), delete, one-off mode, five-day check, motion gate, context Review and pins, unsorted work, announcements, trial banner, limit and model-restricted sheets

Prepare

  • 🆕 Generate: "Any role" and the debate's role filled in with its motion
  • ✅ Generate: motion, side, length, audience, tone, style note, model, dictation, live draft, stream recovery, regenerate a part, undo, open in Crossfire, build a map, discuss, PDF, Word, share link
  • ✅ Attack, Brainstorm, Factoids (every website option, bookmarks to the Factoids folder, PDF, Word, share link)

Practice

  • ✅ Ask: General/Clarify, Citations, Web search, Pros/Cons, Argument map, attachments, Stop, saved chats, save to Library
  • ✅ Crossfire: both modes, styles, seed questions, signals, debrief, voice, saved rounds (read only; the website can't open them at all)
  • 🆕 Sparring model picker (the website's ModelSelector feature="sparring")
  • ✅ Sparring: format, side, order, cost, clock, judge, scorecard, voice, resume, PDF
  • 🆕 Sparring Word export beside the PDF
  • ✅ Motion generator, speech time calculator
  • 🆕 Printable flow sheet (/tools/debate-flow-sheet) as a PDF, from Practice and the iPad sidebar

Library

  • ✅ Link with preview, note (queued offline), file import (txt, md, csv, json, pdf, docx), folders (create, rename, merge, delete, toggle), search, 8 sorts, type filter, Clear all, source view and edit, source quality
  • ➖ Scan pages: not a website feature (the audit found no OCR in src/)

History

  • ✅ Per-debate and unsorted lists, reopen chats, open rounds, delete cases and Sparring rounds, add to a debate
  • 🆕 Delete a saved Crossfire round, from History and from the round

Flowchart

  • ✅ Starter map, cards, links, build from a case, save, save as, saved maps, delete
  • ➖ PNG export is a PDF of the map tree (React Flow's canvas is not on iOS); drag canvas / minimap replaced by role columns

Timer and free tools

  • ✅ Seven formats, POIs, prep banks, overtime, grace, cues, custom formats, Live Activity with Pause/Next, time-up alert, lock-screen widget
  • 🆕 Share a custom format as the website's ?custom= link, and open such a link in the app (codec moved to packages/core)

You (Settings)

  • ✅ UI and output language, theme, plan card with billing states, Download my data, Delete all my data, Delete account, Sign out, Notifications
  • 🆕 "N uses left, resets at HH:MM" (the website's DailyUsage)
  • 🆕 "Signed in with Google / email / Apple"
  • 🆕 A website (Stripe) plan links to debateowl.com to manage it; no prices or purchase links
  • ➖ Admin page and environment switcher (admin/staging only); promo codes and Stripe checkout (App Review: digital purchases go through StoreKit)

Sharing and export

  • ✅ PDF and Word for case, attack, brainstorm, factoids; copy; discuss; open share links; create share links (an iOS extra)
  • ➖ Google Docs export: needs a Google Drive OAuth scope and token refresh in the app; Word and PDF go to Google Drive through the share sheet instead

Global

  • ✅ en/es/hu, light/dark/system, Ollie's welcome and tour, offline reading and write queue, daily usage line, voice, model picker on every AI tool
  • ➖ Workspaces: hidden on the website too (VITE_COLLABORATION_URL unset in production)

Screenshots

The key screens are PNG assets on the release, with all 405 in DebateOwl-r7-screenshots.zip.

Builds and minutes

  • EAS builds:
    • acffe3ed: the pipeline test of the sideload profile, at 2361b759. It produced an installable-shaped IPA.
    • 2f4e3d5a: the final IPA, at 58825f3.
    • Builds this month: 5 of the free plan's 15.
  • EAS Simulator: 0 minutes used. eas simulator:availability returns available: false for the pi-stack account (waitlist at expo.dev/services/simulators), and T3's MacBook device host failed to start. The screenshots are the web export instead, rendered at iPhone (390×844 @3x) and iPad landscape (1366×1024 @2x) sizes against staging with the Pro demo account and faked AI answers. They show layout, copy and colour, not native tab bars, sheets or gestures.

Verification

  • npm run verify locally at the final code:
    • docs, lint and typecheck pass;
    • 953 unit tests pass;
    • collaboration tests pass;
    • 555 Edge Function tests pass;
    • the build and service worker check pass;
    • 47 desktop smoke e2e tests pass.
    • One test failed on the first run: "Ideas" is the same word in Spanish, so it's now on the allowlist.
    • GitHub Actions is billing-blocked, so this is a local pass, not a CI pass.
  • The web-export screenshot script, signed in to staging: 405 shots, with no clipped text and no page errors in the final run of each group.
  • Codex review:
    • r1 at af57794 found 2 issues, fixed in b14b3323 and db4554b5.
    • r2 at f1a5677 found 2 more in the round-1 fix, both fixed in 58825f3 with a test.
    • r3, on that last fix, stalled without a result and was cancelled. 58825f3 has not been reviewed by a second model.
  • Not verified: the IPA on a real iPhone, native gestures and sheets, VoiceOver, Live Activities and widgets on device, and real-device frame rates.

Adam's to-do (not done here)

  • Add debatelab://auth/callback to production Supabase's Auth › Redirect URLs, or Google sign-in in the IPA opens the website and the student has to close it.
  • To get simulator screenshots next time, enable EAS Simulator for pi-stack or reconnect the MacBook device host in T3.

🤖 Generated with Claude Code

Programcode and others added 17 commits October 29, 2026 19:50
DEBATELAB_BUILD=release is the development build without the development
client. Without DebateOwl's own Apple account it keeps only the App Group,
so a free Apple ID can re-sign it, and the push switch shows as unavailable
instead of failing. The sideload profile archives an unsigned Release build
on EAS and packs it as an IPA; release-simulator builds the same app for
the Simulator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AltStore re-signs with a free Apple ID by registering the App Group as
<group>.<TEAMID> and writing that name into ALTAppGroups. expo-widgets and
expo-share-intent now read that key first, so Live Activities, widgets and
the share extension work in an AltStore install; other builds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add Sparring Word export, saved Crossfire deletion, Generate role parity, model selection, account and plan details, shared timer links, sign-in language selection, and printable flow sheets.

Co-Authored-By: Codex <noreply@openai.com>
Celebrate fresh tool results and completed timer rounds, welcome new debates
once, and show save confirmation after its sheet closes. Add shared spring
presses and session entrances while retaining Reduce Motion and accessibility
behavior. Keep timer feedback separate from clock logic.

Co-Authored-By: Codex <noreply@openai.com>
# Conflicts:
#	apps/mobile/src/app/timer.tsx
Paint account-owned cached tab data while refreshing, defer unused catalogs, and isolate streamed drafts and deadline clocks. Virtualize growing sheet lists and source text, skip unchanged source copy writes, and remove inexpensive React Compiler blockers. Record focused verification and the protected remaining skips.

Co-Authored-By: Codex <noreply@openai.com>
# Conflicts:
#	apps/mobile/AGENTS.md
#	apps/mobile/src/app/(tabs)/(debate)/index.tsx
#	apps/mobile/src/app/ask.tsx
#	apps/mobile/src/app/context-review.tsx
#	apps/mobile/src/app/debates.tsx
#	apps/mobile/src/app/flowchart/index.tsx
#	apps/mobile/src/app/history.tsx
#	apps/mobile/src/components/live-run.tsx
#	apps/mobile/src/components/source-view.tsx
#	apps/mobile/src/lib/history.ts
Their new requests ran inside try blocks the React Compiler skips; they go
through runRequest like the rest of the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sign-in menu called i18next directly, so a language whose catalog was
not loaded yet fell back to English; it now goes through changeAppLanguage.
VoiceOver hears what the button chooses, and the screenshot script presses
Send code by name now that the menu comes first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A row's entrance animation drew over the Library's web menu, hiding its
last items. The screenshot script now shoots and dismisses the
celebrations Attack, Brainstorm and Factoids results open with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refresh that failed after a newer switch or create still set the Debate
tab to its error state, and any failed refresh replaced a list the server
had already given. The failure now obeys the same guards as the answer and
only shows the error when this refresh had painted the saved copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With saved copies painted first, the check could open from a copy whose
last activity was days older than the student's latest work on the
website. The debates provider now says whether its list is fresh, and the
check waits for that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dismiss a judged voice round's celebration before its scorecard, and check
the iPad outline against a part the case still has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An offline or unreachable read answered with the saved copy looked like a
server answer, so the five-day question could still open from it; and a
failed refresh kept a copy another refresh had painted. cachedReadFrom says
where its answer came from, and the debates provider tracks whether the
list on screen is a copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 29, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant