The TabataGo scheme only listed TabataGo + TabataGoWidget (iOS), so
xcodebuild -scheme TabataGo never compiled the watchOS app in CI. Add
TabataGoWatch + TabataGoWatchWidget to the build targets so watch-side
breakage is caught before deploy.
Note: TabataGoWidget is a real iOS widget target (defined at project.yml
line 165, app-extension/iOS), NOT a phantom — the prior review was wrong
on that point. The real gap was the missing watch targets.
Embed deps were already correct (TabataGo embeds TabataGoWatch, which
embeds TabataGoWatchWidget). Verified locally with xcodegen 2.45.4:
the generated xcscheme now lists all 4 BlueprintNames.
⚠️ Risk: if the watch target has latent build issues (never compiled in
CI before), this will surface them on the next PR. Desirable — catch
before merge, not in the field.
10 fixes to .github/workflows/pr-iphone-deploy.yml:
Cache & build hygiene:
- Stop deleting build/spm-cache (was wiped by 'rm -rf build/'); clean only
build/derived. RevenueCat (~1.1 GiB) is finally actually preserved.
- Stop nuking ~/Library/Caches/com.apple.dt.Xcode (slows every next build).
- Drop '|| true' on brew install — fail loudly if brew is down.
- Bump build-deploy timeout 20m → 30m for cold builds.
Secrets:
- New step writes Config/Secrets.xcconfig from Gitea secrets (SUPABASE_URL,
SUPABASE_ANON_KEY, REVENUECAT_API_KEY, POSTHOG_API_KEY) before xcodegen.
Preflight fails loud if a required secret is missing. Heredoc body at
column 0 (xcconfig keys are whitespace-sensitive).
LGTM/KO handling:
- Regex now matches anywhere in body with word boundary: \bLGTM\b / \bKO\b.
'Tested, LGTM!' counts; 'KOM' / 'LGTMX' / 'OK' do not.
- Re-fetch ALL comments each poll (not since_id) to catch edits.
- Squash-merge ({"Do":"squash"}) instead of merge commit.
Workflow structure:
- Add concurrency block (per-PR, cancel-in-progress) — prevents double-merge
and stacked pipelines on successive pushes.
- Remove no-op 'permissions:' block (all mutations go via Gitea API with
PR_API_TOKEN, native GITHUB_TOKEN is never used).
# 1.0.0 (2026-07-19)
### Bug Fixes
* add /opt/homebrew/bin to PATH for all steps ([44cebf8](44cebf834c))
* add 6s timeout to MusicService Supabase fetch for offline fallback ([057fbb3](057fbb3c9a))
* add DEVELOPMENT_TEAM to project.yml ([86fb683](86fb683428))
* add DEVELOPMENT_TEAM via xcodebuild CLI ([ebfaa15](ebfaa15e38))
* add HealthKit entitlement and regenerate Xcode project to resolve NSInvalidArgumentException ([9943dce](9943dce82d))
* add missing getPopularWorkouts export to data layer ([569a9e1](569a9e178f))
* add missing Workout fields to program workouts and guard against undefined level ([f11eb6b](f11eb6b9ae))
* add NSSupportsLiveActivities to main app target + cleanup info plists ([f5a36f0](f5a36f0a26))
* add shared model sources to TabataGoWidget target ([1bb6a54](1bb6a544e4))
* add TabataGo scheme to project.yml (xcodegen 2.45.4 compatibility) ([cfe8e8c](cfe8e8cd1b))
* add widget target, push entitlement, error handling, force-unwrap ([a173670](a17367081c))
* add xcodebuild -list debug, use -project flag ([fdf4b6d](fdf4b6d558))
* align bundleIdPrefix with Firebase app (com.tabatago → fr.millianlmx.tabatago) ([52346fb](52346fb4ac))
* align program detail screen with Dark Medical design system ([e0e02c4](e0e02c4550)), closes [#000](https://gitea.1000co.fr/millianlmx/tabatago/issues/000)
* change bundle ID from com.tabatago.app to fr.millianlmx.tabatago ([6cc2530](6cc25300d1))
* **ci:** add -derivedDataPath and target find to build/derived — compatible with -scheme ([3ca3e54](3ca3e54837))
* **ci:** add mkdir -p build to Package IPA step ([d614e72](d614e72031))
* **ci:** add sudo back to xcode-select — must be run as root ([595140e](595140e2f9))
* **ci:** checkout PR branch instead of main ([5079de0](5079de0fb1))
* **ci:** clean SPM caches and DerivedData on CI runner to remove stale PostHog resolution ([00e02b7](00e02b7166))
* **ci:** clean SPM/Xcode caches agressivement pour éviter les segfaults linker après changement de bundle ID ([7c50282](7c50282284))
* **ci:** correct derived data path in install step ([7d9a89e](7d9a89e637))
* **ci:** disable code signing — Mac runner has no Apple Developer account ([4b75485](4b7548501a))
* **ci:** eliminate pipeline bug preventing ios-deploy fallback ([b956e51](b956e5189b))
* **ci:** isolate SPM and DerivedData to prevent cache corruption ([f14186a](f14186aeab))
* **ci:** keep SPM cache — remove aggressive clean that forces RevenueCat re-clone ([069b7d4](069b7d417f))
* **ci:** remove -derivedDataPath and -clonedSourcePackagesDirPath incompatible with -target ([8ce3833](8ce38332b8))
* **ci:** remove -derivedDataPath from resolve step (requires -scheme) ([abf926e](abf926e4bc))
* **ci:** remove grep -v DerivedData from Package IPA step — app builds to DerivedData ([4e2e807](4e2e807fd9))
* **ci:** remove stale .xcodeproj from git, nuke before xcodegen to prevent PostHog ghost dependency ([c45e85e](c45e85edb6))
* **ci:** remove sudo from docker install — Gitea ubuntu runner is root without sudo ([46d9566](46d95661ce))
* **ci:** remove sudo from Setup Xcode step to prevent password prompt hang ([9739a73](9739a739f5))
* **ci:** restore automatic code signing — Apple account now configured on runner ([63fbae3](63fbae3698))
* **ci:** revert build command from -scheme TabataGo to -target TabataGo ([3202ea9](3202ea9b5b)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* **ci:** switch from -target to -scheme for Xcode 26 SPM module path resolution ([b48178f](b48178f167))
* **ci:** switch from Firebase OTA to Xcode WiFi/USB direct deploy ([97b61d3](97b61d3afb))
* **ci:** switch to --token auth and add --project flag for Firebase deploy ([09e77b4](09e77b477c))
* **ci:** use npx firebase to avoid PATH issues with npm global install ([5c73d6b](5c73d6bf29))
* **ci:** use xcodebuild -version instead of xcode-select, clean only runner caches ([2aa312a](2aa312ab61))
* **ci:** wrap deploy commands in if blocks for -e protection ([b57584f](b57584f890))
* clean SPM cache, disable explicit modules ([dde01ce](dde01ce957))
* complete rewrite — add git clone checkout, install node+npm, fix all paths ([1b3ce24](1b3ce24fc9))
* dismiss paywall and sync premium state after successful purchase ([9f15ae2](9f15ae2d79))
* Dynamic Island, Live Activity errors, background audio stop, CFBundleVersion ([40ffb04](40ffb04f31))
* gitea→github context, add ios-deploy, remove -k ([7a02a89](7a02a8949e))
* kill stale Package.resolved, pin PostHog exact, add module emission flags ([7fb4419](7fb44198e7))
* Live Activity concurrency and state observation ([71de3c0](71de3c0aa7))
* Live Activity persists after workout cancel/background ([e42c121](e42c1217db))
* move #if outside array literal in TabataGoComplication.supportedFamilies ([a382ad6](a382ad62dc)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* move Live Activity ownership to ViewModel, fix timer-at-0 and background freeze ([c715c79](c715c797f9))
* permanently fix PostHog module emission error ([5817710](58177102a4)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* remove actions/checkout@v4 (no Node.js on Mac runner) ([8f3979c](8f3979c8a0))
* remove arch -arm64 from xcodegen (runner already handles it) ([7464357](7464357787))
* remove info.properties from widget target — use on-disk Info.plist with NSSupportsLiveActivities ([8073614](807361449b))
* remove PostHog from project.yml — EmitSwiftModule crashes on Xcode 26 ([afc161c](afc161c8ce))
* remove remaining force-unwrap in widgetURL (AGENTS.md §7) ([ee01380](ee01380be8))
* rename GITEA_TOKEN to PR_API_TOKEN (GITEA_ prefix reserved) ([e43f197](e43f197139))
* replace brew with npm for firebase-tools, use arch -arm64 for xcodegen ([1d4d43f](1d4d43f3f2))
* resolve all 228 TypeScript errors across the project ([4458044](4458044d0e))
* resolve redirect loop after login ([d2babbe](d2babbeee3))
* resolve trainers page issues and add seed data ([42d9b26](42d9b2671b))
* restore UIBackgroundModes audio for background playback ([316dab2](316dab2f9c))
* scope shared model sources to TabataGoWidget only ([210c254](210c254298))
* support both EXPO_PUBLIC and NEXT_PUBLIC env vars ([4c5bcc4](4c5bcc41c5))
* **test:** resolve unhandled rejection in sidebar logout error test ([20f0189](20f01899df))
* tolerate brew post-install warning exit code ([96654d6](96654d61c7))
* update PostHog to 3.62.0 to fix SwiftDriver compilation error ([5b67aea](5b67aea7ce))
* update workflow references to .github/ and gitignore xcuserstate ([560b056](560b056dc9))
* use canImport(WatchKit) guard for accessoryCorner, use -scheme in CI build ([60ac624](60ac624487)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if) [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if) [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* use createBrowserClient for proper cookie-based auth ([554ad2a](554ad2a352))
* use full brew path with arch -arm64 for Rosetta runner ([f5a777a](f5a777aee8))
* use full path for npm ([da62d6f](da62d6fa58))
* use xcodebuild build + manual ipa packaging (no scheme needed) ([a48ee00](a48ee0007a))
* **widget:** add NSExtension to info.properties for xcodegen compat ([309d67c](309d67c88a))
* wrap accessoryCorner in #if os(watchOS) for Xcode 26 compat ([a6ea4ca](a6ea4ca904)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* **youtube-worker:** sync package-lock.json with @google/genai dep and harden Dockerfile ([a4c6a12](a4c6a123a0))
### Features
* 5 tab screens wired to centralized data layer ([99d8fba](99d8fba852))
* add admin-web Docker build & push workflow with semantic-release ([0166398](0166398ae1))
* add dependencies for i18n, notifications, purchases, and analytics ([6a94d54](6a94d545f2))
* add form input components with tests ([3df7dd4](3df7dd4a47))
* add media upload component with comprehensive tests ([6adf709](6adf709dce))
* add reusable UI components with tests ([71e9a9b](71e9a9bdb5))
* add shared card components ([001b376](001b376fc0))
* add storage utilities for file management ([e2e9988](e2e99887ac))
* add success toasts for create operations and login ([fc43f73](fc43f73b82))
* add workout form component with advanced test mocking ([9dd1a4f](9dd1a4fe7c))
* add workout management pages ([bd14922](bd14922efa))
* **admin-web, functions:** overhaul music library and add AI genre classification ([edcd857](edcd857c70))
* Apple Watch app + Paywall + Privacy Policy + rebranding ([2ad7ae3](2ad7ae3a34))
* category/collection detail screens + Inter font loading ([2d24831](2d24831f8e))
* close v1 feature gaps — freemium gating, video/audio infrastructure, EAS build config ([a042c34](a042c348c1))
* data layer with types, 50 workouts, and Zustand stores ([5477ecb](5477ecb852))
* design system v2 with liquid glass aesthetic ([511e207](511e207762))
* Dynamic Island pause state, Apple-aligned spacing, and UI polish ([95f34e6](95f34e6471))
* explore tab, React Query data layer, programs, sync, analytics, testing infrastructure ([cd065d0](cd065d07c3))
* extend user profile with onboarding data model ([b600833](b60083341e))
* i18n infrastructure with 4-locale support (en, fr, es, de) ([d6bc7f5](d6bc7f5a4c))
* i18n locale files for all screens (en, fr, es, de) ([e59c87f](e59c87fd1b))
* **i18n:** complete internationalization for iOS + watchOS across all views ([0f5b7b9](0f5b7b9e18))
* implement beautiful toast notifications with Sonner ([3d026b6](3d026b68ee))
* implement full authentication system with middleware protection ([e0057e1](e0057e18e0))
* implement React Query for Supabase data fetching ([b1741e9](b1741e965c))
* integrate theme and i18n across all screens ([f807980](f80798069b))
* Live Activity accessibility and supplemental families (small/medium) ([fe005ee](fe005ee7f3))
* migrate icons to SF Symbols, refactor explore tab, add collections/programs data layer ([b833198](b833198e9d))
* move HealthKit permission to onboarding, remove HR write ([310124a](310124ad63))
* notification, purchase, and analytics services ([540bb01](540bb015c7))
* onboarding flow (6 screens) + audio engine + design system ([fa189fe](fa189fe72e))
* onboarding flow with staggered-reveal wow screen ([aa75afb](aa75afb1b7))
* production-grade Live Activity with type-safe phases, decomposed views, previews, and alert transitions ([dc3ff15](dc3ff15e81)), closes [#Preview](https://gitea.1000co.fr/millianlmx/tabatago/issues/Preview) [#Preview](https://gitea.1000co.fr/millianlmx/tabatago/issues/Preview)
* redesign Dynamic Island with phase-driven UI and animations ([c152c22](c152c22ffb))
* redesign player with Dynamic Island, compact timer, and fix Live Activity timer drift ([b0d364e](b0d364eca2))
* replace native confirm() dialogs with custom delete confirmation dialogs ([b177656](b177656efc))
* shared components, hooks, and audio engine ([13faf21](13faf21b8d))
* switch to Firebase OTA deployment, drop ios-deploy ([9b06c0a](9b06c0ae5c))
* system light/dark theme infrastructure ([f17125e](f17125e231))
* timer engine + full-screen timer UI ([31bdb15](31bdb1586f))
* update Home screen to use React Query with loading states ([1973241](197324188c))
* update mobile app screens ([8c8dbeb](8c8dbebd17))
* workout flow — detail, player, and complete screens ([b0521de](b0521ded5a))
* YouTube music download system with admin dashboard ([3d8d9ef](3d8d9efd70))
The 'should handle logout errors gracefully' test mocked supabase.auth.signOut
to reject (throw), which does not match real supabase-js behaviour (signOut
resolves with { error }, never throws). The rejecting mock caused an unhandled
promise rejection in the async handleLogout onClick handler: tolerated locally
(vitest exit 0) but failing in CI (npx vitest run exits 1).
Switch to a realistic resolved-with-error mock and assert the user is still
redirected to /login (true graceful handling). No production code change.
- admin-web-docker.yml: add 'test' job (Node 22, npm ci, tsc --noEmit,
vitest run) gated by the anti-[skip ci] guard, wire needs: [test] on
build-validation and semantic-release, and needs: [semantic-release, test]
on build-and-push. Tests now run as a gate before Docker build validation
on PRs and before semantic-release on main.
- ci.yml: drop the redundant admin-web-test job (now covered by the test
job in admin-web-docker.yml on PRs), remove the admin-web path filter
and output from the changes job, simplify deploy-functions.needs.
- scripts/verify-admin-web-docker.sh: update check_workflow_jobs to expect
4 jobs (test + build-validation + semantic-release + build-and-push) and
check_workflow_needs to expect build-and-push needs [semantic-release, test]
so the verify script stays green with the new job topology.
- vitest.config.ts: add explicit exclude (e2e/**, playwright-report/**,
test-results/**) spread with configDefaults.exclude to keep
node_modules/dist filtering intact
- app/page.test.tsx: waitFor for async stats resolution, replace
getAllByRole('article') with 'a [data-slot=card]' scoped queries
(Getting Started card has no icon so we scope to the 3 stat cards inside
<Link>), drop console.error assertion (production fetchStats destructures
{ count } from the resolved payload without throwing on
{ count: null, error }, so console.error is never called on that path)
- components/workout-form.test.tsx: mock sonner (toast.error/success with
all methods stubbed) to replace obsolete window.alert assertions, wait for
the custom Select trainer dropdown to mount before clicking, fill the
exercise-name-0 input (default exercises state is [{name:""}] which fails
validation), make insert/update mocks resolve on a macrotask so React can
render the transient 'Saving...' loading state
- app/login/page.test.tsx: replace getByRole('heading') with getByText
(shadcn CardTitle renders a <div>, not a semantic heading), extend sonner
mock with toast.error, fix invalid-credentials mock to return a real
Error instance (production supabase-js returns AuthError extends Error,
so the err instanceof Error branch surfaces the real message)
- components/ui/dialog.test.tsx: skip jsdom-incompatible click-outside test
(Radix Dialog closes via pointerDown on overlay, not reproducible in jsdom;
covered by the Escape test + Playwright E2E), replace aria-modal assertion
with role=dialog + aria-labelledby (modern Radix no longer emits aria-modal)
Scope: tests + config only. No production code change.
- Restaure UIBackgroundModes[audio] dans Info.plist (supprimé par erreur
au commit précédent) → empêche iOS de suspendre l'app en arrière-plan
- Ajoute UIBackgroundModes dans project.yml pour robustesse
- Ajout de NSSupportsLiveActivities et NSSupportsLiveActivitiesFrequentUpdates
dans info.properties du target TabataGo (project.yml) — corrige l'erreur
'Target does not include NSSupportsLiveActivities plist key' au runtime
- Nettoie Info.plist (retire CFBundleURLTypes, UIBackgroundModes redondants)
- Aligne les entitlements (retire aps-environment, ajoute app groups)
- Corrige le bundle version du widget (2 → 1)
xcodegen parses info.path plist and generates INFOPLIST_KEY_* settings,
but silently drops nested dictionaries like NSExtension. Adding it to
info.properties ensures xcodegen produces:
INFOPLIST_KEY_NSExtension_NSExtensionPointIdentifier = widgetkit-extension
which Xcode preserves at build time.
Bash -e doesn't just kill on pipefail — it exits on ANY non-zero
command. Even without a pipeline, both xcrun devicectl and ios-deploy
return non-zero on failure, killing the script before the exit-code
check.
Fix: wrap both in 'if command; then exit 0; fi' so -e treats them
as condition-tested (no early exit). Fallback now actually runs.
When devicectl fails under bash -e -o pipefail, the pipeline
(xcrun devicectl ... | tail -3) exits non-zero and -e kills
the script before the if-check. The ios-deploy USB fallback
was dead code.
Fix: redirect to temp file, capture exit code explicitly.
No pipeline = pipefail has no side-channel effect.
Previous sed command accidentally injected shared model files into
TabataGo, TabataGoWatch, and TabataGoWatchWidget targets, corrupting
their YAML structure. Restored all targets; shared files now correctly
scoped to TabataGoWidget target only.
Widget extension references WorkoutPhase, WorkoutActivityAttributes,
TogglePauseIntent (WorkoutLiveActivity) and MusicActivityAttributes
(MusicLiveActivity) but cannot import parent app module. Shared source
files must be compiled in both targets, following existing pattern
of WatchConnectivityTypes.swift shared between iOS/watch.
Update AGENTS.md and docs/ci-cd-setup.md from .gitea/workflows to .github/workflows
Add *.xcuserstate to .gitignore
Remove tracked UserInterfaceState.xcuserstate from git (preserve on disk)
STOPGAP: CODE_SIGN_IDENTITY="-" CODE_SIGNING_REQUIRED=NO to bypass
signing on the Mac CI runner which has no Apple account signed into Xcode.
REAL FIX: Sign into Xcode → Settings → Accounts with the Apple Developer
account on the Mac runner machine. Then -allowProvisioningUpdates +
DEVELOPMENT_TEAM will auto-provision profiles for any bundle IDs.
- Supprime TOUT DerivedData (pas seulement TabataGo-*) car le changement
de bundle ID (com.tabatago.app → fr.millianlmx.tabatago) a rendu les
anciens caches SPM incohérents
- Ajoute la suppression de ~/Library/org.swift.swiftpm, ~/.swiftpm,
~/Library/Caches/com.apple.dt.Xcode et du ModuleCache
- Supprime les build/ locaux et .build avant xcodegen generate
- Supprime les Package.resolved stale
- Utilise -derivedDataPath et -clonedSourcePackagesDirPath cohérents
entre la résolution SPM et le build