refactor(brand): in-app prose -> 'Closer Couples' (launcher + wordmark stay 'Closer')

Extends the store/marketing rename to in-app user-facing PROSE, per owner decision.
Every string where 'Closer' names the app in a sentence now reads 'Closer Couples'
(Android + iOS): settings, pairing, invite share, widget headlines, paywall
subhead/thank-you, age gate, privacy body.

Deliberately KEPT as 'Closer' (judgment calls, documented in docs/NameChange.md):
- launcher label / iOS CFBundleDisplayName (14 chars truncates under the icon)
- wordmark logos (onboarding, auth alt-text, widget header, share-card drawText,
  iOS onboarding) + their asserting tests
- 'Grow Closer' onboarding pun (intimacy, not the app name)
- entity/length-capped lines: privacy rotator 'so Closer cannot read them'
  (hard 64-char cap), iOS 'not even Closer's servers'
- iOS 'Settings > Closer' help path (must match the kept display name)
- 'Closer Premium' tier label (matches the RevenueCat entitlement display name)
- all technical identifiers (app.closer / closer.app / closer_premium / etc.)

TodayWidgetTest exact-match assertions updated. compileDebugKotlin + unit tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
null 2026-07-11 17:07:31 -05:00
parent b17271c89f
commit 6668a3c4b7
12 changed files with 48 additions and 32 deletions

View File

@ -316,7 +316,7 @@ private fun BiometricLockScreen(activity: AppCompatActivity, onUnlocked: () -> U
}
).authenticate(
BiometricPrompt.PromptInfo.Builder()
.setTitle("Unlock Closer")
.setTitle("Unlock Closer Couples")
.setSubtitle("Verify it's you to continue")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG or

View File

@ -36,11 +36,11 @@ object CloserCopy {
object Paywall {
const val headline = "Go deeper together"
const val subhead = "Unlock everything Closer has built for couples."
const val subhead = "Unlock everything Closer Couples has built for you."
const val choosePlanTitle = "Choose your plan"
const val coupleShared = "One subscription covers you both — treat your partner."
const val thankYouTitle = "You're all set"
const val thankYouBody = "Thank you for supporting Closer."
const val thankYouBody = "Thank you for supporting Closer Couples."
}
object Subscription {
@ -53,9 +53,9 @@ object CloserCopy {
* error was triplicated verbatim. Two disclaimer variants are intentionally distinct
* (brief on sign-up, with-reason on the DOB step) kept separate, not unified. */
object AgeGate {
const val disclaimer = "Closer is an 18+ app."
const val disclaimerWithReason = "Closer is an 18+ app. We ask once to confirm your age."
const val disclaimer = "Closer Couples is an 18+ app."
const val disclaimerWithReason = "Closer Couples is an 18+ app. We ask once to confirm your age."
const val dobRequired = "Please enter your date of birth."
fun ageError(minAge: Int) = "You must be at least $minAge to use Closer."
fun ageError(minAge: Int) = "You must be at least $minAge to use Closer Couples."
}
}

View File

@ -10,7 +10,7 @@ import app.closer.core.navigation.JoinLink
object InviteShareText {
fun build(code: String): String {
val pretty = code.chunked(3).joinToString(" ")
return "Let's try Closer together 💜 It's our own private space to actually talk. " +
return "Let's try Closer Couples together 💜 It's our own private space to actually talk. " +
"Tap to join me: ${JoinLink.webUrl(code)}\n\n" +
"Or enter this code in the app: $pretty"
}

View File

@ -102,7 +102,7 @@ fun PairPromptScreen(
Spacer(Modifier.height(16.dp))
Text(
text = "Closer is built for two. Connect your partner to unlock everything worth unlocking:",
text = "Closer Couples is built for two. Connect your partner to unlock everything worth unlocking:",
style = MaterialTheme.typography.bodyLarge,
color = AuthMuted,
textAlign = TextAlign.Center

View File

@ -34,7 +34,7 @@ object WeeklyRecapShareCard {
else -> "We answered $q questions"
}
val reveals = if (r > 0) " and opened $r reveal${if (r == 1) "" else "s"}" else ""
return "$answered$reveals together this week on Closer 💜"
return "$answered$reveals together this week on Closer Couples 💜"
}
/** Renders the card to a bitmap using plain Canvas (no Compose-capture fragility). */

View File

@ -499,7 +499,7 @@ fun SettingsScreen(
SettingsRow(
icon = CloserGlyphs.Palette,
label = "Appearance",
subtitle = "Make Closer feel comfortable to open",
subtitle = "Make Closer Couples feel comfortable to open",
onClick = { onNavigate(AppRoute.APPEARANCE) }
)
}
@ -534,7 +534,7 @@ fun SettingsScreen(
SettingsRow(
icon = ImageVector.vectorResource(R.drawable.glyph_delete_account),
label = "Delete account",
subtitle = "Permanently remove your Closer account",
subtitle = "Permanently remove your Closer Couples account",
onClick = { onNavigate(AppRoute.DELETE_ACCOUNT) },
tint = SettingsDanger
)

View File

@ -92,9 +92,9 @@ private fun TodayWidgetContent(context: Context, snapshot: TodayWidgetSnapshot)
/** Content-free headline derived only from state flags. */
internal fun headlineFor(s: TodayWidgetSnapshot): String = when {
!s.signedIn -> "Open Closer"
!s.signedIn -> "Open Closer Couples"
!s.paired -> "Invite your partner to begin"
s.appLockEnabled -> "Something's waiting in Closer"
s.appLockEnabled -> "Something's waiting in Closer Couples"
else -> when (s.dailyState) {
"USER_ANSWERED_PARTNER_PENDING" -> "Waiting for your partner"
"PARTNER_ANSWERED_USER_PENDING" -> "Your turn — answer to reveal"

View File

@ -72,8 +72,8 @@
<string name="privacy_streak">Streak and shared wins</string>
<string name="privacy_streak_body">Your couple\'s streak count and challenge completions are shared between both of you.</string>
<string name="privacy_no_export">No data export</string>
<string name="privacy_no_export_body">Closer does not currently offer a data export. Your answers and history are deleted along with your account.</string>
<string name="privacy_rule_intro">Closer is built on one rule: answers stay private until both of you have answered. Here\'s exactly what that means.</string>
<string name="privacy_no_export_body">Closer Couples does not currently offer a data export. Your answers and history are deleted along with your account.</string>
<string name="privacy_rule_intro">Closer Couples is built on one rule: answers stay private until both of you have answered. Here\'s exactly what that means.</string>
<string name="privacy_usage_data_section">Anonymous usage data</string>
<string name="privacy_usage_data_title">Share anonymous usage data</string>
<string name="privacy_usage_data_body">Helps us see which features couples use — never what you write. Your answers, messages, and prompts are end-to-end encrypted and can\'t be in this data even if we wanted them.</string>

View File

@ -15,7 +15,7 @@ class TodayWidgetTest {
@Test
fun `signed-out shows a neutral open prompt`() {
assertEquals("Open Closer", headlineFor(snap(signedIn = false)))
assertEquals("Open Closer Couples", headlineFor(snap(signedIn = false)))
}
@Test
@ -26,7 +26,7 @@ class TodayWidgetTest {
@Test
fun `app-lock hides the daily state behind a generic headline`() {
val locked = headlineFor(snap(appLock = true, state = "BOTH_ANSWERED"))
assertEquals("Something's waiting in Closer", locked)
assertEquals("Something's waiting in Closer Couples", locked)
// The specific state must NOT leak when the lock is on.
assertFalse(locked.contains("reveal", ignoreCase = true))
}

View File

@ -1,7 +1,15 @@
# App rename: "Closer" → "Closer Couples" (store / marketing tier only)
# App rename: "Closer" → "Closer Couples"
> Audit + decision record for renaming the product's public name to **"Closer Couples"**.
> Status: **executed in repo.** Store-console changes remain external owner steps.
>
> **2026-07-09 extension — in-app prose now renamed.** The original pass was store/marketing only
> (in-app copy deliberately stayed "Closer"). Owner decision to extend: **in-app *prose* copy now uses
> "Closer Couples"** across Android + iOS (settings, pairing, invite share, widget headlines, paywall
> subhead/thank-you, age gate, privacy body, iOS settings/onboarding/pairing). The **launcher label and
> wordmark logos still stay "Closer"** (owner kept these — truncation under the icon, and the logo is a
> brand mark). See "What still stays 'Closer'" below. No technical identifiers touched; Android compiles +
> unit tests green (`TodayWidgetTest` assertions updated).
## Context
@ -17,12 +25,20 @@ After auditing every occurrence of the name across Android, iOS, images, and doc
Renaming any of these breaks builds / Firebase / the Play listing:
`namespace app.closer` · `applicationId closer.app` · package `app.closer.*` + `class CloserApp` · deep-link host `closer.app` + `closer://` scheme · Firebase `closer-app-22014` (`google-services.json`, `GoogleService-Info.plist`) · iOS `PRODUCT_BUNDLE_IDENTIFIER app.closer.iphone` + `bundleIdPrefix app.closer` + product/target/scheme `Closer` · SPM package `Closer` · Gradle `rootProject.name "Closer"` · resource filenames (`closer_launcher_*`, `ic_notification_closer`, `ic_launcher*`) · XML styles `Theme.Closer[.Splash]` · entitlement id `closer_premium` · crypto AAD constants.
## What does NOT change (per the chosen tier)
## What still stays "Closer" (after the 2026-07-09 prose extension)
- **Android launcher label**`app/src/main/res/values/strings.xml``app_name` stays **"Closer"**.
- **Android launcher label**`app/src/main/res/values/strings.xml``app_name` stays **"Closer"** (14-char "Closer Couples" truncates under the icon).
- **iOS home-screen label**`iphone/Closer/Info.plist``CFBundleDisplayName` stays **"Closer"** (`CFBundleName`/`PRODUCT_NAME` untouched).
- **All ~25 in-app copy strings** stay "Closer" (paywall, onboarding/age-gate, settings, widget, invite share text, privacy `strings.xml:95-96`, share card, iOS onboarding, etc.).
- **Wordmark logos** (onboarding, auth alt-text, widget, share card) stay "Closer" → their asserting tests stay green, untouched.
- **Wordmark logos** (onboarding `OnboardingScreen`/iOS `OnboardingViews`, auth alt-text `AuthVisuals`, `TodayWidget` header, `WeeklyRecapShareCard` drawText) stay "Closer" → the logo is a brand mark; their asserting tests (`FirstRunRenderSmokeTest` contentDescription, recap/widget tests) stay green.
- **Deliberate copy exceptions that stay "Closer":** the **"Grow Closer"** onboarding headline (intimacy pun, not the app name); the privacy-rotator slogan *"…so Closer cannot read them"* and iOS *"not even Closer's servers"* (entity references; the rotator line also has a hard 64-char display cap that "Closer Couples" would exceed — `CloserBrandCopyTest`); the iOS **"Settings > Closer"** help path (must match the kept `CFBundleDisplayName`); and the **"Closer Premium"** tier label (matches the RevenueCat entitlement display name).
- **Internal engineering/brand-system docs** using "Closer" as codename/design-system name (`DEVELOPMENT_LOG.md`, `SECURITY.md`, `Future.md`, `Engineering_Reference_Manual`, `Claude*.md`, brand-system docs).
## In-app prose renamed to "Closer Couples" (2026-07-09)
The ~26 user-facing strings where "Closer" names the app in a sentence now read "Closer Couples":
- **Android (9 files):** `CloserCopy` (paywall subhead reworded to avoid "Couples…couples", thank-you, age-gate ×3), `InviteShareText`, `PairPromptScreen`, `SettingsScreen` (appearance + delete-account subtitles), `TodayWidget` (Open / "Something's waiting" headlines — header wordmark kept), `MainActivity` biometric prompt, `WeeklyRecapShareCard` caption (drawText wordmark kept), `strings.xml` (`privacy_no_export_body`, `privacy_rule_intro`). `TodayWidgetTest` exact-match assertions updated.
- **iOS (2 files):** `SettingsViews` (profile/account/data-export/legal/help/premium-eligibility/paywall subhead), `PairingViews` explore line.
- **Internal engineering/brand-system docs** using "Closer" as codename/design-system name (`DEVELOPMENT_LOG.md`, `SECURITY.md`, `Future.md`, `Engineering_Reference_Manual`, the `Claude*.md` reports, `docs/brand/asset-system.md` "Closer Artwork Asset System", `docs/brand/generated-art/glyphs/README.md` "Closer G-set").
## Changes to make (in-repo)

View File

@ -28,7 +28,7 @@ struct PairPromptView: View {
)
.closerPadding()
Text("You can keep exploring Closer, but shared reveals and partner activity start after your person joins.")
Text("You can keep exploring Closer Couples, but shared reveals and partner activity start after your person joins.")
.font(CloserFont.footnote)
.foregroundColor(.closerTextSecondary)
.multilineTextAlignment(.center)

View File

@ -28,7 +28,7 @@ struct SettingsView: View {
SettingsProfileHeader(
initials: initials,
name: appState.currentUser?.displayName ?? "You",
detail: "Create an account to save your Closer space",
detail: "Create an account to save your Closer Couples space",
imageUrl: appState.currentUser?.photoUrl
)
}
@ -54,7 +54,7 @@ struct SettingsView: View {
SettingsLinkRow(
icon: "heart.fill",
title: "Connected with \(partner.displayName.isEmpty ? "Partner" : partner.displayName)",
subtitle: "Your shared Closer space is active",
subtitle: "Your shared Closer Couples space is active",
tint: .closerPrimary,
imageUrl: partner.photoUrl,
showsChevron: false
@ -124,7 +124,7 @@ struct SettingsView: View {
SettingsLinkRow(
icon: "square.and.arrow.up",
title: "Export Data",
subtitle: "Download a copy of your Closer data"
subtitle: "Download a copy of your Closer Couples data"
)
}
.buttonStyle(.plain)
@ -150,7 +150,7 @@ struct SettingsView: View {
SettingsLinkRow(
icon: "doc.text",
title: "Terms of Service",
subtitle: "The agreement for using Closer"
subtitle: "The agreement for using Closer Couples"
)
}
.buttonStyle(.plain)
@ -177,7 +177,7 @@ struct SettingsView: View {
SettingsLinkRow(
icon: "envelope",
title: "Contact Us",
subtitle: "Get help from the Closer team"
subtitle: "Get help from the Closer Couples team"
)
}
.buttonStyle(.plain)
@ -207,7 +207,7 @@ struct SettingsView: View {
SettingsLinkRow(
icon: "trash",
title: "Delete Account",
subtitle: "Permanently remove your Closer account",
subtitle: "Permanently remove your Closer Couples account",
tint: .closerDanger,
isDestructive: true,
showsChevron: false
@ -407,7 +407,7 @@ struct DataExportView: View {
.font(CloserFont.title2)
.foregroundColor(.closerText)
Text("Download a copy of all your Closer data including answers, memories, and preferences.")
Text("Download a copy of all your Closer Couples data including answers, memories, and preferences.")
.font(CloserFont.callout)
.foregroundColor(.closerTextSecondary)
.multilineTextAlignment(.center)
@ -580,7 +580,7 @@ struct PairingHelpView: View {
BulletPoint("Codes expire after a set time — generate a new one if needed")
BulletPoint("Each account can only be in one couple at a time")
BulletPoint("Both of you need a Closer account first")
BulletPoint("Both of you need a Closer Couples account first")
}
.closerPadding()
}
@ -704,7 +704,7 @@ struct PaywallView: View {
.foregroundColor(.closerText)
.multilineTextAlignment(.center)
Text("Unlock everything Closer has built for couples.")
Text("Unlock everything Closer Couples has built for you.")
.font(CloserFont.callout)
.foregroundColor(.closerTextSecondary)
.multilineTextAlignment(.center)