Skip to content

release: merge develop into main#1068

Closed
github-actions[bot] wants to merge 511 commits intomainfrom
develop
Closed

release: merge develop into main#1068
github-actions[bot] wants to merge 511 commits intomainfrom
develop

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Apr 3, 2026

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

IgorGanapolsky and others added 30 commits March 10, 2026 09:10
## 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>
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>
Comment thread native-ios/RandomTimer/Sources/Services/TimerManager.swift
Comment thread scripts/molmoweb_browser_verify.py
…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>
Comment thread native-ios/RandomTimer/Sources/Services/ProSoundCatalog.swift
## 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>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 6, 2026

@IgorGanapolsky
Copy link
Copy Markdown
Owner

Superseded by the verified v1.3.17 release path in #1080 and the back-merge in #1082. Closing this stale develop-to-main PR because release policy only allows release/hotfix branches into main.

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()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented Apr 6, 2026

CI Some checks failed

Check Result
Android Tests success
iOS Build & Tests success
Python Unit Tests success
Python Script Tests + Release Gate success
Playwright Local Checks success
Security failure

View details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-state:ci_running Required CI checks are still running

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant