Skip to content

Add the Debate tab, debate switcher and You tab to the app - #125

Merged
Programcode merged 12 commits into
feat/native-iosfrom
native-ios/03b-debate-you
Oct 9, 2026
Merged

Programcode merged 12 commits into
feat/native-iosfrom
native-ios/03b-debate-you

Conversation

@Programcode

@Programcode Programcode commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Second piece of milestone 1 from plans/2026-10-08-native-ios-app.md: the Debate tab built to mock 01, the debate switcher sheet built to mock 02, a new-debate sheet, and the You tab. Targets feat/native-ios.

What the app now does:

  • Debate tab. The "Current debate" pill opens the switcher; the motion is the title; the In favor / Against toggle under it switches between the pro and con preparation, showing work saved for that side or for both, as the website does (nothing about a side is stored); the counts of sources, results and sparring rounds come from count queries on the same tables the website uses; the gradient "Spar against the AI" card and the four Prepare tiles open the existing placeholders until their PRs land; "Pick up where you left off" lists the debate's most recent saved results. With no current debate it shows the empty state and a Start debate action.
  • Switcher sheet. Search, the list with the current debate checked, one-off mode, and New debate. It is a native form sheet on iOS and a full page in the web preview.
  • New debate. Motion and format; creating it makes it current, as on the website. A debate has no side, because students prepare both.
  • You. Plan and today's usage (the website's tier rules), interface language, AI output language, theme, the signed-in account with a staging badge in development, sign out, and account deletion with the website's typed confirmation through the account Edge Function.

One account everywhere: the app reads and writes debates, current_debates and user_settings as the website's stores do (its first settings save also fills the website's default model settings, the Pro ones for Pro students, so the website keeps the phone's choices), so switching a debate or changing the language on the phone shows up on the website. One-off mode lives in memory, as on the website.

Not in this PR: "Download my data" (needs file sharing, later), deleting a debate from the switcher (the mock has no control for it), and offline reading (PR 7).

Checks, run locally on Node 22.23.2 at the final head because GitHub Actions starts no jobs on this account (billing):

  • npm run verify exit 0 (276 Vitest tests, 30 collaboration, 333 Deno, build, service-worker, 30 Chromium smoke). Mobile typecheck, eslint apps/mobile and knip exit 0. Each of the five commits typechecks and lints on its own after a fresh npm ci.
  • Every native module is in the App Store build of Expo Go; the only new dependency is date-fns, pure JavaScript and already in the root tree.
  • 96 screenshots of the web export signed in to staging (iPhone and iPad, light and dark, English and Hungarian, both prep views). Side-by-side with the mocks is attached below.
  • Real actions on staging with throwaway 3b-test- motions: create two debates, switch between them, toggle one-off mode, change language, output language and theme and read user_settings back, reload in Hungarian with dark mode applied, then restore. Every test debate is deleted and the seed account is back to its recorded state. A deletion request with the wrong confirmation returned 400 and deleted nothing.

Not proven here: the native form sheet, keyboard avoidance and the iOS switch on a real iPhone, the Hungarian Flowchart tile shrinking to fit (the web preview can't shrink text, so it truncates there), and a full account deletion (the seed account is never deleted). Those are milestone checks on Adam's phone.

🤖 Generated with Claude Code

Programcode and others added 5 commits October 9, 2026 07:18
The app reads and writes debates, current_debates and user_settings the
way the website's stores do, so both share one Current Debate and one set
of settings. One-off mode stays session-only, as on the website. The
debate side is kept on the device, because debates has no side column.
The interface language and theme follow the account's saved choice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Debate tab follows mock 01: the Current debate pill, the motion as
the title, the side control, counts of sources, results and rounds, the
Spar against the AI card, the Prepare tiles and recent work. Tools and
results open the existing placeholders until their screens land.

The pill opens the switcher from mock 02 as a native form sheet: search,
the debates with their source counts, one-off mode and New debate. New
debate takes a motion, side and format and makes the debate current.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
You shows the plan and today's AI usage, computed as the website's
useSubscription does; the interface language, AI output language and
theme, written to user_settings so the website shows the same; and the
account, with deletion through the account Edge Function behind the
website's typed confirmation. Download my data is left for later, since
it needs file sharing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The screenshot script now captures the Debate tab, the switcher, the
new-debate form, the empty state and You. It answers the app's
user_settings reads with each run's language and the System theme, so
the account's saved choices do not override the locale under test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hungarian "Folyamatábra" did not fit its Prepare tile, so the tiles are
tighter and the label shrinks to fit on iOS. The one-off icon tile used
the segment colour, which matches the card in dark mode; it now uses the
line colour, as in mock 02.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 9, 2026 07:37
@Programcode

Copy link
Copy Markdown
Owner Author

Mocks against the app's web export at the final head: mock, iPhone light English, iPhone dark Hungarian.

Debate tab: mock 01 vs app

Switcher sheet: mock 02 vs app

You tab, light, English (address masked)

New debate, dark, Hungarian

@vercel

vercel Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
debate-navigator Ready Ready Preview Oct 9, 2026 11:35am UTC
debatelab-staging Ready Ready Preview Oct 9, 2026 11:35am UTC

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository YAML (base), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Free
  • Run ID: c02eb2a6-9fbb-442a-9eab-16fbaad900c6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.


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

@Programcode

Copy link
Copy Markdown
Owner Author

Product decision from Adam on the In favor / Against toggle: debaters prepare both sides and usually don't know which one they will speak, so the app will not store or sync a "your side". The toggle becomes a view of the debate's pro and con preparation (results filtered by side, with the website's rule for results tagged 'both'), the Spar card drops "you speak Pro", and Sparring will ask for a side when a round starts. The device-stored side in this PR is being removed in a follow-up commit on this branch before merge.

Programcode and others added 5 commits October 9, 2026 07:54
Students prepare both sides of a motion and rarely know theirs in advance,
so the Debate tab no longer stores a side per debate. Its In favor / Against
toggle now only chooses which side's results and recent work it shows,
using the website's rule: 'both' counts for either side, neutral and
side-less work for neither. Sources and rounds stay whole-debate counts.
The toggle starts on In favor for each debate and is never stored.

The new-debate sheet loses its side field, and the Spar card shows the
format and that the side is picked when the round starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d them

Creating or switching a debate now reads the signed-in account when the
student acts and, if the account changed while the write ran, leaves the
screen alone instead of showing the result to the next account.
Overlapping refreshes carry a sequence number, so only the latest one
lands. Every debate read, count and recent-work query, and the activity
update on a switch, also filter on the user as defence in depth behind
row level security.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The website reads an empty user_settings.feature_configs as a row that was
never saved and replaces its language and theme with the browser's. The
app now writes the website's default feature configs with its first save
while the row has none, filtered on the empty object so configs saved
meanwhile stay. The default builder moves to @debatelab/core so both
clients write the same value, and a test feeds such a row through the
website's hydration.

Each save bumps a per-key version. A load keeps the local value of any
key saved after it started, and a failed save rolls back only its own
key, and only while it is still that key's latest save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The delete sheet printed the server's English message under the
translated heading. It now shows the heading and a translated hint that
the account is still there, and logs the raw message to the console.
The website maps no deletion errors, so there are no cases to translate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the app saved a student's first setting it filled the empty
feature_configs with the non-Pro default model. The website would have
used the Pro default for a student with Pro access, and it never
upgrades configs once they are saved, so a Pro student who first saved
in the app kept the weaker model on the website.

The Pro access check now lives in @debatelab/core (ai/pro-access.ts) and
both the website's settings store and the app use it. The app reads the
tier, grace period and subscriptions on that first save and picks
DEFAULT_PRO_MODEL when the check passes. If the check can't be read, it
writes only the changed column and leaves configs empty, so the next
save tries again.

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

Copy link
Copy Markdown
Owner Author

Codex review r1 (gpt-6.1-sol, head 4391026): 7 findings. I checked each against the code and all 7 were real. All are fixed as of 23aab53.

  1. High. Creating or switching a debate set state without checking the owner after the await. Fixed in 2bca008: the owner is captured before the await, and nothing is applied if the account changed in the meantime.
  2. Medium. App saves left feature_configs empty, so the website's hydrateFromServer treated the row as never saved and dropped the app's language and theme. Fixed in 9439d10: the first save fills in the website's default configs, guarded by containedBy('feature_configs', {}). 23aab53 extends this so Pro students get DEFAULT_PRO_MODEL; the shared check is in packages/core/src/ai/pro-access.ts and is used by the website too, with unchanged behaviour. If that check fails, only the changed column is written. A new test runs the website's hydration on an app-saved row, Pro and non-Pro.
  3. Medium. Settings hydration could overwrite a newer save. Fixed in 9439d10 with a version per setting.
  4. Medium. A failed save restored the whole previous snapshot. Fixed in 9439d10: only that setting rolls back, and only if no newer save of it happened.
  5. Medium. Overlapping refreshes had no request identity. Fixed in 2bca008 with a sequence number.
  6. Medium. The account deletion error showed the raw server message. Fixed in f090107: the app now shows a translated hint and logs the raw message.
  7. Low. Reads and the activity update had no user_id filter. Added in 2bca008 as defence in depth; RLS remains the boundary.

There was also a product change: the In favor / Against toggle is now a pro/con prep view, and nothing is stored (df9c998).

Local checks at 23aab53 (GitHub Actions is billing-blocked, so none of this ran in CI): lint, typecheck, npm test (279 passed), docs:check and build all passed. The f090107 export produced 96 screenshots against staging. npm run verify passed at df9c998.

Not proven: the race fixes rest on reading the code, and the app-side settings write has not run against real PostgREST because staging writes were not allowed.

Codex r2 at 23aab53 is running.

🤖 Generated with Claude Code

Saves now go through one queue per provider, so the last choice made is
the last write even when an earlier save's Pro-access lookup is slower.
A save queued before an account switch is dropped and never touches the
new account's state.

When feature_configs may be empty and Pro access can't be read, the save
now writes nothing and fails, so the You tab rolls the setting back and
shows its save error. Writing the column alone let the website discard
the choice on its next hydrate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed save rolled back to the value on screen when it started, which
could be another unconfirmed choice. The app now keeps, per setting, the
value last loaded or saved successfully and rolls back to that.

Saves, loads and debate writes were guarded by user ID, so work started
before signing out and back in as the same account still landed. They now
check an owner generation that is bumped on every owner change.

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

Copy link
Copy Markdown
Owner Author

Codex review rounds 2 to 4 (gpt-6.1-sol)

  • r2 at 23aab53.
    • Medium: two quick saves of the same setting could reach the database in the wrong order. Fixed in cc3a134: saves now run through one queue.
    • Medium: when the Pro check failed, only the column was written, so feature_configs stayed empty. Fixed in cc3a134: the save now fails and rolls back, and the You tab shows its translated save error.
    • Low: the PR description still said the side was stored. Fixed in the description.
  • r3 at cc3a134.
    • Medium: rollback could restore an earlier choice that had never been saved. Fixed in 89bb34c: rollback now goes to the last value the server confirmed.
    • Medium: queued saves were guarded by user id, which doesn't change when you go from A to B and back to A. Fixed in 89bb34c with an owner generation counter. The same gap in debate create and switch was fixed too.
  • r4 at 89bb34c. No findings. Codex's probes for the r3 scenarios passed on this head and failed on the one before.

Accepted, not fixed:

  • A queued write for the old account that slips past the guard is still filtered to the old user_id, and it runs under the new session's JWT, so RLS keeps it off every row.
  • Right after sign-in, before the account's saved settings load, a save that fails rolls back to the previous values. This was already the case before this PR.

Local checks at 89bb34c (GitHub Actions starts no jobs on this account because of billing): lint, typecheck, npm test (281 passed), docs:check, build, web export, and 96 screenshots against staging all pass. npm run verify passed on #127's code rebased onto cc3a134, which includes this PR's website change.

Merging into feat/native-ios.

🤖 Generated with Claude Code

@Programcode
Programcode merged commit 514032b into feat/native-ios Oct 9, 2026
8 of 18 checks passed
@Programcode
Programcode deleted the native-ios/03b-debate-you branch October 9, 2026 11:40

This branch was successfully deployed

2 active deployments
Preview – debate-navigator — 89bb34c6 Deployed Oct 9, 2026 by vercel[bot]
Preview – debatelab-staging — 89bb34c6 Deployed Oct 9, 2026 by vercel[bot]
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.

2 participants