feat(webapp): make the iOS app App Store-ready to replace the legacy app - #780
Conversation
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The new image assets mostly fit the stated goal, but the native iOS project picked up behavior-changing edits. Please restore the app identity/signing settings and finish updating the splash image set; the failing e2e check looks unrelated to these files.
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The two requested changes are still open: the iOS project still changes the bundle/signing settings, and the splash asset set still only updates one referenced PNG. Please address those existing threads before this is ready.
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The splash asset set is fixed now, thanks. The iOS project still commits DEVELOPMENT_TEAM = AQY72VTZTL; in both Debug and Release though, so I’m keeping this as changes requested until those signing-team entries are removed.
Recreate the previous native app's launch screen exactly: the "Apollon" wordmark in the legacy ApollonDarkGray over the TUM logo on a white background, using the legacy TUM logo artwork byte-for-byte. Regenerate the Capacitor post-launch splash to match that same composition so the whole cold-launch sequence reads as one coherent, familiar brand instead of flashing a different mark. Remove the unused icon-variant source PNGs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
…target Remove the hard-coded DEVELOPMENT_TEAM from the App target; automatic signing resolves the team from the provisioning profile in CI, and a committed personal team is a reviewer-blocking leak. Add an AppStoreScreenshots XCUITest target (and shared scheme) that drives the real Capacitor build to capture App Store screenshots. Refresh the Pods lockfile to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Snapfile plus app-store-screenshots/ tooling that seeds fixtures, normalizes captures to App Store-safe sRGB PNGs with no alpha channel, composes an official-bezel framed set, validates dimensions, and builds a review gallery. Includes a Playwright fallback config and this repo's README-hero fixture so the hero screenshot renders the same diagram the README does. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Localized App Store metadata (name, subtitle, keywords, promo, description, release notes, copyright, marketing/support/privacy URLs) and Fastlane lanes for screenshots, assets-only upload, and a full App Store upload whose review submission is opt-in. APP_STORE_READINESS.md documents the manual sign-off steps (migration test, App Privacy answers, review contact). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Rename the manual workflow to ios-release with a destination choice (testflight, app-store-assets, app-store), an opt-in submit-for-review flag, and a raw/framed screenshot selector. Non-TestFlight destinations validate public metadata and links, capture iPhone and iPad screenshots, and publish a review artifact before any upload. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Remove the runtime Poppins request to fonts.googleapis.com and switch the app shell to the already self-hosted Inter family, so no diagram session contacts a third party and offline launch is deterministic. This makes the "no third parties" privacy claim true; the privacy statement is updated to cover the web and mobile apps and their on-device storage. Note: the privacy statement is public legal text and needs TUM data-protection sign-off before release. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Position Apollon web-first across the README, docs metadata, and landing page while adding iPhone/iPad and VS Code as surfaces of one platform. Add a dedicated user Support page (contact, in-place iOS upgrade/migration guidance, privacy pointer) wired into the sidebar and footer so the App Store support URL resolves. Document the App Store screenshot/metadata workflow for contributors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
… output Add appstore:* scripts (screenshots, framed, prepare-frames, preview, metadata:validate) and expose the two most-used at the repo root. Ignore the Fastlane screenshot/frame output and Xcode DerivedData in git, Prettier, and ESLint so generated artifacts never trip the format or lint gate. Reword the iOS branding changeset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 All feedback is addressed now, nice work.
# Conflicts: # README.md # docs/src/pages/index.tsx # docs/user/overview.md # standalone/webapp/ios/App/Podfile # standalone/webapp/ios/App/Podfile.lock
…us bar Root cause: PageShell's scroll wrapper used a fixed pt-5/md:pt-6 resting offset while the sticky chrome band's `top` includes `--safe-area-inset-top`. On a notched iPhone the band is lifted to `inset + edge` by sticky but its flow box only reserved the fixed padding, so the first diagram cards rendered underneath it; on iPad the mismatch read as a gap above the band. Make the resting padding include the same inset the band's `top` uses, so the band rests just below the notch/status bar and content flows under it in order. Fixes home, legal, and 404 in one place. Also stop hiding the iOS status bar: it now stays visible (like any first-class native app) with its style following the theme, drawing over the reserved inset so the clock/battery are legible above the chrome. Guarded by an e2e assertion that the resting padding clears a simulated notch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
…isites Declare export compliance in Info.plist (ITSAppUsesNonExemptEncryption=false — standard HTTPS/TLS only), so no encryption prompt appears on upload. Add reviewer notes that the release lanes upload (no login needed; how to exercise the optional share feature). Turn the readiness doc's hedged privacy section into definitive App Store Connect answers grounded in the shipped behavior (exports use the app cache + iOS share sheet; only Share/Collaborate transmits data): App Privacy nutrition label (one data type, User Content, only when shared, not linked, no tracking), age rating (4+), category (Developer Tools / Education), no permission usage strings, account-deletion exemption, and Sign in with Apple non-applicability — plus a staged submission checklist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
… file Resolve the SwiftLint notices Codacy flagged in our own screenshot scripts: drop trailing commas in collection literals, replace a 3-member tuple with a named struct, rename single-letter loop vars, and extract the mask builder so the aperture function stays under the body-length limit. Exclude fastlane's vendored SnapshotHelper.swift from Codacy (it is copied verbatim upstream and updated by re-copying, not restyled). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Push everything deliver can set into the release lanes so almost nothing needs clicking in App Store Connect: primary/secondary category (Developer Tools / Education), the age-rating questionnaire (rating_config.json → 4+), the App Review contact (from IOS_APP_REVIEW_* CI secrets; notes stay committed), and the submit-time export-compliance and content-rights answers. Add a `privacy` lane wrapping upload_app_privacy_details_to_app_store for the one field the API key cannot set (needs an Apple ID session), driven by a version-controlled app_privacy_details.json. Wire the review-contact secrets into the workflow. The readiness doc now reflects what is automated vs. the one-time privacy setup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The four existing findings are still unresolved: the cookie and theme-storage disclosures remain inaccurate, the Inter visual baselines are still missing, and the readiness file still fails Prettier. The current CI snapshot also has Docusaurus and Codacy failures, so this is not ready to approve yet.
Principal-engineer review pass (round 1): - Fix latent bug: the two screenshot seeders disagreed on the persistence-store version (1 vs 3); both now import one PERSISTENCE_STORE_VERSION so they can't drift and silently seed an empty app. - Harden OTA: release builds set CAPGO_REQUIRE_SIGNING so a missing public key fails the build instead of silently shipping unsigned updates; add --key-v2 to the bundle zip to lock the Encryption V2 pipeline; abort the manifest fetch on a timeout. - De-hand-roll: replace the custom semver comparator with the `semver` package and extract a pure, exported `shouldApplyUpdate` gate — now unit-tested (liveUpdate.test.ts), matching the repo convention that every service is. - Consistency: SHA-pin the ios-live-update actions and read node from .nvmrc (matching every other workflow); use `pnpm exec` not `npx` for @capgo/cli. - Correctness: drop the needless CORS header on the natively-downloaded zip (only the WebView-fetched manifest needs it); fix the nginx comment that wrongly said bundles come from the image build. - Cut duplicated/misleading comments (PageShell inline vs docblock, the e2e guard, the status-bar catch) and use a stable selector in the safe-area test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The four existing findings are still unresolved on this head: the cookie and theme-storage disclosures remain inaccurate, the Inter visual baselines are still missing, and the readiness file remains unformatted. The current CI snapshot also has lint/format, visual-regression, VS Code, and Codacy failures, so this is not ready to approve yet.
Round-2 review found a real logic bug: shouldApplyUpdate short-circuited on the "builtin" bundle and applied ANY manifest that cleared minNativeVersion without comparing to the native version. Since resetWhenUpdate makes "builtin" the state after every App Store update and fresh install, this both re-downloaded the already-shipped bundle and could DOWNGRADE the web layer when a native release landed ahead of the web deploy. Treat builtin's floor as the native version so updates only ever move strictly forward; add the regression test. Also: use AbortController + setTimeout instead of AbortSignal.timeout (WebKit 16.4+) so the check runs on the iOS 15 deployment target instead of silently throwing. And guard the raw screenshot upload the same as framed, so a local upload with an empty directory can't wipe the live App Store screenshots. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The readiness document is formatted now, thanks. The cookie disclosure, obsolete theme-storage key, and missing Inter visual baselines are still unresolved in the existing threads, and Codacy remains red, so this still needs changes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The three existing threads remain unresolved on this head, and the completed Codacy check is still failing, so this still needs changes.
Review found the privacy statement asserted "no cookies", but the server sets a strictly-necessary HttpOnly apollon_owner_<id> cookie (SameSite=Lax, 180-day max-age) when a diagram is created/shared, to authorize the owner to edit or delete it. Disclose it (purpose, retention, attributes, legal basis) instead of denying cookies exist, in both the privacy statement and the readiness doc, and correct the theme-preference key from the deleted legacy `theme-storage` to the active `apollon-theme`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Split the connected-transparent-region search out of transparentScreenAperture into floodFillTransparentRegion so both stay under the function-length limit (clears the remaining Codacy warning). Behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
f7a6038 to
cb7c4c2
Compare
# Conflicts: # pnpm-lock.yaml # standalone/webapp/tests/visual/svg-export.visual.spec.ts-snapshots/svg-export-template-bridge.png
The iOS project shipped only the AppStoreScreenshots shared scheme, so a clean checkout (cap run, and the CI TestFlight build which uses `scheme: App`) failed with "workspace does not contain a scheme named App". Add the standard shared App.xcscheme. Also commit the Podfile/lock entries `cap sync` generates for the @capacitor/app and @capgo/capacitor-updater plugins this PR adds, so the native build includes them without relying on a sync step to mutate tracked files. Verified by building and launching the app on the iPhone 17 Pro Max simulator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The App Store automation has a release-blocking ordering bug: the archive is built after the screenshot lane replaces the native web bundle and drops OTA signature enforcement. The live-update activation and production-publish paths also do not match their stated lifecycle guarantees; details are inline.
The lockfile regenerated during the main merge resolved the extension's @tumaet/apollon dependency as an injected `file:library(...)` copy instead of `link:../library`. Injected copies are materialized at install time, so they carry no `dist/` — `build:lib` populated the real workspace while the extension resolved to the copy, and vscode-extension-checks failed every run with "Cannot find module '@tumaet/apollon' or its corresponding type declarations". Restore the `link:` resolution main uses (and that `docs` still uses), so the extension typechecks against the freshly built declarations. Verified with a clean `node_modules` + `pnpm install --frozen-lockfile`, then the exact CI steps: `pnpm run build:lib` and `pnpm --filter apollon-extension run typecheck` — both green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 The three existing blocking threads remain unresolved on this head: the App Store archive still follows the screenshot resync, live updates can still activate after backgrounding, and OTA packaging still is not tied to the deployed image source. The new commit only updates the lockfile, so this still needs changes.
…ed commit Three review findings, all reproduced against the code before fixing: - [critical] An `app-store` run captured screenshots before archiving, and the screenshots lane injects fixture localStorage into dist/index.html and re-runs `cap sync` without the signing env. The archived IPA would therefore contain screenshot seed data and no updater public key, accepting UNSIGNED live updates. The build lane now rebuilds the web bundle and re-syncs immediately before archiving, with the signing env passed to that step, so the archive can never inherit another lane's artifacts. - `next()` activates a staged bundle on the next BACKGROUND, not only on a kill (per the plugin's own docs), so switching away could reload the editor mid-task despite the documented cold-start-only contract. Require an explicit `kill` delay condition. - The OTA bundle was built from the dispatched ref while the deployed webapp is chosen by the `image-tag` input, so a rollback or SHA-tag redeploy could publish a bundle from a different commit than the image that went live. Pass the image tag through and check out that exact commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW
Claudia-Anthropica
left a comment
There was a problem hiding this comment.
@tamang29 All requested changes are addressed now, nice work.
Summary
This makes the redesigned Capacitor-based iPhone/iPad app ready to replace the legacy native Apollon app on the same App Store listing (
de.tum.cit.ase.apollon), and does the supporting work that a clean, professional store submission needs. Apollon stays web-first — the web app is the primary product; iPhone/iPad, VS Code, and the embeddable library are surfaces of one platform.It started as an icon/splash refresh (#780) and grew into the whole replacement. It's a large PR, but a cohesive one; the pieces are:
fastlane snapshot/XCUITest (not mockups), normalised to App-Store-safe sRGB with no alpha, optionally framed in official Apple product bezels, generated in CI. Screenshots seed this repo's README-hero diagram and two existing fixtures without changing any release template.fastlane/metadata, and lanes that set almost everything code-first (category, age rating, export compliance, review contact) so little is clicked in App Store Connect.Removing the committed personal signing team (a reviewer-blocker) and adding the screenshot UI-test target are the only changes to the Xcode project.
Release note
Rebuild the iPhone and iPad app on the redesigned editor with the familiar Apollon branding, and stop the web app from loading its font from Google — it is now self-hosted.
Implementation notes
Screenshots are generated, not committed. The PNGs are git-ignored; CI regenerates them and publishes a review gallery. So this PR carries the tooling, fixtures, and metadata — not binaries. The device-framing compositor is hand-written (CoreGraphics) on purpose:
fastlane frameitships Facebook's frames, not Apple's official product bezels, and needs per-device offset files; detecting the screen aperture from the bezel's transparent region lets any official bezel drop in.Fonts / privacy. Dropping the Google Fonts request is the one change that touches the web app's look (Poppins → the already-bundled Inter). It's here because "no third parties" in the privacy policy has to be true in the binary, not just on paper. The privacy statement rewrite is public legal text and should get TUM data-protection sign-off before release — it's flagged in
fastlane/APP_STORE_READINESS.md.Over-the-air updates — architecture. The mobile bundle is the same
distthe web deploy already ships, so there's one cadence: the production deploy publishes the matching signed bundle ("deploy == publish"). It's served by the webapp's own nginx behind the existing Traefik — the collaboration server is untouched. Security lives in the signature, not the host: bundles are signed with Capgo Encryption V2 (private key in CI, public key in the app), so a compromised host still can't ship malicious JS;notifyAppReady()auto-rollback and aminNativeVersiongate protect against bad or incompatible bundles. It's App-Store-compliant (interpreted JS/CSS/HTML only; Apple 2.5.2).Transparency — what was provisioned outside this diff. To make OTA go-live turnkey I set repo/Production secrets and variables (
CAPGO_PRIVATE_KEY,CAPGO_PUBLIC_KEY,CAPGO_MIN_NATIVE_VERSION,ENABLE_LIVE_UPDATE=true) and created the/opt/apollon/app/live-updatesdirectory on the prod VM, reusing the existing deploy identity + gateway (no new key or user). WithENABLE_LIVE_UPDATEon, the next production deploy publishes the first bundle; signing is enforced from the next app build onward.This was reviewed hard before you see it. Two principal-engineer review passes (grounded in the Capgo/Apple/GitHub docs) ran over the branch and found real bugs, now fixed: a screenshot-seed version drift, an OTA path that could silently ship unsigned, and an update gate that could downgrade the web layer when a native release landed ahead of the web deploy. The custom semver comparator was replaced with the
semverpackage behind a pure, unit-testedshouldApplyUpdate.Deliberately not done. OTA is not enabled to submit for review automatically (submission stays opt-in). Templates are unchanged — screenshot fixtures are seeded separately. The manifest host is intentionally the fixed TUM domain rather than an env indirection: it's TUM's app, and a fork rebuilds with its own host.
Known limitations. The OTA path is covered by unit tests and validated end-to-end against the CLI, but not on physical iOS hardware — that's the one thing worth a device before the first real update. The App Store
support_urlonly resolves once these docs are merged and deployed (the release lane validates URL liveness and stops otherwise). The App Privacy questionnaire answers are documented but must be entered/confirmed by the release owner.Steps for testing
pnpm install && pnpm lint && pnpm format:check && pnpm build && pnpm test— green.cd standalone/webapp && node app-store-screenshots/validate-metadata.mjs.cd standalone/webapp && pnpm exec vitest run src/services/liveUpdate.test.ts.tests/e2e/home-page.spec.ts("keeps the sub-page chrome outside iPhone safe areas").cd standalone/webapp && bundle exec fastlane screenshots, then reviewfastlane/screenshots-review.html.Screenshots / screencasts
UI changes are the iOS safe-area/status-bar fixes and the web font. The App Store screenshots are produced by the pipeline (git-ignored) and published as the CI review-gallery artifact; before/after captures of the safe-area fix (iPhone home no longer underlapping the nav bar; iPad editor no longer gapping) were shared during review and can be attached here on request.
Checklist
@tumaet/webapppatch)feat) matches the kind of changeliveUpdate.test.ts; safe-area guard inhome-page.spec.ts)pnpm lint && pnpm format:check && pnpm build && pnpm testlocally — green (0 lint errors, 1384 tests)APP_STORE_READINESS.md)🤖 Generated with Claude Code
https://claude.ai/code/session_01FBU1opcNEbkxQuaptdHbAW