Skip to content

Add mobile play UI and iOS TestFlight scaffold#60

Merged
yourconscience merged 29 commits into
masterfrom
mobile
Jun 10, 2026
Merged

Add mobile play UI and iOS TestFlight scaffold#60
yourconscience merged 29 commits into
masterfrom
mobile

Conversation

@yourconscience

@yourconscience yourconscience commented May 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • polish the Play experience around a mobile-first Space Rangers-style quest viewer
  • add a native iOS WKWebView wrapper that bundles the static site for TestFlight distribution
  • document archive/export commands and add focused iOS scaffold tests

Verification

  • uv run pytest llm_quest_benchmark/tests/test_ios_testflight.py llm_quest_benchmark/tests/test_play_share_social.py llm_quest_benchmark/tests/test_play_quest_index.py -x
  • pnpm run build
  • git diff --check

Note: local TestFlight archive/upload is blocked here because this Mac has Command Line Tools selected and no full /Applications/Xcode.app.

Summary by CodeRabbit

Release Notes

  • New Features

    • iOS app now available with native playback support
    • Media support: in-game images and audio playback with audio toggle control
    • Native iOS sharing for quest results and media exports
    • Current decision insights panel showing cohort distribution data
  • Documentation

    • Added iOS TestFlight distribution guide
  • Style

    • Enhanced play surface UI with refined theming, gradients, and responsive layouts

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b628ff4-7e80-4900-b9b2-5d2702bb9bdf

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
📝 Walkthrough

Walkthrough

This PR adds iOS native app support for LLMQuest by creating a Swift UIKit application that serves the Play web app locally over a custom URL scheme. The changes span Xcode project configuration, Swift app lifecycle and WebView setup, site asset staging infrastructure, Play web app media and sharing enhancements, UI restructuring, build system updates, and comprehensive testing.

Changes

iOS Native Play App with Local Site Serving

Layer / File(s) Summary
Xcode project and app configuration
ios/LLMQuest.xcodeproj/project.pbxproj, ios/LLMQuest.xcodeproj/xcshareddata/xcschemes/LLMQuest.xcscheme, ios/LLMQuest/Info.plist, ios/LLMQuest/PrivacyInfo.xcprivacy, ios/LLMQuest/Assets.xcassets/*
Establishes the iOS Xcode project with PBXProject, build target, source/resource/framework build phases, build configurations for Debug/Release, scheme definition with LLDB debugging, app bundle metadata (display name, identifier, versioning), encryption/privacy flags, scene delegate manifest, launch screen config, supported orientations, app icon asset catalog, and privacy API access declarations.
App delegate and scene lifecycle
ios/LLMQuest/AppDelegate.swift, ios/LLMQuest/SceneDelegate.swift
Implements app entry point (@main AppDelegate) with launch callbacks and scene configuration creation, plus SceneDelegate that creates the UIWindow, sets QuestWebViewController as root, and calls makeKeyAndVisible.
Local site scheme handler and web view controller
ios/LLMQuest/LocalSiteSchemeHandler.swift, ios/LLMQuest/QuestWebViewController.swift
Implements WKURLSchemeHandler to serve bundled lqb:// resources with path traversal protection, MIME type detection, and 404 responses; QuestWebViewController hosts WKWebView configured for inline media, registers the scheme handler, loads lqb://app/play.html, implements JavaScript message handling for file sharing (base64 decode + temp write), and handles navigation delegation to open external links.
Site asset staging pipeline
ios/LLMQuest/StageSiteInputs.xcfilelist, ios/scripts/stage_site.sh
Declares input file list for staged site assets (quest archives, vendor files, Play runtime files), implements shell script that reads the input list, validates source paths, expands ${SRCROOT} references, copies filtered files into the app bundle's site directory, and verifies required assets exist post-copy.
Play app media and sharing enhancements
site/play/app.js, site/play/app.jsx
Adds mediaUrl() helper to construct asset paths; QuestImage, QuestAudio, and MediaStage components to render quest media with error fallback and audio playback control; CurrentDecisionInsight to show cohort distribution for branching decisions; updates EndScreen to display terminal media; extends downloadCanvas and downloadJson to send data via window.webkit.messageHandlers.shareFile when available (iOS/WebKit), falling back to browser download links.
Play app UI restructuring and styling
site/play.html
Replaces CDN-linked Bootstrap/React/Pako with local play/vendor/* assets, expands theme CSS variables for surfaces/muted/accent colors, changes body background to gradient over image, overhauls component styles (topbar, media stage, observation panel, parameter strip, choice buttons, cohort panels, history), and adds responsive media queries for desktop/mobile layout adjustments including sticky AI column and hidden elements on small screens.
Build system and CI integration
package.json, .github/workflows/ci.yml
Adds build:play-static script to copy questplay assets into site/play/questplay/, integrates it into main build step; adds ios-build job on macos-15 that installs dependencies, runs site build, prints Xcode info, and executes xcodebuild build for iOS Simulator with code signing disabled.
TestFlight documentation and comprehensive test suite
docs/IOS_TESTFLIGHT.md, llm_quest_benchmark/tests/test_ios_testflight.py
Provides end-to-end iOS TestFlight distribution guide covering prerequisites, asset staging, CI expectations, archive/export/signing steps, compliance/privacy manifest, ExportOptions generation, and App Store Connect metadata; comprehensive test suite (11 test functions) validates Xcode project structure, Swift sources, app delegate setup, URL scheme handler, metadata/export options, bundled asset presence/CDN absence, staging input file list, quest archives, app icon dimensions/color type, TestFlight documentation completeness, and CI job configuration.
Supporting infrastructure updates
llm_quest_benchmark/tests/core/test_analyzer.py, llm_quest_benchmark/tests/test_play_share_social.py, scripts/import_human_trace.py, site/play/vendor/NOTICE.md, ios/export/ExportOptions.plist.template
Refactors analyzer tests to use module-local isolated_filesystem() context manager; adds test assertion for media surface features (CurrentDecisionInsight, MediaStage, audio toggle, media preload); updates datetime import to use UTC from datetime module for run ID generation; adds Play runtime vendor notice documenting bundled third-party components (Bootstrap 5.3.3, React 18.3.1, React DOM 18.3.1, pako 2.1.0); creates export options template with App Store Connect destination, automatic signing, and symbol upload flags.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yourconscience/llm_quest_benchmark#38: Modifies Play app end-screen sharing flow in site/play/app.js/app.jsx; this PR extends that sharing path to support iOS/WebKit shareFile message handler.
  • yourconscience/llm_quest_benchmark#39: Adds canvas "Share Result" UI and Web Share/PNG fallback in site/play/app.js; this PR integrates iOS WebKit bridge alongside the existing browser download behavior.
  • yourconscience/llm_quest_benchmark#31: Covers cohort display redesign in Play frontend; this PR's CurrentDecisionInsight component and cohort-location state management extends the cohort display work.

Poem

🐰 A native app hops into the Play,
With local schemes and media at bay,
Web views serve quests on iOS bright,
Cohort insights guide the quest-taker's flight,
From staging scripts to TestFlight's way!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main objectives of this changeset: adding mobile-first UI enhancements to the Play experience and scaffolding an iOS TestFlight distribution setup.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mobile

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


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 and usage tips.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces a native iOS wrapper for the LLM Quest experience, utilizing WKWebView and a custom lqb:// scheme to serve local site assets. It includes new documentation for TestFlight builds, Xcode project configurations, and Python tests to ensure asset and build integrity. The web interface has been significantly updated with a responsive 'play shell' layout, including support for local images and audio playback. Review feedback recommends improving the custom scheme handler's error responses with proper HTTP 404 status codes, resolving a potential double-extension bug in media URL generation, and optimizing audio preloading to 'auto' for better mobile responsiveness.

Comment thread ios/LLMQuest/LocalSiteSchemeHandler.swift
Comment thread site/play/app.jsx Outdated
Comment thread site/play/app.jsx Outdated
@yourconscience yourconscience marked this pull request as ready for review June 10, 2026 12:05

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d64e82b62d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ios/LLMQuest/StageSiteInputs.xcfilelist

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
ios/LLMQuest.xcodeproj/project.pbxproj (1)

215-215: ⚡ Quick win

User script sandboxing disabled for site staging access.

ENABLE_USER_SCRIPT_SANDBOXING = NO is set to allow stage_site.sh to access source files outside the build sandbox. This is necessary for the staging script to copy site assets from ../site into the app bundle.

This is a standard configuration for build scripts that need source tree access, and the staging script is read-only.

Also applies to: 276-276

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/LLMQuest.xcodeproj/project.pbxproj` at line 215, The project currently
disables Xcode user script sandboxing (ENABLE_USER_SCRIPT_SANDBOXING = NO) to
let stage_site.sh read ../site; instead re-enable sandboxing by setting
ENABLE_USER_SCRIPT_SANDBOXING = YES and modify the Run Script build phase that
calls stage_site.sh to declare the external resource(s) as explicit input
file(s) (e.g., add ${PROJECT_DIR}/../site or specific files to the script’s
Input Files) or change the script to copy required assets into the allowed
sandboxed paths before packaging; update stage_site.sh references accordingly so
the build phase and ENABLE_USER_SCRIPT_SANDBOXING stay consistent.
ios/LLMQuest/QuestWebViewController.swift (1)

84-89: ⚡ Quick win

Consider surfacing file write errors to the user.

The file write failure is silently ignored (lines 87-89). Users won't receive feedback if sharing fails due to disk space or permissions issues. Consider presenting an alert or status message on catch.

💡 Proposed enhancement
 do {
     try fileData.write(to: fileURL, options: .atomic)
     presentShareSheet(items: [fileURL])
 } catch {
-    // File write failed — fall through silently.
+    let alert = UIAlertController(
+        title: "Share Failed",
+        message: "Unable to save file for sharing: \(error.localizedDescription)",
+        preferredStyle: .alert
+    )
+    alert.addAction(UIAlertAction(title: "OK", style: .default))
+    present(alert, animated: true)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/LLMQuest/QuestWebViewController.swift` around lines 84 - 89, The catch
block in the do/try when writing fileData to fileURL currently swallows errors;
update the catch to surface the failure to the user by presenting an alert or
status message instead of falling through silently. Locate the do { try
fileData.write(to: fileURL, options: .atomic); presentShareSheet(items:
[fileURL]) } catch { ... } block and replace the empty catch with code that uses
a UIAlertController (or your app's existing error UI) to show a user-facing
message that includes error.localizedDescription and a friendly title, and
ensure the alert is presented from QuestWebViewController so users see why
sharing failed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@ios/LLMQuest.xcodeproj/project.pbxproj`:
- Line 215: The project currently disables Xcode user script sandboxing
(ENABLE_USER_SCRIPT_SANDBOXING = NO) to let stage_site.sh read ../site; instead
re-enable sandboxing by setting ENABLE_USER_SCRIPT_SANDBOXING = YES and modify
the Run Script build phase that calls stage_site.sh to declare the external
resource(s) as explicit input file(s) (e.g., add ${PROJECT_DIR}/../site or
specific files to the script’s Input Files) or change the script to copy
required assets into the allowed sandboxed paths before packaging; update
stage_site.sh references accordingly so the build phase and
ENABLE_USER_SCRIPT_SANDBOXING stay consistent.

In `@ios/LLMQuest/QuestWebViewController.swift`:
- Around line 84-89: The catch block in the do/try when writing fileData to
fileURL currently swallows errors; update the catch to surface the failure to
the user by presenting an alert or status message instead of falling through
silently. Locate the do { try fileData.write(to: fileURL, options: .atomic);
presentShareSheet(items: [fileURL]) } catch { ... } block and replace the empty
catch with code that uses a UIAlertController (or your app's existing error UI)
to show a user-facing message that includes error.localizedDescription and a
friendly title, and ensure the alert is presented from QuestWebViewController so
users see why sharing failed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9463cebf-5491-4fa8-ba7e-7af18ba7b282

📥 Commits

Reviewing files that changed from the base of the PR and between 041e407 and d64e82b.

⛔ Files ignored due to path filters (30)
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-1024.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-120.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-152.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-167.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-180.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-20.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-29.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-40.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-58.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-60.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-76.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-80.png is excluded by !**/*.png
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/icon-87.png is excluded by !**/*.png
  • site/play/questplay/background.jpg is excluded by !**/*.jpg
  • site/play/questplay/frame-bottom.png is excluded by !**/*.png
  • site/play/questplay/frame-left-bottom-2.png is excluded by !**/*.png
  • site/play/questplay/frame-left-bottom.png is excluded by !**/*.png
  • site/play/questplay/frame-left-top-2.png is excluded by !**/*.png
  • site/play/questplay/frame-left-top.png is excluded by !**/*.png
  • site/play/questplay/frame-left.png is excluded by !**/*.png
  • site/play/questplay/frame-right-bottom-2.png is excluded by !**/*.png
  • site/play/questplay/frame-right-bottom.png is excluded by !**/*.png
  • site/play/questplay/frame-right-top-2.png is excluded by !**/*.png
  • site/play/questplay/frame-right-top.png is excluded by !**/*.png
  • site/play/questplay/frame-right.png is excluded by !**/*.png
  • site/play/questplay/frame-top.png is excluded by !**/*.png
  • site/play/questplay/frame.png is excluded by !**/*.png
  • site/play/vendor/pako-2.1.0.min.js is excluded by !**/*.min.js
  • site/play/vendor/react-18.3.1.production.min.js is excluded by !**/*.min.js
  • site/play/vendor/react-dom-18.3.1.production.min.js is excluded by !**/*.min.js
📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • docs/IOS_TESTFLIGHT.md
  • ios/LLMQuest.xcodeproj/project.pbxproj
  • ios/LLMQuest.xcodeproj/xcshareddata/xcschemes/LLMQuest.xcscheme
  • ios/LLMQuest/AppDelegate.swift
  • ios/LLMQuest/Assets.xcassets/AppIcon.appiconset/Contents.json
  • ios/LLMQuest/Assets.xcassets/Contents.json
  • ios/LLMQuest/Info.plist
  • ios/LLMQuest/LocalSiteSchemeHandler.swift
  • ios/LLMQuest/PrivacyInfo.xcprivacy
  • ios/LLMQuest/QuestWebViewController.swift
  • ios/LLMQuest/SceneDelegate.swift
  • ios/LLMQuest/StageSiteInputs.xcfilelist
  • ios/export/ExportOptions.plist.template
  • ios/scripts/stage_site.sh
  • llm_quest_benchmark/tests/core/test_analyzer.py
  • llm_quest_benchmark/tests/test_ios_testflight.py
  • llm_quest_benchmark/tests/test_play_share_social.py
  • package.json
  • scripts/import_human_trace.py
  • site/play.html
  • site/play/app.js
  • site/play/app.jsx
  • site/play/vendor/NOTICE.md
  • site/play/vendor/bootstrap-5.3.3.min.css

@yourconscience yourconscience merged commit f91d658 into master Jun 10, 2026
3 checks passed
@yourconscience yourconscience deleted the mobile branch June 10, 2026 12:18
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