Skip to content

Native iOS app: Timer and typed Crossfire - #132

Merged
Programcode merged 11 commits into
feat/native-iosfrom
native-ios/05a-timer-crossfire
Oct 14, 2026
Merged

Programcode merged 11 commits into
feat/native-iosfrom
native-ios/05a-timer-crossfire

Conversation

@Programcode

Copy link
Copy Markdown
Owner

This PR adds the Practice tab's Timer and typed Crossfire. Targets feat/native-ios.

Timer works like the website's TimerPage:

  • It has the four formats and their speeches.
  • It beeps once with a minute left and twice at time up.
  • The clock is built from start timestamps, not counted ticks. It shows the right time after the app comes back from the background, and if a background gap skips past both thresholds, only the time-up signal plays.
  • The full-screen ring follows the Sparring mock, with the gradient only on Start/Pause.
  • Sound comes from expo-audio, with WAVs generated from the website's 880 Hz synth (the script and a README are in apps/mobile/assets/sounds). expo-haptics taps with each beep, and expo-keep-awake keeps the screen on while a speech runs. Muting keeps the tap.
  • The lock-screen Live Activity is left for the dev build, with a TODO(live-activity) marker where it goes.
  • timer.test.ts covers a 90 s gap with no ticks, pausing, and both thresholds.

Crossfire, typed mode:

  • The website's turn, signal and debrief requests go through the app's shared helper. They use the same request bodies, model settings, debate context and the 75 s and 5 s deadlines. postFunction gained an optional timeout to support those deadlines.
  • The reducer and schemas come from @debatelab/core/crossfire.
  • The live round is full screen and follows the system theme.
  • An unfinished round is kept per account in session memory.
  • A finished round saves to crossfires the way the website's recordResult does. One-off rounds aren't saved, as on the website.
  • The debrief renders in Native iOS app: Prepare tab, Generate, case document, Attack and Export #130's document style.
  • Voice mode needs the dev build, so the website's voice toggle shows disabled with a "Coming with the app build" note.

Matches the website rather than the brief:

  • There are no prep timers and no alarm: the website defines an alarm but never plays it.
  • Saved crossfires don't reopen from lists. The website can't reopen them either.

Checks, run locally on Node 22 because GitHub Actions starts no jobs on this account (billing):

  • npm run verify exits 0: 293 Vitest, 333 Deno and 30 Chromium smoke tests.
  • 272 screenshots signed in to staging, covering iPhone and iPad, light and dark, English and Hungarian. The Timer run checks that it fired exactly one beep, then two.
  • The new packages are expo-audio, expo-haptics and expo-keep-awake, all in Expo Go's bundled modules.

Live on the staging seed Pro account, one typed round:

  • Next turn 3.3 s, signal 1.0 s, debrief 6.9 s.
  • Saved with side neutral, then deleted (0 → 1 → 0).

Not proven here: sound, haptics, keep-awake and the native modal on a real iPhone. Those are for Adam's phone.

🤖 Generated with Claude Code

Programcode and others added 4 commits October 9, 2026 15:19
The website's formats, speeches and signals in a full-screen ring like the
live round in mock 05. The clock keeps start timestamps instead of counting
ticks, so it shows the right time after the app comes back from the
background, and a gap that crosses both thresholds sounds only time up.

The beeps are bundled WAVs generated by scripts/render-timer-sounds.ts from
the website's synth, played with expo-audio, with an expo-haptics tap on each
signal. expo-keep-awake keeps the screen on while a speech runs. The web
preview records the signals instead, and the screenshot run checks them on
Playwright's clock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Crossfire as the website's CrossfirePage in its typed mode: the same setup,
the shared reducer and schemas from @debatelab/core, and the crossfire and
crossfireSignal requests with the website's bodies, Ask model settings,
debate context, 75 s and 5 s deadlines and response checks. The round runs
full screen with its clock read from the end timestamp, keeps for the
session if the student leaves it (a request cut off by leaving becomes one
to retry), and is saved to crossfires in its debate when the debrief
arrives. The debrief reads as a document in #130's style.

Voice rounds need the development build, so the website's voice switch
shows disabled with a note. The screenshot run fakes every turn, signal and
debrief in the run's language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Hungarian, "Munkamenet befejezése" squeezed the round's resolution to
two words; the button now wraps its label. On iPad the speech chips and
the signals card sat left of the centred ring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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 14, 2026 12:01pm UTC
debatelab-staging Ready Ready Preview Oct 14, 2026 12:01pm UTC

Copilot AI balanced review requested due to automatic review settings October 9, 2026 15:48

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: 8deabc72-527c-4fec-ac52-a943f7d39c12

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

Web export at the final head (fixed fake outputs), and a real round on staging:

Timer running, iPhone dark hu

Crossfire exchange, iPhone light en

Live Crossfire on staging

Live Crossfire debrief

Programcode and others added 3 commits October 9, 2026 16:40
Brings in #129. Conflicts kept both sides: the screenshot script shoots the
Timer, Crossfire and then the one-off tools, and the app guide keeps the Timer
and Crossfire paragraphs beside #129's one-off text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The website shows crossfire.charactersRemaining under the turn field. It was
left out because plural strings did not render on Hermes; #129's
Intl.PluralRules polyfill fixes that, so the composer now shows it beside its
label, and the screenshot script checks it reads as a number. The app guide
notes that the Crossfire setup shows one-off mode through WorkingIn, as the
website's CurrentDebateStrip does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pausing as the Timer turned from 1:01 to 1:00 moved its last reading past the
threshold without a decision, so that speech never beeped. Time left only
falls while the clock runs, so every fall is now checked (signalFor in
timer.ts); only a fresh clock after a reset or another speech is skipped.

A Crossfire turn or debrief read its model settings and Library context
before its 75 s deadline and outside its abort, so a stalled read kept the
turn pending past 75 s and leaving could not cancel it. withTurnDeadline now
starts the deadline first and passes one signal through the reads and the
request.

A finished round was saved from the state when the debrief was asked for,
so a coaching signal that arrived meanwhile was shown but not saved. The round
is now saved from the state it reaches, as CrossfirePage does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Programcode and others added 2 commits October 9, 2026 17:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared request helper now waits for the session read only until the
deadline or the caller's abort, so a stalled token refresh can't hold a
Crossfire turn or any other request past its timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A request whose caller had already left still read the session, and if
that read failed nobody handled the error. Check the abort first, and
let untilAborted absorb a read it no longer waits for.

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

Copy link
Copy Markdown
Owner Author

Codex review r3 at 595cbdc: 1 finding (Medium): a request cancelled before it started still read the session, and a failing read leaked an unhandled rejection. Fixed in 8f05fe3 (abort checked before the read; untilAborted absorbs a read it no longer waits for; regression test fails without the fix). Merge resolution and locale key sets checked clean. Local: npm run verify green (301 vitest, 30 smoke), mobile typecheck, eslint, knip pass. CI is billing-blocked; these are local results. Codex r4 next.

…/05a-timer-crossfire

# Conflicts:
#	apps/mobile/AGENTS.md
#	apps/mobile/scripts/screenshots.ts
#	apps/mobile/src/app/_layout.tsx
@Programcode

Copy link
Copy Markdown
Owner Author

Codex review r4 at 8f05fe3 (gpt-6.1-sol, high): no findings; probes confirmed r3's unhandled rejection is gone. Then merged feat/native-ios (with #133 Flowchart) as f130458: conflicts in _layout.tsx, screenshots.ts and apps/mobile/AGENTS.md, each resolved by keeping both sides. Local at f130458: npm run verify green (307 vitest, 30 smoke), mobile typecheck, eslint, knip pass. Codex r5 on the merge resolution next.

@Programcode

Copy link
Copy Markdown
Owner Author

Codex review r5 at f130458 (merge of feat with #133): no findings; route registrations, Prepare/Practice navigation, screenshot sections and docs checked; en/es/hu keep identical sets of 174 mobile.* keys. Merged-head screenshot run (iphone-light-en, staging): 61 shots incl. Practice, Timer, Crossfire and Flowchart. Merging into feat/native-ios. Not provable here: timer sound, haptics, keep-awake (Adam's phone).

@Programcode
Programcode merged commit 3803494 into feat/native-ios Oct 14, 2026
8 of 16 checks passed
@Programcode
Programcode deleted the native-ios/05a-timer-crossfire branch October 14, 2026 12:24

This branch was successfully deployed

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