diff --git a/CLAUDE.md b/CLAUDE.md index cce6407..261cf33 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,17 +11,20 @@ PearCal is a peer-to-peer calendar app for Android and iOS built with Expo (Reac ### Android Two test devices are connected via ADB: -- Device 1: `53071FDAP00038` — Pixel/GrapheneOS (owner) -- Device 2: `4H65K7MFZXSCSWPR` — TCL (joiner) +- Device 1: `53071FDAP00038` - Pixel/GrapheneOS (owner). Wifi only, and its IP and + port change on every reconnect. Resolve it with + `/home/tim/peerloomllc/adb-find.sh pixel`, which discovers the current address + over mDNS and prints it. Never hardcode the address. +- Device 2: `4H65K7MFZXSCSWPR` - TCL (joiner). USB, so the serial is stable. -Always use `adb install -r` — **never uninstall** (preserves user data). +Always use `adb install -r` - **never uninstall** (preserves user data). **UI-only changes** (`src/ui/App.jsx`, `src/ui/main.jsx`): ```bash npx esbuild src/ui/main.jsx --bundle --format=iife --jsx=automatic \ --define:process.env.NODE_ENV=\"production\" --outfile=assets/app-ui.bundle cd android && ./gradlew assembleDebug && cd .. -adb -s 53071FDAP00038 install -r android/app/build/outputs/apk/debug/app-debug.apk +adb -s "$(/home/tim/peerloomllc/adb-find.sh pixel)" install -r android/app/build/outputs/apk/debug/app-debug.apk adb -s 4H65K7MFZXSCSWPR install -r android/app/build/outputs/apk/debug/app-debug.apk ``` @@ -31,7 +34,7 @@ node_modules/.bin/bare-pack --linked --defer fs --defer path src/bare.js -o asse npx esbuild src/ui/main.jsx --bundle --format=iife --jsx=automatic \ --define:process.env.NODE_ENV=\"production\" --outfile=assets/app-ui.bundle cd android && ./gradlew assembleDebug && cd .. -adb -s 53071FDAP00038 install -r android/app/build/outputs/apk/debug/app-debug.apk +adb -s "$(/home/tim/peerloomllc/adb-find.sh pixel)" install -r android/app/build/outputs/apk/debug/app-debug.apk adb -s 4H65K7MFZXSCSWPR install -r android/app/build/outputs/apk/debug/app-debug.apk ``` @@ -41,7 +44,7 @@ cd android && ./gradlew assembleRelease && cd .. /home/tim/Android/Sdk/build-tools/36.1.0/apksigner verify --verbose android/app/build/outputs/apk/release/app-release.apk cp android/app/build/outputs/apk/release/app-release.apk ~/pearcal-release.apk ``` -Keystore: `~/keystore.jks` — alias: `pearcal` +Keystore: `~/keystore.jks` - alias: `pearcal` ### iOS @@ -54,7 +57,7 @@ iPhone UDID: `00008030-0009714C2613402E` ssh Tims-Mac-mini.local 'security unlock-keychain -p "" ~/Library/Keychains/buildkey.keychain' ``` -**Note on configuration:** Use `-configuration Release` for device installs — Debug builds try to connect to a Metro bundler at localhost:8081, which fails on physical devices without a running dev server. Release builds embed the JS bundle. +**Note on configuration:** Use `-configuration Release` for device installs - Debug builds try to connect to a Metro bundler at localhost:8081, which fails on physical devices without a running dev server. Release builds embed the JS bundle. **Signing note:** `DEVELOPMENT_TEAM=G79ALD29NA` uses the wildcard provisioning profile already cached on the Mac. `-allowProvisioningUpdates` does NOT work over SSH (no Apple account in the SSH session). @@ -80,7 +83,7 @@ rsync -az Tims-Mac-mini.local:/tmp/PearCal-release.ipa /tmp/ ideviceinstaller install /tmp/PearCal-release.ipa ``` -`set -o pipefail` is required — without it, `tail` swallows xcodebuild's non-zero exit code and the chain falls through to repackage an old cached `PearCal.app` in DerivedData, silently shipping a stale IPA. +`set -o pipefail` is required - without it, `tail` swallows xcodebuild's non-zero exit code and the chain falls through to repackage an old cached `PearCal.app` in DerivedData, silently shipping a stale IPA. **bare.js changes** (rebuild both bundles, then build iOS): ```bash @@ -112,7 +115,7 @@ ideviceinstaller install /tmp/PearCal-release.ipa **iOS bundle caching:** Expo's asset cache survives install-over-top. If iOS behaves differently than expected after a deploy, do a full uninstall first: `ideviceinstaller uninstall com.pearcal` (this wipes app data). Also always keep `bare-ios-sim.bundle` in sync with `bare-ios.bundle` and use `--checksum` with rsync. -**Bare native-addon version drift (iOS boot crash `ADDON_NOT_FOUND`):** the bare bundle is `bare-pack`ed on Linux (uses Linux `node_modules`), but iOS links its native addons (`bare-os`, `bare-fs`, …) as xcframeworks generated by `pod install` on the Mac (from the Mac's `node_modules`). `rsync` syncs source only, not `node_modules`, so the two machines drift. If they diverge, the bundle demands e.g. `bare-os.3.9.3.framework` while the app ships `3.6.2` → `ADDON_NOT_FOUND` → unhandled rejection → `Bare.exit` → immediate crash on every launch (even a fresh install; dies before `Init DB`). **After ANY dependency change (`package.json`), the next iOS build MUST run `npm install` + `pod install` on the Mac first** — the "bare.js changes" / "UI-only" flows above skip both. Order matters: rsync FIRST, then `pod install` (rsync clobbers the Mac's fresh `Podfile.lock` otherwise → `Check Pods Manifest.lock` fails, exit 65). Debug via the iOS Simulator on the Mac (`xcrun simctl … log stream --predicate 'process=="PearCal"'`) — Release console output is visible there, unlike on a physical device. +**Bare native-addon version drift (iOS boot crash `ADDON_NOT_FOUND`):** the bare bundle is `bare-pack`ed on Linux (uses Linux `node_modules`), but iOS links its native addons (`bare-os`, `bare-fs`, …) as xcframeworks generated by `pod install` on the Mac (from the Mac's `node_modules`). `rsync` syncs source only, not `node_modules`, so the two machines drift. If they diverge, the bundle demands e.g. `bare-os.3.9.3.framework` while the app ships `3.6.2` → `ADDON_NOT_FOUND` → unhandled rejection → `Bare.exit` → immediate crash on every launch (even a fresh install; dies before `Init DB`). **After ANY dependency change (`package.json`), the next iOS build MUST run `npm install` + `pod install` on the Mac first** - the "bare.js changes" / "UI-only" flows above skip both. Order matters: rsync FIRST, then `pod install` (rsync clobbers the Mac's fresh `Podfile.lock` otherwise → `Check Pods Manifest.lock` fails, exit 65). Debug via the iOS Simulator on the Mac (`xcrun simctl … log stream --predicate 'process=="PearCal"'`) - Release console output is visible there, unlike on a physical device. **Note:** New Swift/`.m` files must be registered in `PearCal.xcodeproj/project.pbxproj` via the `xcodeproj` Ruby gem before building. See plan tasks for the helper script. @@ -120,20 +123,20 @@ ideviceinstaller install /tmp/PearCal-release.ipa ## Branch Strategy -Always create a branch before starting work — never commit directly to master. +Always create a branch before starting work - never commit directly to master. - Feature branches: `feature/description` - Bug fix branches: `bugfix/description` -- Merge via GitHub PR: `gh pr merge N --merge` -- After merge: `git checkout master && git pull github master` +- Open a PR with `gh pr create`, then stop. Tim reviews and merges (see root `CLAUDE.md`). +- After Tim merges: `git checkout master && git pull github master` ## To-Do List Open items tracked in `TODO.md`; completed items in `DONE.md` (both at repo root, unversioned, gitignored). When adding items, categorize by type, priority, and complexity. -### Completing a TODO item — required workflow: +### Completing a TODO item - required workflow: 1. List verification tests/steps and prompt the user for confirmation/results. 2. If verification passes and no additional work is needed, proceed to commits. -3. After commits, remove the item from `TODO.md` and add it to the top of the relevant date section in `DONE.md` as `- [x] Description — completed YYYY-MM-DD`, then show the remaining open TODO items. +3. After commits, remove the item from `TODO.md` and add it to the top of the relevant date section in `DONE.md` as `- [x] Description - completed YYYY-MM-DD`, then show the remaining open TODO items. ## Patching Rules @@ -185,7 +188,7 @@ Most method calls (`getProfile`, `putEvent`, `joinGroup`, etc.) route transparen | `src/bare.js` | Bare worklet: DB, Autobase groups, Hyperswarm, all data logic | | `src/ui/main.jsx` | WebView bootstrap: sets up `window.__pearDB`, `window.__pearSync`, renders `` | | `src/ui/App.jsx` | Full React calendar UI (runs entirely inside the WebView) | -| `src/invite.js` | Invite link builder/parser — shared between UI and bare contexts | +| `src/invite.js` | Invite link builder/parser - shared between UI and bare contexts | | `src/ui/bundle.js` | Bundled output stub (not edited directly) | ### Data Storage (Bare worklet) @@ -198,7 +201,7 @@ Most method calls (`getProfile`, `putEvent`, `joinGroup`, etc.) route transparen - Group membership and events are mirrored from Autobase view back to local DB via `mirrorToLocal()` - **Peer discovery**: `Hyperswarm`, one topic per group (derived from `groupKey`) -### Native Modules (Android — `android/app/src/main/java/com/pearcal/`, iOS — `ios/PearCal/`) +### Native Modules (Android - `android/app/src/main/java/com/pearcal/`, iOS - `ios/PearCal/`) | Module | Purpose | |--------|---------| diff --git a/app.json b/app.json index aa5c4df..a8232b4 100644 --- a/app.json +++ b/app.json @@ -29,12 +29,12 @@ ] } ], - "versionCode": 1000031, + "versionCode": 1000034, "privacyPolicy": "https://peerloomllc.github.io/pearcal-native/privacy.html" }, "ios": { "bundleIdentifier": "com.pearcal", - "buildNumber": "34", + "buildNumber": "37", "infoPlist": { "NSCameraUsageDescription": "Used to set your profile photo", "NSPhotoLibraryUsageDescription": "Used to set your profile photo", @@ -65,6 +65,6 @@ "assets": [ "./assets/bare-universal.bundle" ], - "version": "1.0.31" + "version": "1.0.34" } } diff --git a/electron/package.json b/electron/package.json index c7acdb8..c9af113 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "pearcal-electron", - "version": "1.0.31", + "version": "1.0.34", "description": "PearCal desktop client (Electron). Mirrors PearGuard's pattern.", "homepage": "https://peerloomllc.com/pearcal/", "author": { diff --git a/ios/PearCal.xcodeproj/project.pbxproj b/ios/PearCal.xcodeproj/project.pbxproj index c18c3ad..880ed53 100644 --- a/ios/PearCal.xcodeproj/project.pbxproj +++ b/ios/PearCal.xcodeproj/project.pbxproj @@ -561,7 +561,7 @@ CODE_SIGN_ENTITLEMENTS = PearCal/PearCal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 34; + CURRENT_PROJECT_VERSION = 37; DEVELOPMENT_TEAM = G79ALD29NA; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -574,7 +574,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.31; + MARKETING_VERSION = 1.0.34; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -601,7 +601,7 @@ CODE_SIGN_ENTITLEMENTS = PearCal/PearCal.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 34; + CURRENT_PROJECT_VERSION = 37; DEVELOPMENT_TEAM = G79ALD29NA; INFOPLIST_FILE = PearCal/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -609,7 +609,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.31; + MARKETING_VERSION = 1.0.34; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -635,7 +635,7 @@ CODE_SIGN_ENTITLEMENTS = PearCalWidget/PearCalWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 34; + CURRENT_PROJECT_VERSION = 37; DEVELOPMENT_TEAM = G79ALD29NA; INFOPLIST_FILE = PearCalWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -644,7 +644,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.31; + MARKETING_VERSION = 1.0.34; PRODUCT_BUNDLE_IDENTIFIER = com.pearcal.widget; PRODUCT_NAME = PearCalWidget; PROVISIONING_PROFILE_SPECIFIER = "PearCal Widget Development"; @@ -782,7 +782,7 @@ CODE_SIGN_ENTITLEMENTS = PearCalWidget/PearCalWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 34; + CURRENT_PROJECT_VERSION = 37; DEVELOPMENT_TEAM = G79ALD29NA; INFOPLIST_FILE = PearCalWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -791,7 +791,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.31; + MARKETING_VERSION = 1.0.34; PRODUCT_BUNDLE_IDENTIFIER = com.pearcal.widget; PRODUCT_NAME = PearCalWidget; PROVISIONING_PROFILE_SPECIFIER = "PearCal Widget Development"; diff --git a/metadata/ios/en-US/release_notes.txt b/metadata/ios/en-US/release_notes.txt index cb52371..f261dc4 100644 --- a/metadata/ios/en-US/release_notes.txt +++ b/metadata/ios/en-US/release_notes.txt @@ -1,13 +1,28 @@ -## What's Changed +🎉 New Features + + - Blind Peers — always-on backup & sync for your shared groups. Pair an always-on "blind peer" to a group and it keeps everyone in sync and backed up around the clock — even when every member's phone is offline — and it can never read your events. Scan a QR to admit one, name each peer, watch its status live, and remove it anytime. + - Encrypted groups. Group data is now encrypted per group, so blind peers (and any helper along the way) only store scrambled data they can't decrypt. Existing groups get a one-time heads-up when encryption turns on. -### ✨ Improvements + ✨ Improvements -- custom interval recurrence + editable series end date -- optional show-upcoming-events on the daily widget (toggle under Profile --> Personal settings) -- in-place auto-update for NSIS + AppImage + - More reliable sync when two devices share a Wi-Fi network. + - Desktop: application menu (Edit + Help/About); notification behavior now matches mobile; irrelevant buttons hidden. + - Blind peers can be renamed inline, and names show up across your devices. -### 🐛 Bug Fixes + 🐛 Bug Fixes -- restore reminders after reboot and app update -- event-editor field parity + centered modal (desktop) + - Android: fixed the stuck-on-loading-screen after force-close + quick reopen. + - iOS: fixed a launch crash. + - App no longer gets stuck at startup if one group fails to open. + - A group's sync helper no longer lingers after the group is deleted. + + 🔒 Security & Privacy + + - Deleting a group, leaving a group, and transferring ownership are now verified against the real owner — a removed/malicious member can't delete or hijack a group. + - Group data is encrypted end-to-end across sync helpers. + + 🖥 Desktop & Seeder (GitHub builds) + + - Blind-peer app installable on Linux, Windows, macOS, Umbrel, and Start9, with auto-update. + - Windows desktop builds now run locally without a build VM. diff --git a/metadata/ios/version/1.0.34/en-US.json b/metadata/ios/version/1.0.34/en-US.json new file mode 100644 index 0000000..1ed21fd --- /dev/null +++ b/metadata/ios/version/1.0.34/en-US.json @@ -0,0 +1 @@ +{"description": "PearCal is a peer-to-peer calendar app that syncs directly between devices \u2014 no server, no cloud, no accounts required.\n\nYour calendar data never leaves your group. It lives only on the devices of the people you choose to share with, powered by Hypercore Protocol.\n\nFEATURES\n\nPeer Groups\n\nCreate a group and invite others via a share link or QR code. Each group has its own color and avatar. Members see only the events shared with them.\n\nEvents & Reminders\n\nCreate one-time or recurring events. Set a reminder time per event. Selectively share events with specific group members \u2014 not everyone in a group has to see every event.\n\nHolidays\nOptional US, Canada, and UK public holiday calendars. Toggle per country from your profile. Holiday events are read-only and stored locally only.\n\nPrivacy First\n\nNo account creation. No email address. No tracking. No ads. Your data stays on your devices.\n\nOpen Source\nPearCal is free and open source. Source code is available on GitHub.\n\nPERMISSIONS\n\nCamera \u2014 used to set your profile photo and scan QR invite codes.\nPhoto Library \u2014 used to set your profile photo.", "keywords": "calendar,p2p,peer-to-peer,private,shared,group,decentralized,no cloud,events,reminders,holidays", "supportUrl": "https://github.com/peerloomllc/pearcal-native/issues", "whatsNew": "New Features\n \n - Blind Peers \u2014 always-on backup & sync for your shared groups. Pair an always-on \"blind peer\" to a group and it keeps everyone in sync and backed up around the clock \u2014 even when every member's phone is offline \u2014 and it can never read your events. Scan a QR to admit one, name each peer, watch its status live, and remove it anytime.\n - Encrypted groups. Group data is now encrypted per group, so blind peers (and any helper along the way) only store scrambled data they can't decrypt. Existing groups get a one-time heads-up when encryption turns on.\n\n Improvements\n\n - More reliable sync when two devices share a Wi-Fi network.\n - Desktop: application menu (Edit + Help/About); notification behavior now matches mobile; irrelevant buttons hidden.\n - Blind peers can be renamed inline, and names show up across your devices.\n\n Bug Fixes\n\n - Android: fixed the stuck-on-loading-screen after force-close + quick reopen.\n - iOS: fixed a launch crash.\n - App no longer gets stuck at startup if one group fails to open.\n - A group's sync helper no longer lingers after the group is deleted.\n\n Security & Privacy\n\n - Deleting a group, leaving a group, and transferring ownership are now verified against the real owner \u2014 a removed/malicious member can't delete or hijack a group.\n - Group data is encrypted end-to-end across sync helpers.\n\n Desktop & Seeder (GitHub builds)\n\n - Blind-peer app installable on Linux, Windows, macOS, Umbrel, and Start9, with auto-update.\n - Windows desktop builds now run locally without a build VM."} \ No newline at end of file diff --git a/seeder-launcher/start9/Dockerfile b/seeder-launcher/start9/Dockerfile index 385361d..3345a3a 100644 --- a/seeder-launcher/start9/Dockerfile +++ b/seeder-launcher/start9/Dockerfile @@ -9,7 +9,7 @@ # # build-start9-s9pk.sh rewrites this FROM line to the freshly-pushed version + # manifest-list digest. -FROM ghcr.io/peerloomllc/pearcal-seeder:1.0.33@sha256:36b9e9407b053c71b840f29a5f0423a67f15c2806827687b2cbec9dd69586c45 +FROM ghcr.io/peerloomllc/pearcal-seeder:1.0.34@sha256:6273ea9b8bb34a841768598587b601ef02a7b066625952633f08b24bb76111c6 # The published image is node:22-bookworm-slim (Debian), which has no init. RUN apt-get update \ diff --git a/seeder-launcher/start9/manifest.yaml b/seeder-launcher/start9/manifest.yaml index 1de4caa..1b39699 100644 --- a/seeder-launcher/start9/manifest.yaml +++ b/seeder-launcher/start9/manifest.yaml @@ -10,7 +10,7 @@ id: pearcal-seeder title: "PearCal Seeder" # emver: up to four dot-separated numbers; the fourth (if present) is the StartOS # packaging revision. Kept in lockstep with the seeder image tag. -version: 1.0.33 +version: 1.0.34 release-notes: | First StartOS release of the PearCal blind-seeder. Runs the seed-mode worklet as an always-on service with a dashboard for enrolling groups and watching diff --git a/seeder-launcher/start9/scripts/procedures/migrations.ts b/seeder-launcher/start9/scripts/procedures/migrations.ts index a20ed31..47ba4c1 100644 --- a/seeder-launcher/start9/scripts/procedures/migrations.ts +++ b/seeder-launcher/start9/scripts/procedures/migrations.ts @@ -4,4 +4,4 @@ import { compat, types as T } from "../deps.ts"; // treats fresh installs and same-version reinstalls as up to date. // build-start9-s9pk.sh rewrites the version string on each release. export const migration: T.ExpectedExports.migration = compat.migrations - .fromMapping({}, "1.0.33"); + .fromMapping({}, "1.0.34"); diff --git a/seeder-launcher/umbrel/docker-compose.yml b/seeder-launcher/umbrel/docker-compose.yml index bb87820..2835905 100644 --- a/seeder-launcher/umbrel/docker-compose.yml +++ b/seeder-launcher/umbrel/docker-compose.yml @@ -13,7 +13,7 @@ services: app: # Published, public, multi-arch (amd64 + arm64), pinned by manifest-list # digest. build-umbrel-image.sh rewrites this to :@sha256:. - image: ghcr.io/peerloomllc/pearcal-seeder:1.0.33@sha256:36b9e9407b053c71b840f29a5f0423a67f15c2806827687b2cbec9dd69586c45 + image: ghcr.io/peerloomllc/pearcal-seeder:1.0.34@sha256:6273ea9b8bb34a841768598587b601ef02a7b066625952633f08b24bb76111c6 restart: on-failure stop_grace_period: 1m security_opt: diff --git a/seeder-launcher/umbrel/umbrel-app.yml b/seeder-launcher/umbrel/umbrel-app.yml index bbf8e65..56c1d1b 100644 --- a/seeder-launcher/umbrel/umbrel-app.yml +++ b/seeder-launcher/umbrel/umbrel-app.yml @@ -6,7 +6,7 @@ id: peerloom-pearcal-seeder name: PearCal Seeder tagline: Keep your PearCal groups in sync 24/7 category: networking -version: "1.0.33" +version: "1.0.34" port: 8731 description: >- Run a blind seeder for your PearCal groups on your Umbrel so their calendars