Skip to content

iOS parity round 2: Library and debates as evidence tools - #257

Merged
Programcode merged 17 commits into
feat/native-iosfrom
native-ios/parity-r2-library
Oct 28, 2026
Merged

Programcode merged 17 commits into
feat/native-iosfrom
native-ios/parity-r2-library

Conversation

@Programcode

@Programcode Programcode commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Round 2 of the DebateOwl iOS and iPadOS parity plan. The Library and debates now do what the website does, with native menus, swipes and haptics on top. It runs in Expo Go and needs no backend change. There are no migrations, no Edge Function deploys and no config or secret changes.

What each item does

1. Library file import (PDF, DOCX, TXT, MD, CSV, JSON): done.

  • The website's reader moved to packages/core/src/source-file-import.ts. The website and the app run the same code, with the same 8 MB and 120,000-character limits, the 80-page PDF cap and the same messages.
  • Errors and warnings now carry codes. The website still shows its English text; the app shows it in en, hu and es.
  • The app picks the file with expo-document-picker and checks size and type before reading anything, using the file's own size when the picker gives none. An Expo DOM component (components/source-file-reader.tsx) runs pdf.js and mammoth and pulls the file in 768 KB slices, one at a time, into one buffer.
  • A read always ends. A two-minute timeout covers a webview that never loads (it shows the website's "Could not read this file."), a new file cancels the read before it, and closing the sheet stops the reader.
  • The DOM component uses @expo/dom-webview, Expo's default for DOM components, which Expo Go includes. react-native-webview turned out not to be needed.
  • pdf.js and mammoth are imported statically. Expo's export cannot split a DOM component into chunks. The webview only loads while a file is being read.
  • The duplicate check matches a file by name within the debate, as the website does.
  • Parsing stays on the device; import-source is unchanged.

2. Folders and sorting: done.

  • Folder chips and New folder. The chosen folder gets a bar with a switch for the whole folder, Rename (which merges into an existing folder after asking) and Delete (which moves its sources to General).
  • The website's eight sort orders, its search and its folder list now live in packages/core/src/library.ts, and LibraryPage uses them. One website fix came with it: the Library now filters by the trimmed folder name its chips show, so a legacy folder saved with spaces matches its chip.
  • The type filter from the mock moved into the More menu.

3. The Active switch and the grounding banner: done.

  • A switch on every row, swipe right to turn a source on or off, the same in the long-press menu and in the source view, plus the folder switch.
  • A source that is off is dimmed and labelled "Off for AI". The switch sits beside the row's link, not inside it, so tapping it never opens the source.
  • The website's grounding banner sits above the list.
  • Changes run in order through one queue, and the newest tap decides. A failed write rolls a source back only if no newer tap replaced it, and then to the last value the server accepted, with the website's message. Offline, nothing is attempted.
  • A mixed folder's switch shows on, so one tap turns it all off. The folder menu has the website's Activate and Deactivate, for General too. The app's request context already sends only active sources, so the switch really changes what grounds the AI.

4. Edit, Copy, quality, Undo, Clear all: done.

  • The source view shows the website's assessment card ("Used in this case" / usage explanation), the switch, the folder, Copy, Share and Delete.
  • Edit opens title, type, origin and content, and Save writes only the fields that changed.
  • Delete acts at once and offers Undo, which puts the row back with its id and date, as on the website.
  • Clear all asks first.
  • Paste a link and Write a note now pick a folder too.

5. Debates: done.

  • Start new debate and Edit settings share the website's DebateForm, with motion, format, your role and instructions and its limits and counters.
  • Judge is an app addition. The website stores settings.judge and its prompts read it as "Judge expectations", but its form has no field. Both forms now keep settings they don't show: the website's DebateForm used to drop the judge on save, and a new test (src/test/debate/debate-form.test.tsx) covers it.
  • The Debate tab shows format, role, judge and instructions under the motion, with Edit settings.
  • Swipe left on a debate in the switcher to delete it, after the website's warning. The current debate can't be deleted, as on the website. The database already cascades the debate's work.
  • After five idle days the website's "Still preparing this debate?" check opens with Continue, Switch or Start new. Closing it counts as Continue, as Escape does on the website.

6. Context menus and haptics: partial.

  • A long press with a preview (Link.Preview and Link.Menu) works on:
    • Library source rows: Open, Turn on/off, Move to folder, Copy, Share, Delete;
    • the iPad dashboard's recent sources: Copy, Share;
    • saved work on the Debate tab, Prepare, Practice and History, including History's chats: Discuss and Share for cases, attacks, brainstorms and factoids, and Delete for cases and Sparring rounds with the History confirmation.
  • Crossfire and chat rows on the Debate tab and Prepare open the Practice tab again (they have no preview, so they are plain rows).
  • Follow-ups after round 1 (iOS parity round 1: prepare tools match the web #254) merges: Pin in the menus, and menus on Ask's own chat list. Discuss on a source is skipped because the website has none.
  • Haptics come from one helper, lib/haptics.ts:
    • toggles, chips, segmented controls and pickers tap through the shared controls;
    • a finished result taps success (keepOpenResult);
    • a failed request warns (ToolError);
    • Crossfire taps when the turn comes back; Sparring already did.
    • Saves and deletes in the new flows tap too.

Shared-file edits that might overlap with round 1 are a few lines each, in tool-form.tsx, open-results.ts, crossfire.tsx, chip.tsx, segmented-control.tsx, form-rows.tsx, _layout.tsx.

Verification

  • npm run verify passed locally at e6591ca5, after merging feat/native-ios with round 1 (iOS parity round 1: prepare tools match the web #254). This is a local pass, not CI; GitHub Actions is billing-blocked. It ran docs:check with knip, lint, typecheck, 807 unit tests, 30 collaboration tests, 543 edge tests, the build, the service worker test and 47 Playwright smoke tests.
  • The new DebateForm test fails without its fix and passes with it.
  • npm run -w @debatelab/mobile typecheck passes.
  • A new core test, packages/core/src/source-file-import.test.ts, covers the limits, surrogate-safe truncation, the JSON fallback, the PDF page cap and naming the reader that failed.
  • At e6591ca5, npx expo export --platform ios builds, including the 3 MB DOM bundle, and npx expo config --json is byte-identical to before, so the Expo Go config is unchanged.
  • The screenshot harness drives the real flows in the web preview against staging (the seeded Pro account). Library rows and all writes are answered locally, so the account's data was not touched.
    • File import picks the fixture PDF, DOCX and PNG through the page's file chooser and checks the extracted text. That is the same reader code the DOM component runs.
    • The duplicate check fired once on a fixture that shared a file name.
  • The shots were refreshed at e6591ca5: 455 are in ~/.t3/scratch/ios-parity/r2-shots/, for iPhone, iPad Air and iPad Air landscape, light and dark, English and Hungarian, plus Spanish on iPhone. They cover the Debate tab, switcher, new and edit debate, the five-day check, Library, More menu, folder bar, add link, note and file (PDF, DOCX, error), source view and edit, toasts, Prepare, Practice and History. The run had no page errors and no clipped text. I looked at every screen in every combination and fixed what they showed. Linked rows lost their layout on the web, Hungarian text was cut beside an open source on iPad, and the long-form shots didn't reach the end of the sheet. A New folder step proves a new folder stays chosen.

Not verified:

  • Nothing ran on a device or simulator. This VPS has no iOS simulator, and Expo Go needs a phone.
  • The native context menus, previews, swipes, haptics and the DOM webview on iOS are only proven by the iOS export building. The web preview has none of them.
  • Rename, merge, the folder switch, Undo and debate delete ran against locally answered requests only, not real staging writes.
  • Worth trying first on a device: the Import file sheet opening right after the Files picker closes (iOS can refuse a presentation while the picker is still animating), the slice messages across the DOM bridge, and a long-press preview on a row inside its swipeable container.

Merge with round 1

feat/native-ios (with #254) is merged in normally at e6591ca5, with no rebase. The locale files merged cleanly. The conflicts kept both rounds:

  • apps/mobile/package.json: both new dependencies, @expo/dom-webview and @formatjs/intl-listformat.
  • package-lock.json: round 1's lockfile, then npm install added round 2's packages.
  • components/icon-paths.ts: both icon sets (no name clashes).
  • components/chip.tsx: round 1's accessibility label and radio state, with round 2's haptics (radio chips tick like a picker).
  • apps/mobile/AGENTS.md: round 2's Library, file import, toast and haptics, debate and row-menu bullets, plus round 1's newer Ask and Generate bullets. The routes line lists both rounds' sheets.
  • scripts/screenshots.ts: only the header's screen list conflicted; it lists both rounds' groups.

Notes for review

  • The web preview renders the DOM component inline, so its bundle grew by pdf.js and mammoth. That bundle only serves the screenshots.
  • knip ignores @expo/dom-webview, which Expo loads by name.
  • Undo restores a row with its old id, as the website does.
  • A rapid double toggle that fails rolls back to the value before the last tap, which is simpler than the website's per-source settle logic.

🤖 Generated with Claude Code

Programcode and others added 11 commits October 28, 2026 11:48
… app

The website's file reader moves to packages/core with pdf.js and mammoth
passed in, so the iOS app can run the same code in a DOM component. Its
errors and warnings now carry codes the app can translate; the website
still shows the same English text. LibraryPage's folder list, search and
sort orders move to core/library.ts unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
haptics.ts gives every screen the same feel: a light tap for switches and
chips, a selection tick for segmented controls and pickers, success when a
tool's result arrives, a warning when a tool's request fails, and a tap
when the turn comes back in Crossfire (Sparring already had one). The web
preview records them for the screenshot script.

toast.tsx shows the website's short messages above the tab bar, with an
optional action such as Undo, the mint check on success and a VoiceOver
announcement.

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

- Import a file (PDF, DOCX, TXT, Markdown, CSV, JSON): the Files picker
  hands the bytes to the website's reader, which runs with pdf.js and
  mammoth in an Expo DOM component, with its 8 MB and 120,000-character
  limits, its notices and its error messages in all three languages.
- Folders: chips to filter by folder, New folder, and for the chosen
  folder a switch for every source in it, Rename (or merge, after asking)
  and Delete, which moves its sources to General, as on the website.
- The Active switch on every row, by swiping right and in the menu, with
  the website's grounding banner and its rollback when a change fails.
- The website's eight sort orders and its search, from packages/core.
- The source view shows quality and use, the switch and folder, Copy,
  Share and Delete, and Edit saves only the fields changed.
- Delete acts at once with Undo, which puts the source back in its place;
  Clear all asks first. Paste a link and Write a note pick a folder too.
- Press and hold a source for a preview and Open, Turn on or off, Move to
  folder, Copy, Share and Delete.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rows on the Debate tab, Prepare, Practice and History open a preview of
the document on a long press, with Discuss and Share for cases, attacks,
brainstorms and factoids, and Delete for cases and Sparring rounds after
the website's History confirmation. History's chat rows preview the chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…5-day check

- Start new debate and Edit settings share the website's DebateForm:
  motion, format, your role and instructions, with its limits. The app
  adds the judge, which the website's prompts already read as "Judge
  expectations" but its form never shows. Settings the form does not show
  are kept.
- The Debate tab shows the format, role, judge and instructions under the
  motion, as the website's card does, with Edit settings.
- Swipe left on any debate but the current one in the switcher to delete
  it, after the website's warning that its sources and saved work go too.
- After five days without work on the Current Debate, the website's
  "Still preparing this debate?" asks to Continue, Switch or Start new;
  closing it counts as Continue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…DF page cap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d fix what they showed

- The harness answers the Library from made-up sources and folders and
  writes nothing, imports the PDF, DOCX and PNG in scripts/fixtures
  through the real reader, and shoots Edit settings, the long new-debate
  form and the five-day check. Debates read as active, so the check stays
  out of the other shots. A missing browser no longer leaves its server up.
- Rows inside a Link keep their layout: the Link's slot drops a style
  function, so the pressed dim moves to an inner view.
- The web header takes its choices from library-options.ts; importing
  them from library-header resolved to itself on the web.
- A chosen folder hides each row's folder badge; the import error is
  centred; the debate settings card gives its text the full width.
- The guide describes file import, folders, toasts, haptics and debates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ith nothing under it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rows next to an open source show only the folder and its use (the pane
shows the rankings), and source meta lines and badges wrap instead of
being cut off. The harness scrolls a sheet with whatever scrolls it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The Library reset its folder chip to All folders when the folder list
  had not reloaded yet, so New folder and Rename lost the selection. The
  check now runs only when the folder list itself reloads; the harness
  creates a folder through the prompt and checks it stays chosen.
- ToastProvider holds the toast and ToastHost draws it, so the root
  layout gains two lines instead of reindenting its whole Stack.
- A switch taps once: the haptic sits in the controls that lack one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 13:58
@coderabbitai

coderabbitai Bot commented Oct 28, 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

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.

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 insufficient remaining quota.

Programcode and others added 6 commits October 28, 2026 14:15
A row whose work opens the Practice tab has no Link, so it navigates
itself; linked rows still navigate only through their Link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- When the picker gives no size, the file's own size is checked against
  the 8 MB limit before any of it is read into JavaScript.
- The reader pulls 768 KB slices one at a time into one buffer, read
  natively as base64, so at most one slice is in flight.
- A read always ends: a two-minute timeout covers a webview that never
  loads (the website's "Could not read this file."), a new file cancels
  the read before it, and closing the sheet stops the reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow's link

- A failed write rolls a source back only if no newer tap replaced it,
  and then to the last value the server accepted.
- Other screens reload once the switch queue is idle.
- A mixed folder's switch shows on, so one tap turns it all off; the
  folder menu has the website's Activate and Deactivate, for General too.
- The switch sits beside the row's link, so tapping it never opens the
  source; the harness checks that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…acy folder names

DebateForm saved only format and role, which erased settings.judge set in
the iOS app; it now keeps the settings it does not show. The Library
filters by the trimmed folder name its chips show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/parity-r2-library

# Conflicts:
#	apps/mobile/AGENTS.md
#	apps/mobile/package.json
#	apps/mobile/scripts/screenshots.ts
#	apps/mobile/src/components/chip.tsx
#	apps/mobile/src/components/icon-paths.ts
#	package-lock.json
@Programcode
Programcode merged commit 08c11d9 into feat/native-ios Oct 28, 2026
3 of 13 checks passed
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