Extend release-preflight to reject an Android manifest missing the
READ_MEDIA_IMAGES/READ_MEDIA_VIDEO tools:node="remove" attributes and
an iOS Info.plist that regained dev-launcher-only keys, both
regressions introduced silently by expo prebuild during the SDK 54 to
56 upgrade.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Since the SDK 56 dependency churn, arborist nests next under apps/web and
npm 10 skipped its platform-optional SWC binaries on the x64 CI runner,
breaking next build. Pin the linux and darwin binaries as direct
optionalDependencies of @sauci/web (bump alongside next) and regenerate
the lockfile from scratch with npm 10.8.2 to match CI. Verified in a
linux/amd64 node:20.19.4 container: npm ci installs the binary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EAS auto-increment updates the Info.plists but not the widget target's
CURRENT_PROJECT_VERSION; the shipped widget binary already carries 47
from its Info.plist.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AAB sha256 4893ee6f64f90bb1b2e1f13c6b088cbe75103337fbc101751da7076224778c29
verified: com.sauci.app 1.0.8 (51), production upload key, four ABIs,
Worklets/Reanimated/Skia libraries, no broad media read permissions,
production OTA endpoint, channel, and code-signing certificate. Supersedes
the build 50 artifact, which predated the skia 2.6.9 bump and the
expo-av migration and was quarantined unshipped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
IPA sha256 760517ccf474d496c940c3f84825f4852306d840d913090610a77dc24450ee36
verified: com.sauci.app and com.sauci.app.LiveDrawWidget both 1.0.8 (47),
team BXVPBATDZY, associated domains, shared app group, production OTA
runtime, endpoint, and code-signing certificate, no dev-launcher keys.
Also syncs the tracked Podfile.lock to the archive's resolution (expo-av
removed; ExpoVideo, ExpoAudio, ExpoSystemUI added; skia 2.6.9).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
expo-av has no SDK 56 compatible release, and its EXEventEmitter.h
interface was removed from expo-modules-core, so the iOS release
archive fails compiling Pods/EXAV. Move video playback to
expo-video's useVideoPlayer/VideoView, and audio playback and
recording to expo-audio's createAudioPlayer/AudioModule.AudioRecorder,
preserving existing controls, mute/loop/resize behavior, and the
recording audio session configuration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2.6.2 fails to archive under the SDK 56 toolchain: base64.cpp includes
third_party/base64.h, which the podspec header glob no longer resolves.
2.6.9 includes the header relative to its own directory.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AAB sha256 29c9bee3949e51b027c558637c04eefec96bf7f177151cb9571ca2a289ffee4e
verified: com.sauci.app 1.0.8 (50), production upload key, four ABIs,
Worklets/Reanimated/Skia libraries, no broad media read permissions,
production OTA endpoint, channel, and code-signing certificate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SDK 56 prebuild added NSBonjourServices, RCTMetroPort, and the Expo
Dev Launcher NSLocalNetworkUsageDescription to the tracked Info.plist.
v1.0.7 shipped without them and the production app has no local-network
feature to declare.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SDK 56 prebuild dropped the tools:node=remove entries for
READ_MEDIA_IMAGES and READ_MEDIA_VIDEO, so the merged release manifest
regained permissions the Play media policy forbids; Sauci uses the system
photo picker. Restore the removals shipped in v1.0.7.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The prebuild-regenerated gradle.properties restored stock 2GB heap and
512MB Metaspace limits; KSP and CMake configuration under SDK 56 exhaust
Metaspace and fail the release build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bare expo export (used by the OTA preflight and by eoas publish) also
exported the unused web target, whose static render breaks under SDK 56.
XPREM serves only iOS and Android bundles and the website is apps/web, so
declare the Expo app native-only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SDK 56 prebuild derived the widget bundle id from its folder name
(com.sauci.app.widget) and pointed CODE_SIGN_ENTITLEMENTS at the ignored
ios/.targets cache. Restore the shipped com.sauci.app.LiveDrawWidget
identity, the tracked entitlements file, and pin bundleIdentifier in the
target config so future prebuilds keep it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Install expo-observe (56.0.29, matching the SDK 56 line) and configure
its endpoint under expo.extra.eas.observe in app.json, alongside the
existing EAS projectId. Leave dispatchInDebug and sampleRate at their
defaults.
Log an app_started custom event once per cold start from the root
layout's existing "run once" startup effect, and install a global JS
error handler that logs xprem_js_crash and then always delegates to
whatever handler was previously registered, so existing Crashlytics
reporting is unaffected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump expo, react, react-native, and all expo-* / react-native-* peers to
the SDK 56 line via expo install --fix, adding expo-splash-screen and
expo-system-ui for config plugins the SDK split out of core. Bump
@bacons/apple-targets, react-native-view-shot, and other native modules
to versions compatible with React Native 0.85 and the new architecture.
Remove @react-navigation/* direct dependencies and route their hooks
through expo-router, which is no longer compatible with react-navigation
as of SDK 56. Fix RN 0.85's removed StyleSheet.absoluteFillObject (use
absoluteFill) and react-native-view-shot 5.x's ViewShotRef type across
call sites. Downgrade eslint-plugin-react-hooks v7's new React Compiler
era rules to warnings pending a dedicated refactor, and repair Jest
fallout from the jest-expo/reanimated/worklets/expo-media-library bumps.
Regenerate the committed ios/ and android/ native projects with
`expo prebuild --clean` (required for @bacons/apple-targets 5.x) against
the production release channel, and document that requirement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The entrypoint guard compared process.argv[1] against
new URL(import.meta.url).pathname. A URL pathname percent-encodes spaces, so
in any checkout whose absolute path contains one the comparison never matched
and main() never ran.
Every ota:preflight:*, ota:publish:*, and ota:doctor:* script then exited 0
having done absolutely nothing, which is indistinguishable from success. A
publish that silently no-ops is worse than one that fails: it reports a
shipped update that never left the machine.
Compare resolved filesystem paths instead.
Verified from a checkout whose path contains a space: before the change
`node scripts/ota-publish.mjs production --preflight` exited 0 with no
output; after it runs a real export for both platforms and prints the
preflight confirmation. Argument validation still rejects unknown targets and
unsupported EOAS arguments.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Server registration is complete: app id 9a66f3c2-fda7-416f-87e2-6db833b32e9d,
real code-signing certificate (public half), staging/production channels
mapped server-side. Drops the app-id placeholder guard (cert guard stays) and
re-includes certificate.pem past the root *.pem ignore rule, which had been
silently excluding it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Install expo-updates and the eoas CLI, and configure the client against
https://ota.apps.jbrunton.co.uk.
app.json stays the static source of truth for version, buildNumber, and
versionCode, which EAS rewrites during a release build. A new app.config.js
spreads it and layers on the updates block, because XPREM selects a release
channel from a build-time request header and that value differs per EAS
build profile.
- updates.url points at the XPREM manifest endpoint, /manifest
- runtimeVersion uses the appVersion policy, so the runtime version is the
store-visible expo.version and a native change requires a store build
- expo-channel-name comes from RELEASE_CHANNEL, set per EAS build profile:
development, staging (preview profile), and production
- expo-app-id and an empty xprem-branch header are declared at build time,
since expo-updates only sends headers that existed when the build was made
- code signing uses keyid main and rsa-v1_5-sha256 against a certificate at
certs/certificate.pem, skipped when DISABLE_CODE_SIGNING is set so a dev
client can run against the packager
The XPREM app id and the code-signing certificate are still placeholders
pending the update server deployment. The config throws a descriptive error
whenever RELEASE_CHANNEL is set while either is unresolved, so a
misconfigured build fails at config resolution rather than shipping a client
that can never validate a manifest.
The native projects are Expo CNG output and are left untouched: the
expo-updates config plugin embeds the real app id and certificate contents,
so they are regenerated as a reviewed source change once both values land.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The API has no CORS handling, so local admin development serves API calls
same-origin and forwards them to the local API process.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dare proof migration file landed on staging without a journal entry, so the
drizzle migrator would never apply it. Register it in the journal and move the
solo sealed answers migration to 0022 to keep filenames and journal order aligned.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the inline ShareableCard in SharePreviewModal with a designed
QuestionShareCard: Sauci logo + wordmark header, pack badge, and a
branded sauci.app footer pill on the captured story image. The card
deliberately never carries the couple join code - it is a private
pairing credential, not a referral link (a regression test enforces
this). Text-share fallbacks now carry a full https://sauci.app link,
and all three share paths fire a new question_shared analytics event
with the destination.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sender can require photo or audio proof when sending any dare,
catalogue or custom. The recipient captures and uploads the proof as a
new couple-scoped 'dare_proof' media kind and attaches it on submit;
the server enforces ownership, kind, couple, and MIME-type match.
The proof renders inline on the dare card for the sender to review
before confirming completion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unpaired users could only reach a waiting-room dead end and could not answer
questions until a partner joined. Responses.couple_id is now nullable so a
solo user answers immediately and banks "sealed answers" (couple_id IS NULL).
Pairing claims both members' sealed answers and computes matches for every
question both have now answered. Invite copy and the unpaired push nudge
escalate with the sealed answer count.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Give support read-only visibility into purchase history, streak locality
fields, notification preferences, the dares loop, couples quiz sessions,
invite-code lookup, and recent live-draw activity, all backed by resources
already allow-listed in the admin repository.