Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 4 additions & 8 deletions apps/apple/CascadeMac/Cascade.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- App Sandbox lets us run inside the macOS sandbox. Audio + Application
Support writes + IOKit power assertions + MediaPlayer Now Playing all
work under sandbox without extra entitlements. -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Explicit: this app never reaches the network. -->
<key>com.apple.security.network.client</key>
<false/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<false/>
</dict>
</plist>
36 changes: 24 additions & 12 deletions apps/apple/CascadeMac/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,29 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Cascade</string>
<key>CFBundleName</key>
<string>Cascade</string>
<key>NSHumanReadableCopyright</key>
<string>© 2026 Jacob Stephens</string>
<!-- High-resolution capable -->
<key>NSHighResolutionCapable</key>
<true/>
<!-- Required so `MPNowPlayingInfoCenter` shows up in Control Center. -->
<key>MPNowPlayingInfoPropertyMediaType</key>
<integer>1</integer>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Cascade</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>MPNowPlayingInfoPropertyMediaType</key>
<integer>1</integer>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>© 2026 Jacob Stephens</string>
</dict>
</plist>
39 changes: 26 additions & 13 deletions apps/apple/CascadeWatch/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,31 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Cascade</string>
<key>CFBundleName</key>
<string>Cascade</string>
<key>WKApplication</key>
<true/>
<key>WKWatchOnly</key>
<false/>
<!-- Mode A (thin remote) does not need background audio on the watch.
When/if Mode B ships, add:
<key>WKBackgroundModes</key><array><string>audio</string></array> -->
<key>NSHumanReadableCopyright</key>
<string>© 2026 Jacob Stephens</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Cascade</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>© 2026 Jacob Stephens</string>
<key>WKApplication</key>
<true/>
<key>WKCompanionAppBundleIdentifier</key>
<string>page.stephens.cascade</string>
<key>WKWatchOnly</key>
<false/>
</dict>
</plist>
79 changes: 44 additions & 35 deletions apps/apple/CascadeiOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,49 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Cascade</string>
<key>CFBundleName</key>
<string>Cascade</string>
<key>UIRequiresFullScreen</key>
<false/>
<key>UILaunchScreen</key>
<dict>
<key>UIColorName</key>
<string>LaunchBackground</string>
</dict>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<!-- Allow the audio engine to keep running when the screen locks or the
user backgrounds the app — non-negotiable for an 8-hour focus session
player. -->
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<!-- Required so MPNowPlayingInfoCenter shows up on the lock screen and
Control Center for AirPods / headphone button control. -->
<key>MPNowPlayingInfoPropertyMediaType</key>
<integer>1</integer>
<key>NSHumanReadableCopyright</key>
<string>© 2026 Jacob Stephens</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Cascade</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>MPNowPlayingInfoPropertyMediaType</key>
<integer>1</integer>
<key>NSHumanReadableCopyright</key>
<string>© 2026 Jacob Stephens</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchScreen</key>
<dict>
<key>UIColorName</key>
<string>LaunchBackground</string>
</dict>
<key>UIRequiresFullScreen</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
</dict>
</plist>
42 changes: 42 additions & 0 deletions apps/apple/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
44 changes: 44 additions & 0 deletions apps/apple/scripts/install-device.sh
Original file line number Diff line number Diff line change
@@ -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."
Loading