Skip to content

iOS: tellable incrementing version (1.0.x + dev tag/SHA) instead of frozen 1.0 - #5592

Merged
lawrencecchen merged 1 commit into
mainfrom
feat-ios-tellable-version
Jun 8, 2026
Merged

lawrencecchen merged 1 commit into
mainfrom
feat-ios-tellable-version

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The iOS app's MARKETING_VERSION was hardcoded to 1.0 in ios/Config/Shared.xcconfig and never bumped, so every iOS build (TestFlight releases AND dev dogfood builds) showed 1.0. CFBundleVersion is a date/build id, so neither real users nor the dev could easily tell which version/build a phone was running. The version wasn't displayed in the app at all (only sent as an analytics super-property).

What changed

  • Release/TestFlight version increments. Bump MARKETING_VERSION 1.0 → 1.0.0 (CFBundleShortVersionString). This is the authoritative source for both Debug and Release, verified with xcodebuild -showBuildSettings (the pbxproj does not override it; Debug.xcconfig and Release.xcconfig both #include Shared.xcconfig). TestFlight already inherits it because ios/scripts/upload-testflight.sh never passes MARKETING_VERSION, so this alone makes release builds report a meaningful version.
  • Manual patch-bump script. New ios/scripts/bump-ios-version.sh (patch default, also minor/major/X.Y.Z) bumps the version on release, mirroring the macOS bump-version.sh model. Kept manual on purpose: a per-merge auto-bump would force CI to commit back to the repo. CFBundleVersion stays the per-upload monotonic UTC timestamp that upload-testflight.sh already stamps, so App Store's strictly-increasing-build-number requirement is unaffected.
  • Dev builds are tellable. New empty-default Info.plist keys CMUXDevTag / CMUXGitSHA (sourced from CMUX_DEV_TAG / CMUX_GIT_SHA build settings). ios/scripts/reload.sh overrides them with the --tag and the short git SHA (trailing + when the working tree is dirty, including untracked files). Empty on release, so release shows a clean 1.0.0.
  • In-app display. AppVersionInfo (in CmuxMobileSupport) is a pure, testable value type that assembles the display string from the bundle's info dictionary plus an injected isDevBuild. Shown in MobileSettingsView under a new About → Version row (selectable text). 10 unit tests cover release vs dev, dirty marker, missing keys, and unexpanded-macro fallback.

What the version shows now

  • TestFlight/release build: 1.0.0 (20260607031606) — clean semver + the monotonic build number. Bump the patch with ios/scripts/bump-ios-version.sh per release.
  • Dev/dogfood build: 1.0.0 (1) · grid · a1b2c3d — semver + build number + --tag + short git SHA, so two dev builds are never indistinguishable.

The patch increments only when you run ios/scripts/bump-ios-version.sh on release (manual, no CI churn). The build number keeps auto-incrementing per TestFlight upload.

Tradeoff

The MARKETING_VERSION bump is manual, not fully automated. A fully-automated per-merge bump would require CI to commit the version back to the repo on every iOS-affecting merge (churn + race-prone with the existing TestFlight workflow). The documented bump-ios-version.sh path is the deliberate, low-risk choice; the build number (CFBundleVersion) is already auto-monotonic per upload, so uploads never collide regardless.

Verification

Build-metadata change — verified by inspecting the built Info.plist, no full iOS dogfood required. Simulator build via ios/scripts/reload.sh --tag iosver succeeded; built cmux.app/Info.plist shows CFBundleShortVersionString=1.0.0, CMUXDevTag=iosver, CMUXGitSHA=7135f32c5f+. All 25 CmuxMobileSupport tests pass. Autoreview clean (patch is correct), cmux-policy clean.

Localization: mobile.settings.about and mobile.settings.version added to ios/cmux/Resources/Localizable.xcstrings with en + ja. The version value string is not localizable text.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
Build metadata and settings UI only; release builds explicitly hide dev tag/SHA via #if DEBUG, with tests covering edge cases.

Overview
Replaces the frozen iOS MARKETING_VERSION (1.0 → 1.0.0) with a semver users and testers can report, and surfaces that identity in the app.

In-app: New About → Version row in MobileSettingsView shows a selectable string from AppVersionInfo, which reads CFBundleShortVersionString, CFBundleVersion, and optional dev keys from the bundle.

Release: ios/scripts/bump-ios-version.sh manually bumps semver in Shared.xcconfig on release (patch/minor/major or explicit X.Y.Z). TestFlight’s per-upload CFBundleVersion behavior is unchanged.

Dev dogfood: Info.plist adds CMUXDevTag / CMUXGitSHA (from CMUX_DEV_TAG / CMUX_GIT_SHA, empty by default). reload.sh passes the --tag and short git SHA (with + when the tree is dirty, including untracked files). DEBUG builds append · tag · sha to the About string; release builds stay clean even if those keys are set.

Unit tests cover formatting, release vs dev gating, placeholders, and missing keys. Localized About / Version labels (en + ja).

Reviewed by Cursor Bugbot for commit 5fc8f13. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Make the iOS app version tellable. Release/TestFlight builds now show semver + build number, and dev builds add a tag and short git SHA. The version also appears in-app.

  • New Features

    • Bump MARKETING_VERSION from 1.0 → 1.0.0 (CFBundleShortVersionString) for Debug and Release.
    • Add ios/scripts/bump-ios-version.sh to manually bump semver on release; CFBundleVersion stays the auto UTC timestamp.
    • Dev builds stamp CMUXDevTag and CMUXGitSHA via ios/scripts/reload.sh (adds “+” when dirty); release/TestFlight render a clean string and ignore dev metadata.
    • Show About → Version in MobileSettingsView using AppVersionInfo from CmuxMobileSupport (selectable text). Tests cover release/dev formatting.
  • Migration

    • On releases, run ios/scripts/bump-ios-version.sh (defaults to patch).
    • No change to TestFlight; the build number still auto-increments per upload.
    • For dev builds, use ios/scripts/reload.sh --tag <name> to stamp the tag and SHA.

Written for commit 5fc8f13. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Adds an "About" section to Settings that displays the app version and build metadata with localized labels and accessibility identifiers.
  • Tests

    • Adds tests covering version formatting, placeholder handling, dev-build metadata, and fallback behavior.
  • Chores

    • Updates project versioning configuration and adds/updates scripts to manage and embed version and build identifiers during builds.

@vercel

vercel Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 8, 2026 2:57am
cmux-staging Building Building Preview, Comment Jun 8, 2026 2:57am

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an "About" section to Settings showing the app version, introduces AppVersionInfo to read/format bundle build metadata with tests, wires xcconfig/Info.plist and scripts to supply git/tag build values, and adds localized strings.

Changes

About Section with Version Display

Layer / File(s) Summary
Build configuration for version metadata
ios/Config/Shared.xcconfig, ios/Config/Info.plist
MARKETING_VERSION bumped to 1.0.0; new CMUX_GIT_SHA and CMUX_DEV_TAG variables added to xcconfig with empty defaults; corresponding CMUXDevTag and CMUXGitSHA keys added to Info.plist.
Version information library
Packages/CmuxMobileSupport/Sources/CmuxMobileSupport/AppVersionInfo.swift
AppVersionInfo struct reads marketing version, build number, and custom metadata from the bundle info dictionary; trims and normalizes values; displayString formats X.Y.Z (build) and conditionally appends dev tag and git SHA when isDevBuild is true; current() builds from Bundle.main.infoDictionary.
Version library tests
Packages/CmuxMobileSupport/Tests/CmuxMobileSupportTests/AppVersionInfoTests.swift
Tests verify release builds show only marketing version and optional build number, dev builds append tag and SHA (including dirty marker), unexpanded build-variable placeholders are treated as empty, and missing marketing version falls back to 0.0.0.
Settings UI and localization
Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift, ios/cmux/Resources/Localizable.xcstrings
MobileSettingsView adds an About section displaying AppVersionInfo.current().displayString in a LabeledContent row labeled "Version" with info.circle and an accessibility identifier; adds localized "About" and "Version" strings (EN/JA).
Build pipeline integration
ios/scripts/bump-ios-version.sh, ios/scripts/reload.sh
New bump-ios-version.sh updates MARKETING_VERSION in xcconfig with semantic bump options (patch/minor/major or explicit); reload.sh computes short git SHA and appends + for dirty trees, and passes CMUX_GIT_SHA and CMUX_DEV_TAG into xcodebuild invocations for simulator and device.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#5544: Also modifies MobileSettingsView.swift by adding a settings section; overlaps UI component changes.
  • manaflow-ai/cmux#5499: Changes how CFBundleVersion build numbers are generated for TestFlight; relates to the buildNumber field read by AppVersionInfo.

Poem

🐰 A little rabbit nudges the view,

"Version" shines in Settings true,
From xcconfig to bundle tucked,
Git tag and build number plucked,
Hooray — the About row hops into view!

🚥 Pre-merge checks | ✅ 18 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: replacing a frozen version 1.0 with an incrementing semver system (1.0.x) that includes dev tag and SHA metadata.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed AppVersionInfo is a pure Sendable value type with immutable properties; MobileSettingsView is SwiftUI View (intentionally MainActor); no actor isolation violations introduced.
Cmux Swift Blocking Runtime ✅ Passed No blocking/timing-based synchronization found. AppVersionInfo is pure synchronous; MobileSettingsView only renders UI. Tests are deterministic.
Cmux No Hacky Sleeps ✅ Passed New iOS shell scripts (bump-ios-version.sh, reload.sh) and Python utility (asc_max_build.py) contain no fixed sleeps, polling, or timing workarounds that violate runtime-no-hacky-sleeps.md.
Cmux Algorithmic Complexity ✅ Passed All production code changes are O(1) with fixed-size collections; test-only code and developer tooling are properly exempted. No nested collection scans, rescans, or unbounded iterations introduced.
Cmux Swift Concurrency ✅ Passed No legacy async patterns found. AppVersionInfo is a pure value type. MobileSettingsView's Task properly awaits methods at a SwiftUI boundary—a standard pattern.
Cmux Swift @Concurrent ✅ Passed PR adds synchronous AppVersionInfo struct and its usage in MobileSettingsView. No nonisolated async functions, no invalid @concurrent annotations, and no unhopped heavy async calls from UI isolation.
Cmux Swift File And Package Boundaries ✅ Passed AppVersionInfo (99 lines) correctly placed in CmuxMobileSupport package with tests; MobileSettingsView adds minor UI glue (+14 lines); no responsibility violations.
Cmux Swift Logging ✅ Passed All Swift source code changes contain no print, debugPrint, dump, NSLog, or improperly configured Logger statements. Code is pure value types with appropriate string formatting.
Cmux User-Facing Error Privacy ✅ Passed Version display contains only public git hashes, version numbers, and build labels. No credentials, vendor names, provider details, or sensitive metadata exposed.
Cmux Full Internationalization ✅ Passed All new user-facing text uses L10n.string() with matching Localizable.xcstrings entries for both locales (en, ja); AppVersionInfo contains only version data.
Cmux Swiftui State Layout ✅ Passed AppVersionInfo is a pure Sendable value type. MobileSettingsView's About section displays version from AppVersionInfo.current()—no state, lazy subtrees, or render-time mutations.
Cmux Architecture Rethink ✅ Passed Pure immutable AppVersionInfo struct with clear single source of truth; no timing, dispatch, locks, observers, duplicate entrypoints, or split lifecycle ownership detected.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds iOS version display to MobileSettingsView form section (not a standalone window) and AppVersionInfo value type. No window APIs or cmux.* identifiers added.
Cmux Source Artifacts ✅ Passed All 8 changed files are legitimate source code, configs, tests, scripts, or localization catalogs with no artifact patterns detected per source-control-artifacts.md.
Description check ✅ Passed The PR description comprehensively covers the problem, changes, resulting behavior, tradeoffs, and verification—aligned with all template sections.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-tellable-version

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes iOS builds identifiable by replacing the frozen 1.0 marketing version with 1.0.0, wiring dev-build metadata (CMUXGitSHA / CMUXDevTag) into Info.plist via new xcconfig build settings, and surfacing a selectable About → Version row in MobileSettingsView.

  • AppVersionInfo is a new pure value type in CmuxMobileSupport that assembles the display string from the app bundle's info dictionary plus a compile-time #if DEBUG gate; 10 unit tests cover all edge cases including unexpanded macro fallback.
  • reload.sh now stamps CMUX_GIT_SHA (short SHA + + for dirty tree) and CMUX_DEV_TAG (the --tag argument) into the build; release/TestFlight builds pick up the empty xcconfig defaults.
  • bump-ios-version.sh is a new manual release script that bumps MARKETING_VERSION semver in Shared.xcconfig, mirroring the macOS workflow.

Confidence Score: 5/5

Safe to merge — build metadata wiring with no auth, data, or networking changes.

All changes are confined to version display: xcconfig defaults, Info.plist key additions, a read-only SwiftUI row, a new pure value type with 10 passing tests, and a release-only shell helper. The only pre-existing concern (unescaped dots in the sed pattern of bump-ios-version.sh) was already flagged in a prior review thread and poses negligible practical risk at the current single-MARKETING_VERSION-line xcconfig. No production logic paths are affected.

No files require special attention.

Important Files Changed

Filename Overview
Packages/CmuxMobileSupport/Sources/CmuxMobileSupport/AppVersionInfo.swift New pure value type assembling the displayable version string; well-structured, testable, and correctly guards against unexpanded build-setting macros.
Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift Adds the About → Version row; fully localized, uses selectable text, no SwiftUI state or layout issues.
Packages/CmuxMobileSupport/Tests/CmuxMobileSupportTests/AppVersionInfoTests.swift 10 tests covering release vs dev display strings, dirty-tree marker, missing keys, and unexpanded-macro fallback.
ios/scripts/bump-ios-version.sh New manual semver bump script; works correctly for the current xcconfig but uses unescaped dots in the sed pattern (flagged in previous thread).
ios/scripts/reload.sh Adds GIT_SHA computation (with dirty-tree marker) and passes CMUX_GIT_SHA / CMUX_DEV_TAG to xcodebuild for both simulator and device targets.
ios/Config/Shared.xcconfig Bumps MARKETING_VERSION to 1.0.0 and adds empty-default CMUX_GIT_SHA / CMUX_DEV_TAG build settings.
ios/cmux/Resources/Localizable.xcstrings Adds mobile.settings.about and mobile.settings.version with both en and ja translations, consistent with all other entries in the catalog.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Build type] -->|reload.sh| B[DEBUG dev build]
    A -->|upload-testflight.sh| C[Release / TestFlight build]

    B --> D["GIT_SHA = short HEAD + '+' if dirty\nCMUX_DEV_TAG = --tag argument"]
    D --> E["Info.plist: CMUXGitSHA / CMUXDevTag\n(build settings injected by xcodebuild)"]

    C --> F["Shared.xcconfig defaults\nCMUX_GIT_SHA = (empty)\nCMUX_DEV_TAG = (empty)"]
    F --> E

    E --> G["AppVersionInfo.init(infoDictionary:isDevBuild:)"]
    G -->|"#if DEBUG → isDevBuild = true"| H["displayString: '1.0.0 (1) · grid · a1b2c3d'"]
    G -->|"#else → isDevBuild = false"| I["displayString: '1.0.0 (20260607031606)'"]

    H --> J[MobileSettingsView\nAbout → Version row]
    I --> J

    K[bump-ios-version.sh] -->|"patch / minor / major / X.Y.Z"| L["Shared.xcconfig\nMARKETING_VERSION = X.Y.Z"]
    L --> G
Loading

Reviews (2): Last reviewed commit: "iOS: tellable incrementing version (1.0...." | Re-trigger Greptile


echo "New: MARKETING_VERSION=$NEW_MARKETING"

sed -i '' "s/^MARKETING_VERSION = $CURRENT_MARKETING\$/MARKETING_VERSION = $NEW_MARKETING/" "$XCCONFIG"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The dots in $CURRENT_MARKETING are unescaped, so they act as regex wildcards in the sed pattern. For 1.0.0 the pattern becomes 1.0.0, which matches 1X0X0 or any other two-dot sequence at that position. In practice there is only one MARKETING_VERSION line in the xcconfig, but if the file ever gains a commented example or a comment referencing the current version the substitution could silently match the wrong line. The post-check only verifies the grep result matches $NEW_MARKETING, so a wrong-line replacement would be silently missed. Escaping the dots makes the pattern precise.

Suggested change
sed -i '' "s/^MARKETING_VERSION = $CURRENT_MARKETING\$/MARKETING_VERSION = $NEW_MARKETING/" "$XCCONFIG"
ESCAPED_CURRENT="${CURRENT_MARKETING//./\\.}"
sed -i '' "s/^MARKETING_VERSION = $ESCAPED_CURRENT\$/MARKETING_VERSION = $NEW_MARKETING/" "$XCCONFIG"

lawrencecchen added a commit that referenced this pull request Jun 8, 2026
…ag/SHA) into dog bundle

# Conflicts:
#	ios/cmux/Resources/Localizable.xcstrings
…rozen 1.0

The iOS app's MARKETING_VERSION was hardcoded to 1.0 in
ios/Config/Shared.xcconfig and never bumped, so every build (TestFlight and
dev dogfood) showed "1.0". CFBundleVersion is a date/build id, so neither
users nor the dev could tell which version/build a phone was running.

Changes:
- Bump MARKETING_VERSION 1.0 -> 1.0.0 (CFBundleShortVersionString). This is
  the authoritative source for both Debug and Release (verified via
  -showBuildSettings; the pbxproj does not override it). TestFlight already
  inherits it (upload-testflight.sh never passes MARKETING_VERSION), so this
  alone makes release builds tellable.
- Add ios/scripts/bump-ios-version.sh to bump the patch on release. The bump
  stays manual (mirrors the macOS bump-version.sh model) so there is no CI
  commit-back loop on every merge; CFBundleVersion remains the per-upload
  monotonic UTC timestamp.
- Thread the dev --tag and short git SHA into new CMUXDevTag / CMUXGitSHA
  Info.plist keys (empty by default, overridden by ios/scripts/reload.sh), so
  a DEBUG build is tellable. The SHA gets a trailing "+" when the working tree
  is dirty (tracked or untracked changes).
- AppVersionInfo (CmuxMobileSupport): pure, testable value type that assembles
  the display string. Release shows "1.0.0 (<build>)"; dev appends
  "· <tag> · <sha>", e.g. "1.0.0 (123) · grid · a1b2c3d". 10 unit tests.
- Show it in MobileSettingsView under a new "About" > "Version" row
  (selectable). en + ja localization added.

Verified: simulator build via reload.sh; built Info.plist shows
CFBundleShortVersionString=1.0.0, CMUXDevTag=iosver, CMUXGitSHA=7135f32c5f+.
All 25 CmuxMobileSupport tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lawrencecchen
lawrencecchen force-pushed the feat-ios-tellable-version branch from 955430b to 5fc8f13 Compare June 8, 2026 02:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fc8f1346f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ios/scripts/reload.sh
Comment on lines +151 to +153
GIT_SHA="$(git -C "$IOS_DIR" rev-parse --short HEAD 2>/dev/null || true)"
if [[ -n "$GIT_SHA" && -n "$(git -C "$IOS_DIR" status --porcelain 2>/dev/null)" ]]; then
GIT_SHA="$GIT_SHA+"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stamp a per-reload build identifier

When reload.sh is run repeatedly from the same commit/tag after changing uncommitted iOS sources, this still stamps the same value such as abc123+ every time, and the script does not override CURRENT_PROJECT_VERSION so the Settings row remains 1.0.0 (1) · <tag> · abc123+. That makes distinct dogfood builds indistinguishable in exactly the workflow this About row is meant to support; include a per-reload timestamp/build number or tree-derived identifier instead of only HEAD plus a dirty marker.

Useful? React with 👍 / 👎.

@lawrencecchen
lawrencecchen merged commit 0051607 into main Jun 8, 2026
25 of 27 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — 5fc8f134 Deployed Jun 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant