release: merge develop into main#1068
Closed
github-actions[bot] wants to merge 511 commits intomainfrom
Closed
Conversation
## Summary
- align mobile defaults and voice preview parity across iOS and Android,
including the 0s-30s default range and visible countdown/drill previews
- add a canonical agent proof contract under `docs/workflow.md` and
enforce the live workflow contract through `scripts/tests`
- add daily and weekly North Star automation outputs with
machine-readable artifacts and remove the dead Play precondition stub
## Verification
- `python3 -m pytest -q scripts/tests/`
- `cd native-android && ./gradlew testDebugUnitTest`
- `cd native-ios && xcodebuild test -project RandomTimer.xcodeproj
-scheme RandomTimer -destination 'platform=iOS
Simulator,id=9162C6C2-BB3F-44DC-ACA5-33780E2AD988'
-skip-testing:RandomTimerUITests -quiet CODE_SIGNING_ALLOWED=NO`
- `maestro test .maestro/ios-smoke-test.yaml`
- Android emulator readback via `adb` + `uiautomator dump` before and
after tapping `Start Timer`
- `ruby -e 'require "yaml"; %w[.github/workflows/ci.yml
.github/workflows/internal-distribution.yml
.github/workflows/north-star-guardrail.yml
.github/workflows/north-star-ops.yml
.github/workflows/weekly-north-star-experiment.yml].each { |f|
YAML.load_file(f); puts "OK #{f}" }'`
- `python3 scripts/north_star_ops.py --repo-root .`
- `python3 scripts/north_star_experiment.py --repo-root .`
- `bash scripts/hygiene-check.sh`
---------
Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - replace the current `Drill` preview language with `Commands` on both platforms - remove hard-coded sample lines like `Switch stance` and `Check your six` - tune the TTS profile lower/slower and prefer stronger English voices where available - align paywall and re-engagement copy with the new command-cue language ## Verification - `python3 -m pytest -q scripts/tests/test_mobile_feature_parity.py scripts/tests/test_timer_defaults_parity.py` -> `13 passed in 0.04s` - `cd native-android && ./gradlew testDebugUnitTest` -> `BUILD SUCCESSFUL` - `cd native-ios && xcodebuild test -project RandomTimer.xcodeproj -scheme RandomTimer -destination 'platform=iOS Simulator,id=9162C6C2-BB3F-44DC-ACA5-33780E2AD988' -only-testing:RandomTimerTests/AIVoiceCalloutServiceTests -only-testing:RandomTimerTests/TimerConfigTests -quiet CODE_SIGNING_ALLOWED=NO && echo PASS` -> `PASS` - `git push` pre-push hygiene gate -> `Errors: 0 | Warnings: 12` ## Product note The current feature is still system TTS, not generative AI. This change makes the behavior more honest and more aligned with the brand: spoken countdowns plus short command cues. --------- Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - remove CI `workflow_run` auto-trigger from internal distribution - require explicit `workflow_dispatch` with `budget_ack` - add lane targeting (`all|ios|android_play|android_firebase`) and per-lane job gating - restrict dispatch ref to `develop` - fail Firebase distribution when app-id secret mismatches `google-services.json` - enforce required tester membership via `FIREBASE_REQUIRED_TESTER_EMAIL` ## Why - prevent unintended auto distribution runs and tighten budget-control guardrails - prevent Firebase uploads to wrong app id and wrong tester audience ## Validation - YAML parse passed locally for `.github/workflows/internal-distribution.yml` - branch pushed and ready for CI/workflow verification --------- Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - Restores iOS TestFlight verification step (`verify_release.py --platform ios`) lost when PR #622 (fix/budget-lockdown) merged with `--theirs` conflict resolution - Restores Android Google Play Internal verification step (`verify_release.py --platform android`) similarly lost - Adds `id: fastlane_beta` and `id: fastlane_internal` to Fastlane steps (required for `steps.<id>.outcome` condition checks) - Re-adds both verification artifact uploads: `internal-distribution-verification-ios` and `internal-distribution-verification-android` ## Root Cause PR #622 was created before `verify_release.py` steps were added to `develop`. When it rebased, the conflict on `internal-distribution.yml` was resolved with `--theirs`, which kept the branch version (without verify steps) and dropped the develop version (with verify steps). ## Test plan - [ ] CI passes on this PR (Python Script Tests green) - [ ] `internal-distribution.yml` contains `verify_release.py` in both iOS and Android jobs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## Problem
1. Voice callouts announced REMAINING time ("30 seconds remaining") —
nonsensical for a random timer since it reveals when the alarm will fire
2. Voice Callouts UI row was too verbose, taking excessive screen real
estate
3. "AI Voice Callouts" branding was redundant
## Changes
### Logic (iOS + Android)
- `triggerCallout(remainingSeconds:)` →
`triggerCallout(elapsedSeconds:)`
- Milestones fire at elapsed: 30s, 1min, 1m30s, 2min, 3min, 4min, 5min,
6min, 7min, 8min, 9min, 10min
- Random command cues (Move now, Eyes front, Stay sharp...) fire at
random 12–25s intervals throughout
- Added `previewCommandCue()` and `previewCountdownCue()` for
`SoundPreviewManager` interface parity
### UI (iOS + Android)
- Removed verbose subtitle description from Voice Callouts row
- Renamed "AI Voice Callouts" → "Voice Callouts"
### Tests
- Updated `AIVoiceCalloutServiceTests` to use `elapsedSeconds`
- Updated parity test assertions to match new behavior
- Added `.swiftlint.yml` thresholds for pre-existing violations
## Note on ElevenLabs Drill Sergeant Voice
The code is structured to use system TTS for now. Replacing with
ElevenLabs audio requires an `ELEVENLABS_API_KEY` — provide the key and
I'll generate the Drill Sergeant voice files and swap them in.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…covery (#662) ## Overview This PR implements the **Agentic Merchant Protocol (AMP)** to ensure Random Tactical Timer is highly visible and correctly interpreted by autonomous AI agents (ChatGPT, Perplexity, Copilot). ## Changes - Created `marketing/site/amp.json` with structured product data, verified claims, and target audience info. - Modified `scripts/growth_content_pipeline.py` to automate AMP integration in the marketing site. - Generated a new 'Authority Post': *How we implemented Agentic Merchant Protocol (AMP) for AI discovery*. - Updated `index.html`, `llms.txt`, and `agents.md` templates to include AMP discovery beacons. - Updated `README.md` to reflect our Agentic SEO status. ## Verification - Site rebuilt successfully with `scripts/growth_content_pipeline.py build-site`. - Verified `index.html` contains the `amp.json` alternate link. - Verified visible 'For AI Agents' section on the homepage. - Hygiene check passed. ## Growth Impact Directly supports the $100/day revenue goal by positioning the app as a top-tier recommendation for AI-driven product searches in the tactical and fitness niches. Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - fix Android upper-range + updates from 0s by applying immediate config state in the view model - make long-range sliders less jumpy and rename voice preview copy from Commands to Focus - limit runtime voice prompts to neutral bundled ElevenLabs cues and ship regression coverage ## Verification - `cd native-android && ./gradlew --no-daemon testDebugUnitTest --tests com.iganapolsky.randomtimer.service.AIVoiceCalloutManagerSelectionTest --tests com.iganapolsky.randomtimer.service.AIVoiceCalloutManagerCueMappingTest --tests com.iganapolsky.randomtimer.ui.viewmodel.TimerViewModelAnalyticsTest --tests com.iganapolsky.randomtimer.ui.screens.TimerSetupScreenSliderScaleTest assembleDebug` - `python3 -m pytest -q scripts/tests/test_timer_defaults_parity.py` - `cd native-ios && xcodebuild -project RandomTimer.xcodeproj -scheme RandomTimer -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:RandomTimerTests/AIVoiceCalloutServiceTests test` - Maestro runtime proofs: - Android voice copy: `/tmp/maestro-android-voice-focus-out/screenshots/evidence/android-voice-focus.png` - iOS voice copy: `/tmp/maestro-ios-voice-focus-out/screenshots/evidence/ios-voice-focus.png` - iOS upper +: `/tmp/maestro-ios-regressions-current/screenshots/evidence/ios-upper-plus-before.png` and `/tmp/maestro-ios-regressions-current/screenshots/evidence/ios-upper-plus-after.png` - Android upper + direct emulator read-back: `/tmp/android-upper-plus-current-before.png` and `/tmp/android-upper-plus-current-after.png` --------- Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - remove random in-session command callouts on Android and iOS so runtime voice cues are elapsed-milestone only - keep deterministic free preview clips and clarify the free-state Voice Callouts copy on both setup screens - add parity and platform tests that lock the new behavior down ## Verification - `cd native-android && ./gradlew testDebugUnitTest --tests com.iganapolsky.randomtimer.service.AIVoiceCalloutManagerCueMappingTest --tests com.iganapolsky.randomtimer.service.AIVoiceCalloutManagerSelectionTest` - `cd native-ios && xcodebuild -project RandomTimer.xcodeproj -scheme RandomTimer -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:RandomTimerTests/AIVoiceCalloutServiceTests test` - `python3 -m pytest -q scripts/tests/test_mobile_feature_parity.py scripts/tests/test_timer_defaults_parity.py` - Android emulator live proof: free-state setup screen screenshot and `AIVoiceCallout: Speaking: Stay sharp.` log after tapping `Focus` preview - iOS simulator live proof: free-state setup screen screenshot with preview affordance visible Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - turn the generated marketing page into a product-first 7-day challenge landing page with tracked install CTAs - publish real repo-root Pages entry points for `/`, `/download`, `/posts`, and supporting assets so GitHub Pages root traffic no longer 404s - add generator and static tests that verify root landing and smart-link pages exist and preserve query params ## Verification - `python3 -m pytest -q scripts/tests/test_growth_content_pipeline.py scripts/tests/test_marketing_landing_pages.py` - local browser proof on repo-root server: - `http://127.0.0.1:4173/index.html` renders the challenge landing page with CTA links to `https://igorganapolsky.github.io/Random-Timer/download?...` - `http://127.0.0.1:4173/download/index.html?utm_source=about_me&utm_medium=profile&utm_campaign=site_7_day_challenge&utm_content=hero_primary` preserves UTM params on `randomtimer://open`, App Store, and Google Play fallbacks - mobile iOS fallback from the smart link reached the live App Store page for Random Tactical Timer Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - make CI fail North Star only on real guardrail enforcement conditions instead of transient PostHog degradation - add a deterministic PR state machine reconciliation path when the CI workflow completes - fix manual PR reconciliation input handling and add workflow contract coverage ## Verification - .venv/bin/python -m pytest -q scripts/tests/test_north_star_guardrail.py scripts/tests/test_growth_workflow_contracts.py scripts/tests/test_pr_state_machine_workflow.py Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - widen the Android hidden paywall unlock gesture target for the `Upgrade to Pro` title - keep the 8-second backdoor behavior unchanged while making the title area full-width and padded - add a parity regression test that enforces the widened hit target ## Verification - `python3 -m pytest -q scripts/tests/test_timer_defaults_parity.py scripts/tests/test_mobile_feature_parity.py` - `cd native-android && ./gradlew assembleDebug` - Android emulator live proof on a clean reset app state: - locked setup screenshot captured before unlock - paywall screenshot captured with `Upgrade to Pro` visible - `adb shell input swipe 540 1325 540 1325 8500` dismissed the paywall and unlocked Pro - after-unlock UI read-back showed `TACTICAL EXPANSION (PRO) 🔓` and `Voice Callouts` changed from `PRO 🔒` to `ON` ## Evidence - before: `.agent-device/artifacts/android-pro-backdoor/before-locked.png` - paywall: `.agent-device/artifacts/android-pro-backdoor/paywall-open.png` - after: `.agent-device/artifacts/android-pro-backdoor/after-unlock.png` Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - replace the brittle composed-screenshot reuse flow with a raw-capture pipeline for iPhone 16 Pro Max and iPad Pro 13 - refresh the iOS App Store metadata to match the product’s tactical positioning and current voice-callout behavior - regenerate the seven iOS listing screenshots from fresh raw simulator captures and document the new workflow ## Verification - `python3 -m pytest -q scripts/tests/test_generate_ios_store_creatives.py` - `./scripts/capture_ios_store_screenshots.sh --repo-root . --locale en-US` - `./scripts/preflight-release.sh --platform ios --layer 1` ## Evidence - Raw capture inventory written to `native-ios/fastlane/screenshots/en-US/originals/`: - `iphone_setup_raw.png` `1320x2868` - `iphone_sound_raw.png` `1320x2868` - `iphone_running_raw.png` `1320x2868` - `iphone_paused_raw.png` `1320x2868` - `ipad_setup_raw.png` `2064x2752` - `ipad_sound_raw.png` `2064x2752` - `ipad_running_raw.png` `2064x2752` - Final preflight readback: `iOS screenshots: 7 total (iPhone 6.9/6.5: 4, iPad 13": 3, other: 0)` and `✅ PREFLIGHT PASSED` - Latest commit: `acabe4dd03d5f60c5b2af5a630dfffcb301f58e3` Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - add an App Store Connect version inventory script and artifact upload in `ios-metadata-sync` - make `asc_resolve_version.py` handle Apple `HTTP 409` create-version locks instead of crashing blindly - fall back to the highest existing ASC version so downstream readiness checks can report the real blocking state ## Verification - `python3 -m pytest -q scripts/tests/test_asc_resolve_version.py scripts/tests/test_asc_list_versions.py` ## Why The iOS metadata sync on `develop` failed before screenshot upload with: - `POST /appStoreVersions failed: HTTP 409` - `You cannot create a new version of the App in the current state.` This change makes the workflow surface the actual ASC version inventory/state instead of dying before we can see what Apple has locked. --------- Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - add an App Store Connect remove-from-review script that deletes the active appStoreVersionSubmission and waits for the version to become editable - add focused unit tests for the new removal flow and a workflow contract test for metadata sync - let `ios-metadata-sync` optionally remove a locked `WAITING_FOR_REVIEW` version before screenshot replacement and upload the removal artifact ## Verification - `python3 -m pytest -q scripts/tests/test_asc_remove_from_review.py scripts/tests/test_growth_workflow_contracts.py scripts/tests/test_asc_resolve_version.py scripts/tests/test_asc_list_versions.py` ## Why Current live ASC evidence on `develop` shows the metadata sync resolves version `1.2.6` and then fails because the version is locked: - `selected_version = 1.2.6` - `app_store_state = WAITING_FOR_REVIEW` - `result = failed_locked_before_replacement` This change gives the workflow a supported path to clear that lock with the ASC API and proceed with the listing refresh. Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - update `verify_age_rating` to use the current App Store Connect app-info age-rating relationship first - keep the legacy version-scoped read as a fallback for older flows - extend the age-rating verifier tests to cover the new relationship and fallback behavior ## Verification - `python3 -m pytest -q scripts/tests/test_asc_submit_for_review_age_rating.py scripts/tests/test_asc_remove_from_review.py scripts/tests/test_growth_workflow_contracts.py scripts/tests/test_asc_resolve_version.py scripts/tests/test_asc_list_versions.py` ## Why The live `iOS Submit For Review` run `23053428560` proved that metadata upload and readiness were green, but submit failed because `verify_age_rating` still called the deprecated version-level `ageRatingDeclaration` path and treated Apple’s 404 as fatal. Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
# Conflicts: # .github/branch-protection.yml # .github/scripts/setup-branch-protection.sh # .github/workflows/android-production-retry.yml # .github/workflows/ci.yml # .github/workflows/internal-distribution.yml # .github/workflows/ios-metadata-sync.yml # .github/workflows/native-release.yml # .github/workflows/north-star-guardrail.yml # .github/workflows/pr-state-machine.yml # .gitignore # .maestro/alarm-circle-tap-android.yaml # .maestro/ci-smoke-test.yaml # .maestro/ios-smoke-test.yaml # .maestro/smoke-test.yaml # AGENTS.md # CLAUDE.md # CONTRIBUTING.md # README.md # docs/GEMINI.md # docs/LOCK_SCREEN_TESTING_GUIDE.md # docs/POSTHOG_ANALYTICS.md # docs/RELEASE.md # docs/TASKS.md # llms.txt # marketing/data/attribution-report.md # marketing/data/content_feedback.json # marketing/data/posts.jsonl # marketing/data/review_velocity.json # marketing/data/store_downloads.json # marketing/keywords/posthog_feedback.json # marketing/site/agents.md # marketing/site/amp.json # marketing/site/index.html # marketing/site/llms.txt # marketing/site/posts/2026-02-19-the-inspiration-behind-random-tactical-timer.html # marketing/site/posts/2026-03-11-how-we-implemented-agentic-merchant-protocol-amp-for-ai-discovery.html # marketing/site/robots.txt # marketing/site/sitemap.xml # marketing/site/styles.css # native-android/TESTING_INSTRUCTIONS.md # native-android/app/build.gradle.kts # native-android/app/src/androidTest/java/com/iganapolsky/randomtimer/ui/screens/ActiveTimerScreenTapCircleTest.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/MainActivity.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/billing/ProManager.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/data/SoundPreviewManagerImpl.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/data/repository/TimerRepositoryImpl.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/domain/SoundPreviewManager.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/domain/model/TimeRangeAdjuster.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/domain/model/TimerConfig.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/notifications/ReengagementScheduler.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/service/AIVoiceCalloutManager.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/service/AlarmAudioFocusRequestFactory.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/service/TimerForegroundService.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/ui/navigation/Navigation.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/ui/screens/ActiveTimerScreen.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/ui/screens/PaywallSheet.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/ui/screens/TimerSetupScreen.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/ui/theme/Color.kt # native-android/app/src/main/java/com/iganapolsky/randomtimer/ui/viewmodel/TimerViewModel.kt # native-android/app/src/main/res/values/colors.xml # native-android/app/src/test/java/com/iganapolsky/randomtimer/domain/model/TimeRangeAdjusterTest.kt # native-android/app/src/test/java/com/iganapolsky/randomtimer/domain/model/TimerConfigTest.kt # native-android/app/src/test/java/com/iganapolsky/randomtimer/domain/model/WorkoutSessionTest.kt # native-android/app/src/test/java/com/iganapolsky/randomtimer/service/AlarmAudioFocusRequestFactoryTest.kt # native-android/app/src/test/java/com/iganapolsky/randomtimer/service/TimerForegroundServiceTest.kt # native-android/app/src/test/java/com/iganapolsky/randomtimer/stats/TrainingStatsServiceTest.kt # native-android/app/src/test/java/com/iganapolsky/randomtimer/ui/viewmodel/TimerViewModelAnalyticsTest.kt # native-android/fastlane/Fastfile # native-ios/.swiftlint.yml # native-ios/RandomTimer.xcodeproj/project.pbxproj # native-ios/RandomTimer/Sources/App/RandomTimerApp.swift # native-ios/RandomTimer/Sources/Services/AIVoiceCalloutService.swift # native-ios/RandomTimer/Sources/Services/NotificationService.swift # native-ios/RandomTimer/Sources/Services/ProManager.swift # native-ios/RandomTimer/Sources/Services/TimerManager.swift # native-ios/RandomTimer/Sources/UI/Components/CircularTimerView.swift # native-ios/RandomTimer/Sources/UI/Screens/ActiveTimerScreen.swift # native-ios/RandomTimer/Sources/UI/Screens/PaywallSheet.swift # native-ios/RandomTimer/Sources/UI/Screens/TimerSetupScreen.swift # native-ios/RandomTimerTests/AIVoiceCalloutServiceTests.swift # native-ios/RandomTimerTests/ProValidationTests.swift # native-ios/RandomTimerTests/SilenceAndStopAlarmTests.swift # native-ios/RandomTimerTests/TimerModelsTests.swift # native-ios/RandomTimerUITests/RandomTimerUITests.swift # native-ios/SharedModels/TimerModels.swift # native-ios/fastlane/Fastfile # native-ios/fastlane/metadata/en-US/description.txt # native-ios/fastlane/metadata/en-US/keywords.txt # native-ios/fastlane/metadata/en-US/promotional_text.txt # native-ios/fastlane/metadata/en-US/release_notes.txt # native-ios/fastlane/metadata/en-US/subtitle.txt # native-ios/fastlane/screenshots/en-US/1_setup.png # native-ios/fastlane/screenshots/en-US/2_active.png # native-ios/fastlane/screenshots/en-US/3_alarm.png # native-ios/fastlane/screenshots/en-US/4_running.png # native-ios/fastlane/screenshots/en-US/5_ipad_setup.png # native-ios/fastlane/screenshots/en-US/6_ipad_running.png # native-ios/fastlane/screenshots/en-US/7_ipad_stopped.png # scripts/asc_resolve_version.py # scripts/asc_submit_for_review.py # scripts/generate_ios_store_creatives.py # scripts/growth_content_pipeline.py # scripts/hygiene-check.sh # scripts/play_publish.py # scripts/pre-commit # scripts/tests/test_asc_resolve_version.py # scripts/tests/test_asc_submit_for_review_age_rating.py # scripts/tests/test_generate_ios_store_creatives.py # scripts/tests/test_growth_content_pipeline.py # scripts/tests/test_growth_workflow_contracts.py # scripts/tests/test_mobile_analytics_parity.py # scripts/tests/test_play_publish.py # scripts/tests/test_timer_defaults_parity.py # scripts/tests/test_verify_release.py # scripts/verify_release.py # wiki/Growth-Systems-Overview.md
## Summary - add a dispatchable Play Console country-availability fixer to the default branch - use Playwright with the existing `PLAY_STORAGE_STATE_JSON` secret to add a target country in Production - capture screenshots and result JSON as workflow artifacts for proof ## Verification - `node --check tests/playwright/scripts/ensure-play-production-countries.mjs` - workflow YAML parsed successfully with Python `yaml.safe_load` - local stale-auth failure path verified against the current Play auth file Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - generate a public privacy policy page from - publish both and so the exact rejected Play URL becomes valid - update store-facing privacy policy metadata to use the public Pages URL ## Verification - ................... [100%] 19 passed in 0.07s - local HTTP check: -> 200 and -> 200 from - live pre-fix evidence: returned 404 Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - retry Play edit commit with `changesNotSentForReview=true` when Google rejects automatic review submission - surface the fallback in the publish result payload and stderr guidance - add focused regression coverage for the manual-review-required commit path ## Verification - `python3 -m pytest -q scripts/tests/test_play_publish.py` Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - replace epoch-second Android release codes with a monotonic calculator based on build.gradle and existing Play track codes - write a proof artifact for the chosen version code during release runs - add focused regression tests for the calculator ## Verification - `python3 -m pytest -q scripts/tests/test_compute_android_release_version_code.py scripts/tests/test_play_publish.py` --------- Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
## Summary - harden the analytics truth layer so degraded PostHog reads preserve last-known-good data instead of publishing fake zeroes - centralize Android/iOS source version parsing and reuse it across release scripts and workflows - align hygiene/docs with shipped behavior by catching `/tmp/` leaks and removing false `No subscriptions` claims ## Verification - `python3 -m pytest -q scripts/tests/test_source_versions.py scripts/tests/test_compute_android_release_version_code.py scripts/tests/test_validate_release_branch.py scripts/tests/test_store_downloads_snapshot.py scripts/tests/test_attribution_feedback.py scripts/tests/test_north_star_guardrail.py scripts/tests/test_north_star_ops.py scripts/tests/test_wiki_sync.py` - `python3 -m pytest scripts/tests -q --cov=scripts --cov-report=term --cov-report=json:/tmp/random-timer-audit-python-coverage-after.json` - `bash scripts/hygiene-check.sh` - `./scripts/preflight-release.sh --platform both --layer 1` - `./scripts/bump-version.sh 1.2.3 --dry-run` - `python3 scripts/source_versions.py --repo-root . --format json` Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
Fixes metadata sync failure by adding changesNotSentForReview parameter to commit API call --------- Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
## Summary - align source release metadata with the real store candidate version (1.3.7 / Android base code 1773900000 / iOS build 151) - replace the remaining brittle iOS workflow version extraction with `scripts/source_versions.py` - include the Android/iOS Pro Sound Arsenal and voice-toggle persistence fix from the pending product branch - add regression coverage for release-context/workflow parsing and mobile parity ## Verification - python3 -m pytest -q scripts/tests/test_source_versions.py scripts/tests/test_compute_android_release_version_code.py scripts/tests/test_release_context.py scripts/tests/test_growth_workflow_contracts.py scripts/tests/test_mobile_feature_parity.py scripts/tests/test_timer_defaults_parity.py - bash scripts/preflight-release.sh --platform both --layer 1 - python3 scripts/source_versions.py --repo-root . --format json - python3 scripts/release_context.py --repo-root . --locale en-US --json-out .tmp-release-context.json --no-remote - cd native-android && ./gradlew assembleDebug testDebugUnitTest --tests com.iganapolsky.randomtimer.domain.model.TimerConfigTest --tests com.iganapolsky.randomtimer.data.repository.TimerRepositoryImplTest --tests com.iganapolsky.randomtimer.billing.MonetizationAnalyticsPayloadTest - cd native-ios && xcodebuild -scheme RandomTimer -destination 'platform=iOS Simulator,id=FB1D04C9-33DF-40E5-9F56-466FE9D1495B' -only-testing:RandomTimerTests/TimerConfigTests test ## Notes - This supersedes the open product-only PR that fixed the Sound Arsenal and voice-toggle state but did not fix release-version drift. Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com>
Made-with: Cursor
Made-with: Cursor
Adds a workflow_dispatch input to force iOS metadata sync against the current live App Store version. This is needed to replace stale public listing assets/copy without targeting a draft version.
## Summary - **P0**: Default timer range changed from 0-300s to **30-120s** — users get meaningful value on first tap - **P0**: Quick-start banner for first-time users: "Tap Start for a random 30s–2min drill. Customize later." - **P1**: One-line explainer under Timer Range: "Each timer picks a random duration in your range — stay ready for anything." - **P2**: Paywall gates deferred until after first `timer_completed` — free users see Pro labels but no lock/paywall before first completion - **Fix**: Crashlytics BigQuery script now surfaces 403 permission errors instead of silently returning "not set up" ## Context Data shows 85 → 8 distinct users completing timers (95.6% abandon rate). High `settings_changed` volume suggests friction. These changes reduce time-to-first-completion by: 1. Setting sane defaults so users can tap Start immediately 2. Explaining what "random" means to reduce anxiety tweaking 3. Removing paywall friction before users experience value ## Test plan - [x] Android unit tests: 195/195 pass (including new `ActivationDefaultsTest`) - [x] iOS unit tests: 16/16 pass (including new `ActivationDefaultsTests`) - [x] Maestro e2e: banner visible on fresh install, timer starts, navigates to active screen (iOS verified via MCP) - [x] Android emulator: screenshot verified — banner, explainer, and 30s-2m defaults all visible - [x] iOS simulator: screenshot verified — banner, explainer, and 30s-2m defaults all visible - [ ] Full Maestro suite on connected iPhone (Android Maestro driver needs reinstall) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ation nudge once (#1076) Follow-up to #1073 merged before this patch landed on the feature branch. - Add missing tests/playwright/src/browserSelection.ts (fixes ERR_MODULE_NOT_FOUND in Playwright strict CI). - Handle RuntimeError from check_bigquery_export in check_crashlytics main() and collect_crashlytics_snapshot; add tests. - Apply activation 20–60s preset only once per install (shared onboarding prefs / UserDefaults) so manual 30–120 is not overridden on return to setup. Made with [Cursor](https://cursor.com) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## What changed - rewrote iOS subtitle, keywords, promotional text, and description to target `random timer`, `reaction timer`, `dry fire`, `boxing`, `BJJ`, and `HIIT` intent more directly - rewrote Google Play short and full descriptions around the same search terms while keeping the tactical positioning - updated the metadata regression test to assert the new search-focused copy ## Why - exact brand search was already fine - generic and high-intent store discovery was weak for `random timer`, `reaction timer`, `dry fire timer`, and `boxing timer` - this patch makes the indexed metadata match those queries instead of relying on slogan copy ## Validation - `uvx --from pytest pytest scripts/tests/test_store_metadata_copy.py -q` - `python3 scripts/check_store_listing_parity.py --repo-root .` ## Impact - stronger App Store keyword coverage - stronger Google Play search-facing metadata - no code-path changes, store listing only
Auto-generated PR to sync develop after release --------- Co-authored-by: Igor Ganapolsky <iganapolsky@gmail.com> Co-authored-by: Igor Ganapolsky <igor.ganapolsky@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: IgorGanapolsky <201209+IgorGanapolsky@users.noreply.github.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: aider (openrouter/qwen/qwen3-coder) <aider@aider.chat>
|
Owner
Comment on lines
+222
to
+231
| init( | ||
| bundle: Bundle = .main, | ||
| packStore: ProAudioPackStore = .shared, | ||
| activateAudioSession: @escaping AudioSessionActivator = AIVoiceCalloutService.activateVoiceAudioSession | ||
| ) { | ||
| self.bundle = bundle | ||
| self.packStore = packStore | ||
| self.activateAudioSession = activateAudioSession | ||
| self.activateAudioSession() | ||
| } |
There was a problem hiding this comment.
Bug: AIVoiceCalloutService unconditionally activates the audio session on initialization, ducking background audio for users who haven't enabled the AI voice feature.
Severity: MEDIUM
Suggested Fix
Delay the activation of the audio session until it is confirmed that the AI voice feature is enabled and will be used. Move the session activation logic out of the initializer and into a method that is only called when the feature is active. Alternatively, guard calls to resetSession() with a check for feature enablement.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: native-ios/RandomTimer/Sources/Services/AIVoiceCalloutService.swift#L222-L231
Potential issue: The `AIVoiceCalloutService` initializer unconditionally activates the
audio session upon initialization. Because the service's shared instance is accessed for
all users, this causes other background audio (e.g., music, podcasts) to be ducked even
for free-tier users who have not enabled the AI voice feature. This behavior is
triggered by unguarded calls to `AIVoiceCalloutService.shared.resetSession()` in
`TimerManager.swift` when a timer is canceled or repeats, leading to a poor user
experience.
Contributor
Author
|
❌ CI Some checks failed
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Auto-generated PR to sync main with release v1.3.15