diff --git a/apps/apple/CascadeMac/Cascade.entitlements b/apps/apple/CascadeMac/Cascade.entitlements
index 28d8e54..0ab12df 100644
--- a/apps/apple/CascadeMac/Cascade.entitlements
+++ b/apps/apple/CascadeMac/Cascade.entitlements
@@ -2,13 +2,9 @@
-
- com.apple.security.app-sandbox
-
-
- com.apple.security.network.client
-
+ com.apple.security.app-sandbox
+
+ com.apple.security.network.client
+
diff --git a/apps/apple/CascadeMac/Info.plist b/apps/apple/CascadeMac/Info.plist
index 9d1d8e2..53bed7a 100644
--- a/apps/apple/CascadeMac/Info.plist
+++ b/apps/apple/CascadeMac/Info.plist
@@ -2,17 +2,29 @@
- CFBundleDisplayName
- Cascade
- CFBundleName
- Cascade
- NSHumanReadableCopyright
- © 2026 Jacob Stephens
-
- NSHighResolutionCapable
-
-
- MPNowPlayingInfoPropertyMediaType
- 1
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Cascade
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ MPNowPlayingInfoPropertyMediaType
+ 1
+ NSHighResolutionCapable
+
+ NSHumanReadableCopyright
+ © 2026 Jacob Stephens
diff --git a/apps/apple/CascadeWatch/Info.plist b/apps/apple/CascadeWatch/Info.plist
index 90534f0..0817ede 100644
--- a/apps/apple/CascadeWatch/Info.plist
+++ b/apps/apple/CascadeWatch/Info.plist
@@ -2,18 +2,31 @@
- CFBundleDisplayName
- Cascade
- CFBundleName
- Cascade
- WKApplication
-
- WKWatchOnly
-
-
- NSHumanReadableCopyright
- © 2026 Jacob Stephens
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Cascade
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ NSHumanReadableCopyright
+ © 2026 Jacob Stephens
+ WKApplication
+
+ WKCompanionAppBundleIdentifier
+ page.stephens.cascade
+ WKWatchOnly
+
diff --git a/apps/apple/CascadeiOS/Info.plist b/apps/apple/CascadeiOS/Info.plist
index 0c68f49..eebf649 100644
--- a/apps/apple/CascadeiOS/Info.plist
+++ b/apps/apple/CascadeiOS/Info.plist
@@ -2,40 +2,49 @@
- CFBundleDisplayName
- Cascade
- CFBundleName
- Cascade
- UIRequiresFullScreen
-
- UILaunchScreen
-
- UIColorName
- LaunchBackground
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
- UIInterfaceOrientationPortraitUpsideDown
-
-
- UIBackgroundModes
-
- audio
-
-
- MPNowPlayingInfoPropertyMediaType
- 1
- NSHumanReadableCopyright
- © 2026 Jacob Stephens
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Cascade
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ MPNowPlayingInfoPropertyMediaType
+ 1
+ NSHumanReadableCopyright
+ © 2026 Jacob Stephens
+ UIBackgroundModes
+
+ audio
+
+ UILaunchScreen
+
+ UIColorName
+ LaunchBackground
+
+ UIRequiresFullScreen
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationPortraitUpsideDown
+
diff --git a/apps/apple/project.yml b/apps/apple/project.yml
index 8035d50..cdb7282 100644
--- a/apps/apple/project.yml
+++ b/apps/apple/project.yml
@@ -44,8 +44,20 @@ targets:
optional: true
info:
path: CascadeMac/Info.plist
+ properties:
+ CFBundleDisplayName: Cascade
+ NSHighResolutionCapable: true
+ # Required so MPNowPlayingInfoCenter shows up in Control Center.
+ MPNowPlayingInfoPropertyMediaType: 1
+ NSHumanReadableCopyright: "© 2026 Jacob Stephens"
entitlements:
path: CascadeMac/Cascade.entitlements
+ properties:
+ # App Sandbox: audio + Application Support writes + IOKit power
+ # assertions + MediaPlayer Now Playing all work under sandbox.
+ com.apple.security.app-sandbox: true
+ # Explicit: this app never reaches the network.
+ com.apple.security.network.client: false
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: page.stephens.cascade
@@ -71,6 +83,26 @@ targets:
optional: true
info:
path: CascadeiOS/Info.plist
+ properties:
+ CFBundleDisplayName: Cascade
+ UIRequiresFullScreen: false
+ UILaunchScreen:
+ UIColorName: LaunchBackground
+ UISupportedInterfaceOrientations:
+ - UIInterfaceOrientationPortrait
+ "UISupportedInterfaceOrientations~ipad":
+ - UIInterfaceOrientationPortrait
+ - UIInterfaceOrientationLandscapeLeft
+ - UIInterfaceOrientationLandscapeRight
+ - UIInterfaceOrientationPortraitUpsideDown
+ # Keep the audio engine running when the screen locks or the app is
+ # backgrounded — non-negotiable for an 8-hour focus session player.
+ UIBackgroundModes:
+ - audio
+ # Required so MPNowPlayingInfoCenter shows on the lock screen / Control
+ # Center for AirPods / headphone-button control.
+ MPNowPlayingInfoPropertyMediaType: 1
+ NSHumanReadableCopyright: "© 2026 Jacob Stephens"
entitlements:
path: CascadeiOS/Cascade.entitlements
settings:
@@ -111,6 +143,16 @@ targets:
optional: true
info:
path: CascadeWatch/Info.plist
+ properties:
+ CFBundleDisplayName: Cascade
+ # Marks this as a (modern, single-target) watchOS app. Without it,
+ # installd rejects the embedded watch bundle ("no WKApplication key").
+ WKApplication: true
+ WKWatchOnly: false
+ # Pairs the watch app to the iPhone app as its companion — required for
+ # the embedded watch bundle to install alongside its iOS host.
+ WKCompanionAppBundleIdentifier: page.stephens.cascade
+ NSHumanReadableCopyright: "© 2026 Jacob Stephens"
entitlements:
path: CascadeWatch/Cascade.entitlements
settings:
diff --git a/apps/apple/scripts/install-device.sh b/apps/apple/scripts/install-device.sh
new file mode 100755
index 0000000..ac9b141
--- /dev/null
+++ b/apps/apple/scripts/install-device.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+# Build, sign, and install CascadeiOS onto a connected iPhone, then launch it.
+#
+# Prerequisites (one-time):
+# 1. iPhone connected + paired (tap "Trust This Computer" on the phone).
+# 2. Developer Mode ON: iPhone Settings > Privacy & Security > Developer Mode.
+# 3. An Apple ID added to Xcode (Settings > Accounts) so a development
+# signing certificate + team exist. A free Apple ID works (7-day apps).
+#
+# Usage (from apps/apple/):
+# ./scripts/install-device.sh [TEAM_ID] [DEVICE_UDID]
+# Both args are auto-detected if omitted.
+set -euo pipefail
+cd "$(dirname "$0")/.." # -> apps/apple
+
+# --- resolve the device ---------------------------------------------------
+UDID="${2:-$(xcrun devicectl list devices 2>/dev/null | awk '/available/{print $3; exit}')}"
+[ -n "${UDID:-}" ] || { echo "No connected/paired device found. Plug in + Trust the phone."; exit 1; }
+echo "→ device: $UDID"
+
+# --- resolve the signing team ---------------------------------------------
+TEAM="${1:-$(security find-certificate -a -c 'Apple Development' -p 2>/dev/null \
+ | openssl x509 -noout -subject 2>/dev/null \
+ | grep -oE 'OU *= *[A-Z0-9]{10}' | grep -oE '[A-Z0-9]{10}' | head -1)}"
+[ -n "${TEAM:-}" ] || { echo "No Apple Development team found. Add your Apple ID in Xcode > Settings > Accounts, then pass the 10-char Team ID as arg 1."; exit 1; }
+echo "→ team: $TEAM"
+
+# --- build + sign for the device ------------------------------------------
+echo "→ building (automatic signing)…"
+xcodebuild -project Cascade.xcodeproj -scheme CascadeiOS -configuration Debug \
+ -destination "id=$UDID" -derivedDataPath build/dd-iosdev \
+ DEVELOPMENT_TEAM="$TEAM" CODE_SIGN_STYLE=Automatic \
+ -allowProvisioningUpdates -allowProvisioningDeviceRegistration build
+
+APP="build/dd-iosdev/Build/Products/Debug-iphoneos/Cascade.app"
+[ -d "$APP" ] || { echo "Build did not produce $APP"; exit 1; }
+
+# --- install + launch ------------------------------------------------------
+echo "→ installing onto device…"
+xcrun devicectl device install app --device "$UDID" "$APP"
+echo "→ launching…"
+xcrun devicectl device process launch --device "$UDID" page.stephens.cascade || true
+echo "✓ Done. If the phone shows 'Untrusted Developer', go to Settings > General"
+echo " > VPN & Device Management, trust your cert, then re-run this script."