Mobile Store Release Notes
The canonical spec for the per-release, paste-ready mobile store copy that closes out the weekly release train: Google Play "What's new" (Release notes), App Store Connect "What's New in This Version", and App Store Connect App Review notes.
1. Purpose / when this runs
Store listing copy is pasted by hand into Play Console and App Store Connect — there is no automated upload path for it. ionic_frontend/fastlane/ has no metadata/ directories (confirmed: only Appfile, Fastfile, Matchfile, README.md exist there), and the Fastfile release lanes run with skip_metadata: true / skip_screenshots: true — see app-store-submission.md §6. The Fastlane lanes upload the binary only; every "What's New" field is a manual, human-executed step.
That manual step is the last act of the weekly release train: after release cuts the tag and the mobile store builds (mobile-ios.yml, mobile-android.yml) are dispatched against it, Vicki drafts this copy from the tagged CHANGELOG.md and hands it to Josh as a second, paste-ready comment on the train issue — see docs/operations/deployment.md#beta-release-cadence--weekly-release-train and docs/guides/multica-autopilots.md §7c for the surrounding runbook.
Sibling artifact: the help site's public /whats-new page draws from the same tagged CHANGELOG section but is Dori's to draft, not Vicki's, and has no hard per-release deadline — see Help site changelog (/whats-new).
2. Source of truth (hard rule)
Draft only from the ## [X.Y.Z] section of ionic_frontend/CHANGELOG.md, read at the tagged commit — i.e. after release has renamed [Unreleased] to [X.Y.Z] in the same commit. The file's own header comment is explicit that this is the user-facing source, not the repo-root one:
<!-- USER-FACING CHANGELOG This file is rendered by ionic_frontend/src/components/ChangelogModal.vue (line ~44). Only include changes a user would notice: new features, visible improvements, user-affecting fixes. Format: Keep-a-Changelog (## [X.Y.Z] - YYYY-MM-DD). Update ## [Unreleased] before every release. Engineering-only changes (CI, deps, refactors) belong in /CHANGELOG.md (repo root) instead. -->
Never draft from git log, and never draft from the repo-root /CHANGELOG.md — that file is engineering-only (CI, deps, refactors) and was never meant to be user-facing. Never invent a feature, fix, or detail that isn't in the tagged [X.Y.Z] section.
Why ordering matters: on 2026-07-22, a hand-cut v4.2.0 tag (skipping scripts/release.sh) meant VITE_APP_VERSION correctly resolved to 4.2.0 (it comes from the tag/ref name) while the bundled CHANGELOG.md was still frozen at the 4.1.4 entry — the [4.2.0] section didn't exist in that commit yet. Two mobile builds shipped with a "What's New" modal reading v4.2.0 but showing 4.1.4 content. See docs/operations/mobile-builds.md for the full incident (OBJ-1684). The same risk applies here in reverse: draft this copy from a stale or pre-tag CHANGELOG.md and the store copy describes a release that hasn't shipped yet, or misses one that has. Always read the CHANGELOG at the tagged commit, never from a local working tree that might be ahead or behind it.
Extraction command (reads the tagged commit's [X.Y.Z] section only, stops before the next ## [ header):
git show <vX.Y.Z>:ionic_frontend/CHANGELOG.md | awk -v ver="X.Y.Z" \
'/^## \[/{ if ($0 ~ ("\\[" ver "\\]")) { f=1 } else if (f) { exit } } f'
ver=takes the bare version —4.2.0, not<4.2.0>and notv4.2.0. A wrong value matches nothing, prints nothing, and still exits 0 — there is no error to notice. If the command returns no output, stop and fix the version; never draft from an empty extraction. The command also assumes the version's## [X.Y.Z]header appears exactly once in the file, which holds for any tag cut byscripts/release.sh(it renames[Unreleased]in a single pass). Older hand-edited sections have duplicated a header before — if the output contains two## [headings, the file has a duplicate and the extra section must be dropped by hand.
Verified against this repo for v4.2.0 (ver="4.2.0"), real output:
## [4.2.0] - 2026-07-22
### Added
- **Share a private goal with your accountability partner, just that one** — flip on sharing for a single goal from its detail page, and your partner sees it on your profile. Everything else stays private, and you can turn it off any time. Nothing shares unless you say so.
- **Your home screen widget gets a glow-up, on iPhone and Android** — Small, Medium, and Large widgets on both platforms now show a warm stone-tower streak in place of the old flat tiles, with a one-line Coach nudge that's actually earned. Tap a habit straight from your Home Screen to check it off, no app launch required. iPhone also gets new Lock Screen shortcuts — Android doesn't have a Lock Screen widget surface to add them to.
- **Your allies now have one home on Communities** — find allies, review requests, and see everyone keeping you accountable from a single new section, instead of hunting across the page. A new "Open feed" link takes you straight to your activity feed, and "See ally activity" jumps right to the Allies tab.
- **Not sure what goal to set? Let Coach ask a few questions** — a short quiz (2-4 questions) turns your answers into 3-5 goal suggestions with a one-line "why this fits." Find it under "Set it up myself" on Goal Creation, or as a branch off naming your goal during onboarding.
- **Coach now notices when your mood and a goal move together** — if brighter days line up with more check-ins on one of your goals, Coach mentions it on your Dashboard. Purely reflective — nothing changes unless you act on it.
- **Coach can suggest moving a goal's target date when you're consistently ahead or behind pace** — shows up on your Goals page. One tap moves it, with an Undo right after if you change your mind.
- **The "Partners" tile on Communities is now "Allies" — and it's tappable.** Tap it to jump straight to your allies list. Any ally who's also an accountability partner now shows a **Partner** badge right on their row, so you can see who's already holding you accountable at a glance.
### Fixed
- **The sign-in screen's logo could get stuck half-drawn** — after a longer wait or a light/dark mode switch mid-load, you could briefly see "bjectuve" with a stray gold dot instead of the full mark. It now always settles in complete.
- **Milestone reordering now lands drag-and-drop items exactly where you drop them, including the first and last spot.**
- **A safety limit on drafting goals and asking Coach for suggestions now actually works.** If you fire off a lot of these in quick succession, you may see "Coach is catching up" — wait a minute and try again. Your typed idea or quiz answers are always preserved.3. Field limits
| Surface | ASC/Play field | Limit |
|---|---|---|
| Google Play Console → Release → Release notes (Default – en-US) | "What's new" | 500 characters per locale |
| App Store Connect → App version | "What's New in This Version" | 4,000 characters |
| App Store Connect → App Review Information | Notes | 4,000 characters |
The Play limit is the binding constraint — 500 characters forces ruthless compression, so draft Play first and let it set the ceiling for what makes the cut. App Store has roughly 8x the room and can carry the same story with less compression, plus the ### Fixed entries Play usually has to drop.
4. Voice rules
Reuse the existing house rules rather than reinventing them:
.claude/skills/writing-release-notes/SKILL.md— brand tone (coach-like, action-forward, celebratory without fluff), user-benefit language over implementation language.docs/brand/brand.md— full brand voice guide.- No "AI" label — user-facing copy says Coach, never "AI". Already asserted in
app-store-submission.md's intro and its pre-submission checklist.
Specific to this copy:
- Lead line = the single most compelling change in the release, no bullet marker — it sets the release's story before the bullets back it up.
- Bullets use
•, one line each, leading with the user outcome or verb (not the internal/technical framing). - Merge related CHANGELOG entries into one bullet where they tell the same story; drop anything a user wouldn't notice or wouldn't care about.
- Play
### Fixedentries are usually cut for space. Include one only if it's a fix users actually complained about and it still fits inside 500 characters after the### Addedstory is told. - App Store has room for the Fixed items — same story, less compression; a Play-cut fix can usually go back in for App Store.
5. Worked example (Play, v4.2.0)
This is the copy that actually shipped to Google Play for v4.2.0, reproduced verbatim — it is the reference for the compression the voice rules above are meant to produce:
The seven ### Added entries collapse into a lead line plus four bullets — the widget glow-up becomes the lead (most visually compelling, spans two platforms), and the remaining six entries merge down to four: goal sharing, the Coach quiz, the mood/pace insights (two entries merged into one), and the Communities ally home (merged with the Partners-tile rename, since both are "Communities got easier to use").
Your widget just leveled up: a stone-tower streak, a one-line Coach nudge, and tap-to-check-off habits — no app needed.
• Share one goal privately with your accountability partner. Everything else stays yours.
• Not sure what to set? Answer a few questions — Coach hands you goal ideas that fit.
• Coach now connects your mood and goals, and can nudge your target date when you're ahead or behind.
• Your allies now live in Communities — find allies, review requests, see who's got your back.Character count: 492 (well under the 500 limit). Verified with a Unicode-codepoint-aware count — plain wc -m under this environment's default C locale miscounts the em dashes and curly apostrophes as multiple bytes each (reported 506–507); re-run with LC_ALL=en_US.UTF-8 wc -m <file> (493, including one trailing newline) or python3 -c "print(len(open('<file>').read().rstrip(chr(10))))" (492) to get an accurate count. Always state the number you actually measured — never guess or eyeball it against the limit.
6. App Review notes
The standing boilerplate — app description paragraph, "HOW TO SIGN IN", "NOTES FOR REVIEW" bullets, and export compliance line — lives in app-store-submission.md §4 and is unchanged release to release. Reference it there; never retype or paraphrase it here, and never reproduce the demo account credentials in this doc — they're already in that section.
The only per-release variable is a resubmission preamble, added to the top of the Notes field only when the previous submission was rejected. Name each guideline number Apple cited and state exactly what changed to address it, so the reviewer can verify the fix without re-reading the whole app.
How Vicki knows. App Store review status lives in App Store Connect's Resolution Center and in Josh's email. Nothing in this repo reads it, and Vicki cannot. (An App Store Connect API credential does exist —
ASC_KEY_ID/ASC_ISSUER_ID/ASC_KEY_CONTENT, used by the Fastlane lanes inionic_frontend/fastlane/Fastfile— but no lane or script queries review status with it, and it is a GitHub Actions secret scoped tomobile-ios.yml, not something injected into Vicki's agent environment. Treat it as unavailable.) Vicki must never guess. Rejection status is Josh's to supply: he states it in his "go" reply on the train issue. If the go reply doesn't mention it, Vicki writes the App Review notes section of the comment as "Resubmission preamble: unknown — Josh, was the previous submission rejected? If yes, reply with the guideline number(s) and I'll draft the preamble." and still posts the comment and closes the train. Silently omitting the preamble is the failure mode that produced the v4.1.3 dual rejection; an explicit unanswered question is not.
Illustrative shape (guideline number → what was rejected → what changed → where to verify):
Using the real v4.1.3 dual rejection as the example — two guidelines cited in one review pass:
- Guideline 2.1 (App Completeness) — the demo password was unusable on iOS: only Clerk's web
<SignIn>accepted a password, so the demo account's password field never appeared on the reviewer's device. Changed: native sign-in now surfaces the same password path. Verify: sign in with the demo credentials fromapp-store-submission.md§4 on a physical iOS device (or the App Store build under review) — the password field must appear without the 5-tap logo gesture.- Guideline 4.8 (Login Services) — Google-only login isn't a compliant "sign in with a third-party service" story on iOS without an equivalent Apple option. Changed: Sign in with Apple was added alongside Google. Verify: the sign-in screen shows both options.
A resubmission preamble is absent on a first submission or on any resubmission where the prior pass had no rejection notes to answer.
7. Train-issue comment template
The exact markdown body Vicki posts as the second comment on the train issue (the first is the release-shipped synopsis — see the "Weekly release train execution" final action in docs/guides/multica-agent-crew.md). The template below is given inside a fenced block; the two inner paste blocks it contains use ~~~ (not ```) so they nest correctly inside the outer fence Vicki posts through:
## Vicki — mobile store copy for <vX.Y.Z>
Paste-ready, sourced from `ionic_frontend/CHANGELOG.md` at tag `<vX.Y.Z>`.
### Google Play Console → Release notes (en-US) — <N>/500 chars
~~~
<Play "What's new" text — lead line, no bullet marker, then `• ` bullets>
~~~
### App Store Connect → What's New in This Version — <N>/4,000 chars
~~~
<App Store "What's New" text — same story, less compression, Fixed items included as space allows>
~~~
### App Store Connect → App Review Information → Notes
Standing boilerplate unchanged — see
[`app-store-submission.md` §4](../operations/app-store-submission.md#4-app-review-information).
<Resubmission preamble — exactly one of:
- present: name the guideline(s) + what changed + where to verify
- absent: first submission / no prior rejection to answer
- unknown: "Josh, was the previous submission rejected? If yes, reply with the guideline
number(s) and I'll draft the preamble." (use this whenever the go reply didn't say)>
**Self-check:**
- [ ] No "AI" anywhere in either block
- [ ] Every line traces to a `[<vX.Y.Z>]` CHANGELOG entry
- [ ] Play block ≤500 characters
- [ ] Counts above were **measured** — paste the command and its output, don't assert a number:
`python3 -c "import sys;print(len(sys.stdin.read().rstrip('\n')))" < block.txt`
- [ ] Resubmission preamble is present, absent, or explicitly asked — never silently skippedNever
- Never invent a feature, fix, or detail not present in the tagged
[X.Y.Z]CHANGELOG section. - Never exceed 500 characters on the Play "What's new" field.
- Never write "AI" anywhere in this copy — use Coach.
- Never retype or paraphrase the App Review standing boilerplate — link
app-store-submission.md§4. - Never draft before
releasehas landed the changelog rename ([Unreleased]→[X.Y.Z]) on the tagged commit. - Never guess whether the previous submission was rejected — ask Josh in the comment (§6).
- Never write the draft file into the repo working tree — use
/tmp/and delete it after posting, so nothing strays into a later commit.
Last updated: 2026-08-01