Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
62ae643
feat: update Apollon's icon
tamang29 Jun 22, 2026
4165cd0
Merge branch 'main' into feat/ios-icon-update
tamang29 Jun 22, 2026
5987b90
feat: update Apollon iOS icon, splashscreen
tamang29 Jun 22, 2026
a453a1c
fix: use white background
tamang29 Jun 22, 2026
44c55de
Merge remote-tracking branch 'origin/main' into feat/ios-icon-update
FelixTJDietrich Jun 27, 2026
69335d6
fix(webapp): use supplied iOS icon artwork
tamang29 Jul 6, 2026
c48eb02
Merge branch 'main' into feat/ios-icon-update
tamang29 Jul 6, 2026
0ecc2ac
feat(webapp): reuse legacy Apollon launch screen and align iOS splash
FelixTJDietrich Jul 24, 2026
27fdfa6
feat(webapp): drop committed signing team and add screenshot UI-test …
FelixTJDietrich Jul 24, 2026
9fba03c
feat(webapp): add automated App Store screenshot pipeline
FelixTJDietrich Jul 24, 2026
979bd9c
feat(webapp): add App Store metadata and Fastlane release lanes
FelixTJDietrich Jul 24, 2026
9b13697
ci: generalize iOS workflow to TestFlight and App Store destinations
FelixTJDietrich Jul 24, 2026
15ffbbb
fix(webapp): drop Google Fonts and align privacy statement
FelixTJDietrich Jul 24, 2026
67517ee
docs: lead with web, add support page, and document mobile presence
FelixTJDietrich Jul 24, 2026
0eb5fe3
chore(webapp): wire App Store scripts and ignore generated screenshot…
FelixTJDietrich Jul 24, 2026
f8cb492
Merge remote-tracking branch 'origin/main' into feat/ios-icon-update
FelixTJDietrich Jul 24, 2026
43bb513
fix(webapp): clear the iOS notch/status bar at rest and show the stat…
FelixTJDietrich Jul 24, 2026
a423007
docs(webapp): add App Privacy answers and complete submission prerequ…
FelixTJDietrich Jul 24, 2026
f549395
style(webapp): clean up screenshot Swift tooling and exclude vendored…
FelixTJDietrich Jul 24, 2026
bac360d
feat(webapp): automate App Store Connect fields code-first via fastlane
FelixTJDietrich Jul 24, 2026
4f18513
feat(webapp): add self-hosted over-the-air live updates for the mobil…
FelixTJDietrich Jul 24, 2026
ce65c50
chore(webapp): pin node-pty build off for the @capgo/cli dev dependency
FelixTJDietrich Jul 24, 2026
a25320e
fix(webapp): correct OTA signing flow and reuse the existing deploy t…
FelixTJDietrich Jul 24, 2026
bd08953
fix(ci): pin appleboy/scp-action to the correct v1.0.0 commit SHA
FelixTJDietrich Jul 24, 2026
df5b21f
refactor(webapp): harden OTA, fix seeder drift, de-bloat per review
FelixTJDietrich Jul 24, 2026
f5dbf5c
fix(webapp): gate OTA against the native version; iOS 15 fetch timeout
FelixTJDietrich Jul 24, 2026
592178b
docs(webapp): apply prettier formatting to the App Store readiness doc
FelixTJDietrich Jul 24, 2026
03a62d7
docs(webapp): broaden the changeset to cover the font/privacy change
FelixTJDietrich Jul 24, 2026
260c716
fix(webapp): disclose the diagram-owner cookie and the current theme key
FelixTJDietrich Jul 24, 2026
717d015
chore(visual): refresh Playwright baselines
github-actions[bot] Jul 24, 2026
07f87c4
refactor(webapp): extract the flood-fill from the screenshot compositor
FelixTJDietrich Jul 24, 2026
cb7c4c2
style(webapp): drop trailing comma in the flood-fill neighbors literal
FelixTJDietrich Jul 24, 2026
2900276
Merge remote-tracking branch 'origin/main' into feat/ios-icon-update
FelixTJDietrich Jul 24, 2026
2dedd20
fix(webapp): add the shared App scheme and sync the new plugin pods
FelixTJDietrich Jul 24, 2026
827eea4
fix(deps): keep the VS Code extension linked to the library workspace
FelixTJDietrich Jul 24, 2026
607985e
fix(webapp): archive pristine signed assets and pin OTA to the deploy…
FelixTJDietrich Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/three-socks-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tumaet/webapp": patch
---

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.
9 changes: 9 additions & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Codacy configuration.
#
# `exclude_paths` is additive to Codacy's default ignores and does not change
# which analysis tools are enabled — it only tells Codacy to skip these files.
exclude_paths:
# Vendored verbatim from the fastlane project
# (https://github.com/fastlane/fastlane) and updated by re-copying, not by
# hand. We do not restyle it to local lint rules.
- "standalone/webapp/ios/App/AppStoreScreenshots/SnapshotHelper.swift"
16 changes: 16 additions & 0 deletions .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,19 @@ jobs:
image-tag: ${{ inputs.image-tag }}
deployment-base-path: "/opt/apollon/app"
secrets: inherit

# Publish the matching over-the-air web-bundle update for the mobile app right
# after the web app is deployed ("deploy == OTA publish"). Opt-in: only runs
# where the ENABLE_LIVE_UPDATE variable is "true", so operators who don't ship
# a mobile app are unaffected.
publish-live-update:
needs: deploy-app
if: ${{ inputs.deploy-app && vars.ENABLE_LIVE_UPDATE == 'true' }}
Comment thread
FelixTJDietrich marked this conversation as resolved.
uses: ./.github/workflows/ios-live-update.yml
with:
# Build the OTA bundle from the SAME commit as the deployed image, not from
# whatever ref this workflow was dispatched on — otherwise a rollback or a
# redeploy of an older `sha-<commit>` tag would ship a web bundle that does
# not match the web app that just went live.
source-ref: ${{ inputs.image-tag }}
secrets: inherit
105 changes: 105 additions & 0 deletions .github/workflows/ios-live-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: ios-live-update

# Publishes an over-the-air web-bundle update for the mobile app. The bundle is
# the SAME dist the webapp deploy ships, signed with the Capgo private key, and
# copied to the directory the webapp nginx serves at /live-updates/. Reuses the
# EXISTING deployment identity and gateway (VM_* + DEPLOYMENT_GATEWAY_*), so no
# new key or user is introduced. Runs on manual dispatch and is callable from
# the production deploy so a normal deploy also publishes the matching bundle.
#
# Uses (already configured for deploys):
# secrets: CAPGO_PRIVATE_KEY, VM_SSH_PRIVATE_KEY, DEPLOYMENT_GATEWAY_SSH_KEY
# vars: CAPGO_PUBLIC_KEY, CAPGO_MIN_NATIVE_VERSION, VM_HOST, VM_USERNAME,
# DEPLOYMENT_GATEWAY_HOST, DEPLOYMENT_GATEWAY_USER, DEPLOYMENT_GATEWAY_PORT

on:
workflow_dispatch:
inputs:
source-ref:
description: >
Commit to package, as the deployed image tag (`sha-<commit>`) or a raw
commit SHA. Empty uses the dispatched ref.
required: false
type: string
workflow_call:
inputs:
source-ref:
description: "Deployed image tag (`sha-<commit>`) or commit SHA to package."
required: false
type: string

concurrency:
group: ios-live-update-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

jobs:
publish:
runs-on: ubuntu-latest
environment: Production
steps:
# `sha-<commit>` is the tag convention build-and-push publishes images under,
# so stripping the prefix yields the commit the deployed image was built from.
- name: Resolve the commit to package
id: source
run: |
REF="${{ inputs.source-ref }}"
echo "ref=${REF#sha-}" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ steps.source.outputs.ref }}

- uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6.0.8
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: .nvmrc
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build web bundle
run: |
pnpm --filter @tumaet/apollon run build
pnpm --filter @tumaet/webapp run build

- name: Build + sign the live-update bundle
working-directory: standalone/webapp
env:
CAPGO_PRIVATE_KEY: ${{ secrets.CAPGO_PRIVATE_KEY }}
CAPGO_PUBLIC_KEY: ${{ vars.CAPGO_PUBLIC_KEY }}
CAPGO_MIN_NATIVE_VERSION: ${{ vars.CAPGO_MIN_NATIVE_VERSION }}
run: pnpm run live-update:build

# Upload the immutable bundle zip(s) FIRST so the manifest never points at
# a file that is not yet on the host.
- name: Copy bundle zips to the VM
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
source: "standalone/webapp/live-updates-dist/*.zip"
target: "/opt/apollon/app/live-updates/"
strip_components: 3

- name: Copy manifest to the VM
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
with:
host: ${{ vars.VM_HOST }}
username: ${{ vars.VM_USERNAME }}
key: ${{ secrets.VM_SSH_PRIVATE_KEY }}
proxy_host: ${{ vars.DEPLOYMENT_GATEWAY_HOST }}
proxy_username: ${{ vars.DEPLOYMENT_GATEWAY_USER }}
proxy_key: ${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}
proxy_port: ${{ vars.DEPLOYMENT_GATEWAY_PORT }}
source: "standalone/webapp/live-updates-dist/manifest.json"
target: "/opt/apollon/app/live-updates/"
strip_components: 3
109 changes: 105 additions & 4 deletions .github/workflows/ios-testflight-release.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
name: ios-testflight-release
name: ios-release

on:
workflow_dispatch:
inputs:
destination:
description: Where to publish the build and App Store assets
required: true
default: testflight
type: choice
options:
- testflight
- app-store-assets
- app-store
submit_for_review:
description: Submit the App Store version for review after upload
required: true
default: false
type: boolean
screenshot_style:
description: Screenshot presentation to upload
required: true
default: framed
type: choice
options:
- raw
- framed

concurrency:
group: ios-testflight-release-${{ github.ref }}
group: ios-release-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

jobs:
testflight:
release:
runs-on: [self-hosted, macOS]
timeout-minutes: 60
defaults:
Expand All @@ -32,6 +55,11 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Validate App Store metadata and public links
if: inputs.destination != 'testflight'
working-directory: standalone/webapp
run: pnpm appstore:metadata:validate

- name: Install Ruby gems (fastlane + cocoapods)
working-directory: standalone/webapp
run: |
Expand All @@ -53,15 +81,49 @@ jobs:

- name: Capacitor sync (iOS)
working-directory: standalone/webapp
# Bake the live-update signing public key into the app so the updater
# strictly rejects any bundle it can't verify. Unset (e.g. forks) → the
# feature is simply disabled, not insecure.
env:
CAPGO_PUBLIC_KEY: ${{ vars.CAPGO_PUBLIC_KEY }}
# Fail the build if the signing key is missing rather than shipping
# OTA with signature enforcement silently disabled.
CAPGO_REQUIRE_SIGNING: "true"
run: pnpm exec cap sync ios

- name: Resolve marketing version and build number
if: inputs.destination != 'app-store-assets'
run: |
MARKETING_VERSION=$(node -p "require('./standalone/webapp/package.json').version")
echo "MARKETING_VERSION=$MARKETING_VERSION" >> "$GITHUB_ENV"
echo "BUILD_NUMBER=$(date +'%Y%m%d%H%M')" >> "$GITHUB_ENV"

- name: Prepare licensed Apple product bezels
if: inputs.destination != 'testflight' && inputs.screenshot_style == 'framed'
working-directory: standalone/webapp
run: pnpm run appstore:screenshots:prepare-frames

- name: Capture iPhone and iPad App Store screenshots
if: inputs.destination != 'testflight'
working-directory: standalone/webapp
env:
APP_STORE_SCREENSHOT_STYLE: ${{ inputs.screenshot_style }}
run: bundle exec fastlane screenshots
Comment thread
FelixTJDietrich marked this conversation as resolved.

- name: Upload App Store screenshot review
if: inputs.destination != 'testflight'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ios-app-store-screenshot-review
path: |
standalone/webapp/fastlane/screenshots/
standalone/webapp/fastlane/screenshots-framed/
standalone/webapp/fastlane/screenshots-review.html
if-no-files-found: error
retention-days: 14

- name: Build iOS app (Fastlane)
if: inputs.destination != 'app-store-assets'
working-directory: standalone/webapp
env:
BUILD_NUMBER: ${{ env.BUILD_NUMBER }}
Expand All @@ -72,18 +134,57 @@ jobs:
API_KEY_ID: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY_ID }}
API_KEY_ISSUER_ID: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
API_KEY_PASSWORD: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_PASSWORD }}
# The lane rebuilds and re-syncs the web bundle before archiving, so the
# signing key must be present here too — otherwise the archived binary
# would accept unsigned live updates.
CAPGO_PUBLIC_KEY: ${{ vars.CAPGO_PUBLIC_KEY }}
CAPGO_REQUIRE_SIGNING: "true"
run: bundle exec fastlane build

- name: Upload to TestFlight (Fastlane)
if: inputs.destination == 'testflight'
working-directory: standalone/webapp
env:
API_KEY_ID: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY_ID }}
API_KEY_ISSUER_ID: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
API_KEY_PASSWORD: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_PASSWORD }}
run: bundle exec fastlane release

- name: Upload App Store metadata and screenshots (Fastlane)
if: inputs.destination == 'app-store-assets'
working-directory: standalone/webapp
env:
APP_STORE_SCREENSHOT_STYLE: ${{ inputs.screenshot_style }}
API_KEY_ID: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY_ID }}
API_KEY_ISSUER_ID: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
API_KEY_PASSWORD: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_PASSWORD }}
# Optional: App Review contact. Unset secrets are skipped, leaving the
# existing App Store Connect values untouched.
APP_REVIEW_FIRST_NAME: ${{ secrets.IOS_APP_REVIEW_FIRST_NAME }}
APP_REVIEW_LAST_NAME: ${{ secrets.IOS_APP_REVIEW_LAST_NAME }}
APP_REVIEW_EMAIL: ${{ secrets.IOS_APP_REVIEW_EMAIL }}
APP_REVIEW_PHONE: ${{ secrets.IOS_APP_REVIEW_PHONE }}
run: bundle exec fastlane store_assets

- name: Upload App Store build, metadata, and screenshots (Fastlane)
if: inputs.destination == 'app-store'
working-directory: standalone/webapp
env:
APP_STORE_SCREENSHOT_STYLE: ${{ inputs.screenshot_style }}
API_KEY_ID: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY_ID }}
API_KEY_ISSUER_ID: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
API_KEY_PASSWORD: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_PASSWORD }}
SUBMIT_FOR_REVIEW: ${{ inputs.submit_for_review }}
# Optional: App Review contact. Unset secrets are skipped, leaving the
# existing App Store Connect values untouched.
APP_REVIEW_FIRST_NAME: ${{ secrets.IOS_APP_REVIEW_FIRST_NAME }}
APP_REVIEW_LAST_NAME: ${{ secrets.IOS_APP_REVIEW_LAST_NAME }}
APP_REVIEW_EMAIL: ${{ secrets.IOS_APP_REVIEW_EMAIL }}
APP_REVIEW_PHONE: ${{ secrets.IOS_APP_REVIEW_PHONE }}
run: bundle exec fastlane release_app_store

- name: Upload IPA artifact
if: always()
if: always() && inputs.destination != 'app-store-assets'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ios-ipa
Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ android/
**/fastlane/README.md
**/fastlane/test_output
**/fastlane/screenshots
**/fastlane/screenshots-framed
**/fastlane/screenshots-preview
**/fastlane/screenshots-review.html
**/fastlane/device-frames
**/.DerivedData*/

# Capacitor generated icons
icons/
Expand All @@ -79,3 +84,6 @@ docs/build/

# Temp dir used by scripts/check-doc-snippets.mjs (removed on each run)
docs/.snippet-typecheck/

# Live-update publish artifacts (built by scripts/build-live-update.mjs)
**/live-updates-dist/
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ ios/
.docusaurus/
**/playwright-report/
**/test-results/
**/fastlane/README.md
pnpm-lock.yaml

# Generated by the TanStack Router plugin (committed, not hand-formatted).
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ The docs are a [Docusaurus](https://docusaurus.io/) site published at <https://l
- [Library](https://ls1intum.github.io/Apollon/library/): embedding the `@tumaet/apollon` editor.
- [User Guide](https://ls1intum.github.io/Apollon/user/): getting started, requirements, and self-hosting.
- [Contributor](https://ls1intum.github.io/Apollon/contributor/): project structure, scripts, deployment, and troubleshooting.
- [Support](https://ls1intum.github.io/Apollon/user/support): getting help, and updating from the previous iPhone/iPad app.

Operations, legal pages, and TUM DSMS material live in [`ops/`](./ops) in this repo.

Expand Down
8 changes: 8 additions & 0 deletions docker/compose.app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ services:
- LEGAL_PROFILE=${LEGAL_PROFILE:-}
networks:
- apollon-network
volumes:
# First-party over-the-air live-update bundles for the mobile app, served
# by this container's nginx at /live-updates/ (see nginx.conf). Populated
# by the ios-live-update workflow on deploy; read-only in the container.
# An absent/empty directory simply yields 404s and the app stays on its
# shipped bundle, so this is safe before the first publish and for operators
# who ship no mobile app.
- "${LIVE_UPDATE_DIR:-/opt/apollon/app/live-updates}:/usr/share/nginx/html/live-updates:ro"
cap_drop:
- ALL
security_opt:
Expand Down
Loading
Loading