diff --git a/apps/calendar-macos/CompassKit/Sources/CompassKit/Auth/AccountChoiceCopy.swift b/apps/calendar-macos/CompassKit/Sources/CompassKit/Auth/AccountChoiceCopy.swift
new file mode 100644
index 0000000000..c7d1e097f3
--- /dev/null
+++ b/apps/calendar-macos/CompassKit/Sources/CompassKit/Auth/AccountChoiceCopy.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+/// Web parity: `apps/calendar-web/src/auth/providers/provider-copy.util.ts`.
+public enum AccountChoiceCopy {
+ public static let localDeviceOnlyLabel = "On this device only"
+ public static let localDeviceOnlySubtitle =
+ "Events stay in this browser until you connect an account."
+ public static let connectCalendarAccountLabel = "Connect a calendar account"
+ public static let connectCalendarAccountHelper =
+ "Pick Google, Microsoft, or Apple. Or keep using this device only from the calendar list."
+}
diff --git a/apps/calendar-macos/CompassKitTests/AccountChoiceCopyTests.swift b/apps/calendar-macos/CompassKitTests/AccountChoiceCopyTests.swift
new file mode 100644
index 0000000000..47868351fc
--- /dev/null
+++ b/apps/calendar-macos/CompassKitTests/AccountChoiceCopyTests.swift
@@ -0,0 +1,17 @@
+import XCTest
+@testable import CompassKit
+
+final class AccountChoiceCopyTests: XCTestCase {
+ func testAccountChoiceCopyMatchesWeb() {
+ XCTAssertEqual(AccountChoiceCopy.localDeviceOnlyLabel, "On this device only")
+ XCTAssertEqual(
+ AccountChoiceCopy.localDeviceOnlySubtitle,
+ "Events stay in this browser until you connect an account.")
+ XCTAssertEqual(
+ AccountChoiceCopy.connectCalendarAccountLabel,
+ "Connect a calendar account")
+ XCTAssertEqual(
+ AccountChoiceCopy.connectCalendarAccountHelper,
+ "Pick Google, Microsoft, or Apple. Or keep using this device only from the calendar list.")
+ }
+}
diff --git a/apps/calendar-macos/CompassUI/Sources/CompassUI/Onboarding/WelcomeModalView.swift b/apps/calendar-macos/CompassUI/Sources/CompassUI/Onboarding/WelcomeModalView.swift
index 3d1473e31f..7a3af21cff 100644
--- a/apps/calendar-macos/CompassUI/Sources/CompassUI/Onboarding/WelcomeModalView.swift
+++ b/apps/calendar-macos/CompassUI/Sources/CompassUI/Onboarding/WelcomeModalView.swift
@@ -106,7 +106,21 @@ struct WelcomeModalView: View {
.foregroundStyle(theme.textMutedColor)
}
VStack(spacing: 12) {
- primaryButton(title: "Explore without an account", hint: "S") { model.welcomeExplore() }
+ primaryButton(title: AccountChoiceCopy.localDeviceOnlyLabel, hint: "S") {
+ model.welcomeExplore()
+ }
+ Text(AccountChoiceCopy.localDeviceOnlySubtitle)
+ .font(.rubik(14))
+ .foregroundStyle(theme.textMutedColor)
+ VStack(spacing: 4) {
+ Text(AccountChoiceCopy.connectCalendarAccountLabel)
+ .font(.rubik(14, weight: .semibold))
+ .foregroundStyle(theme.textColor)
+ Text(AccountChoiceCopy.connectCalendarAccountHelper)
+ .font(.rubik(14))
+ .foregroundStyle(theme.textMutedColor)
+ }
+ .frame(maxWidth: .infinity)
Text("Ready to keep it? Start your free 7-day trial.")
.font(.rubik(14))
.foregroundStyle(theme.textMutedColor)
diff --git a/apps/calendar-macos/CompassUI/Sources/CompassUI/Settings/SettingsAccountsSectionView.swift b/apps/calendar-macos/CompassUI/Sources/CompassUI/Settings/SettingsAccountsSectionView.swift
index 4ed5640ca9..27acd52544 100644
--- a/apps/calendar-macos/CompassUI/Sources/CompassUI/Settings/SettingsAccountsSectionView.swift
+++ b/apps/calendar-macos/CompassUI/Sources/CompassUI/Settings/SettingsAccountsSectionView.swift
@@ -52,6 +52,12 @@ struct SettingsAccountsSectionView: View {
private var connectButtons: some View {
VStack(alignment: .leading, spacing: 6) {
+ Text(AccountChoiceCopy.connectCalendarAccountLabel)
+ .font(.rubik(13, weight: .semibold))
+ .foregroundStyle(theme.textColor)
+ Text(AccountChoiceCopy.connectCalendarAccountHelper)
+ .font(.rubik(11))
+ .foregroundStyle(theme.textMutedColor)
Button("Connect Google") {
Task { await syncStore.connect(provider: .google) }
}
diff --git a/apps/calendar-web/src/auth/posthog/signup-funnel.ts b/apps/calendar-web/src/auth/posthog/signup-funnel.ts
index f1be6e252d..47e05bfa4d 100644
--- a/apps/calendar-web/src/auth/posthog/signup-funnel.ts
+++ b/apps/calendar-web/src/auth/posthog/signup-funnel.ts
@@ -152,7 +152,7 @@ export async function trackSignupStartedAtClick(
trackSignupStarted(source, anon_events_created);
}
-/** Anonymous visitor chose local-only mode (e.g. sidebar "This browser"). */
+/** Anonymous visitor chose local-only mode (e.g. sidebar "On this device only"). */
export function trackLocalModeSelected(source: LocalModeSource): void {
track("local_mode_selected", { source });
}
diff --git a/apps/calendar-web/src/auth/providers/ConnectProviderChooser.test.tsx b/apps/calendar-web/src/auth/providers/ConnectProviderChooser.test.tsx
index eaa55a4268..b6b277c17b 100644
--- a/apps/calendar-web/src/auth/providers/ConnectProviderChooser.test.tsx
+++ b/apps/calendar-web/src/auth/providers/ConnectProviderChooser.test.tsx
@@ -155,9 +155,13 @@ describe("ConnectProviderChooser", () => {
const user = userEvent.setup();
available = ["google", "microsoft", "apple"];
- renderChooser(
+ {CONNECT_CALENDAR_ACCOUNT_LABEL} +
++ {CONNECT_CALENDAR_ACCOUNT_HELPER} +
+{CALENDAR_HOST_EXPLAINER}
++ {CONNECT_CALENDAR_ACCOUNT_HELPER} +
+ {LOCAL_DEVICE_ONLY_SUBTITLE} +
++ {CONNECT_CALENDAR_ACCOUNT_LABEL} +
++ {CONNECT_CALENDAR_ACCOUNT_HELPER} +
+Ready to keep it? Start your free 7-day trial.
diff --git a/docs/frontend/frontend-runtime-flow.md b/docs/frontend/frontend-runtime-flow.md index 258adfdd91..a4990f7eaf 100644 --- a/docs/frontend/frontend-runtime-flow.md +++ b/docs/frontend/frontend-runtime-flow.md @@ -90,13 +90,13 @@ Welcome → signup → first-event contract: - the last screen is titled **Let's get started** with the subtitle **Connect a calendar or start fresh**. Its CTA order is **Continue with Google** (`G`, when Google is available), **Sign up with email** (`U`), then - **Explore without an account** (`S`), all the same full-width height. + **On this device only** (`S`), all the same full-width height. Explore is a quieter secondary pill so signup stays the stronger choice. Social and legal links (`6`–`0`) sit below. Google leads because the scopes Compass requests include the calendar, so that one round trip signs the user up *and* connects it, the moment the product starts being worth keeping -- **Explore without an account** closes the welcome dialog and leaves the +- **On this device only** closes the welcome dialog and leaves the Shortcut Showcase closed until the visitor opts in - **Log in** opens the auth modal and leaves the showcase flags alone, so a returning user is not handed the practice or the first-event prompt @@ -222,7 +222,7 @@ lands on the grid. Rageclicks on public booking pages are still dropped by the booking filter. `local_mode_selected` fires when an anonymous visitor chooses the sidebar -**This browser** row (local IndexedDB mode). It must not be confused with +**On this device only** row (local IndexedDB mode). It must not be confused with `signup_started`, which is reserved for explicit login or trial CTAs (OAuth, email sign-up modal, command palette, and similar). diff --git a/docs/frontend/shortcut-commandments.md b/docs/frontend/shortcut-commandments.md index 4fe6b2c335..ad3ea41e0b 100644 --- a/docs/frontend/shortcut-commandments.md +++ b/docs/frontend/shortcut-commandments.md @@ -95,7 +95,7 @@ Settings is an OverlayPanel with its own letter map while it holds app-lock (`apps/calendar-web/src/settings/useSettingsShortcuts.ts`). Those letters are not page-jump digits; they activate controls already on the Settings overlay. Billing: `U` Update card, -`C` Cancel subscription, `R` Resume subscription. Accounts: `A` Add account, +`C` Cancel subscription, `R` Resume subscription. Accounts: `A` Connect a calendar account, `E` Export, `D` Delete account, `O` Log out. `B` (trial badge / Start Premium) is registered by `UpgradeConfirmationProvider` and keeps working while Settings is open. Meeting settings reveal only sidebar digits diff --git a/e2e/accessibility/connect-onboarding-a11y.spec.ts b/e2e/accessibility/connect-onboarding-a11y.spec.ts index f653e13805..ec41f849a5 100644 --- a/e2e/accessibility/connect-onboarding-a11y.spec.ts +++ b/e2e/accessibility/connect-onboarding-a11y.spec.ts @@ -140,7 +140,7 @@ test("the add-account chooser with Google, Microsoft, and Other / iCloud has no const settingsDialog = page.getByRole("dialog", { name: "Settings" }); await settingsDialog.getByRole("button", { name: "Accounts" }).click(); const addAccount = settingsDialog.getByRole("button", { - name: "Add account", + name: "Connect a calendar account", }); await expect(addAccount).toBeVisible(); await addAccount.click(); diff --git a/e2e/accessibility/teaching-surface-a11y.spec.ts b/e2e/accessibility/teaching-surface-a11y.spec.ts index 2733766dbe..898b6840a7 100644 --- a/e2e/accessibility/teaching-surface-a11y.spec.ts +++ b/e2e/accessibility/teaching-surface-a11y.spec.ts @@ -40,7 +40,7 @@ test("exploring without an account returns focus to the week view without trappi .click(); await welcomeDialog.getByRole("button", { name: "Next" }).click(); await welcomeDialog - .getByRole("button", { name: "Explore without an account" }) + .getByRole("button", { name: "On this device only" }) .click(); await expect(welcomeDialog).toBeHidden(); diff --git a/e2e/calendars/microsoft-surfaces.spec.ts b/e2e/calendars/microsoft-surfaces.spec.ts index 11197c8be0..32eda530c6 100644 --- a/e2e/calendars/microsoft-surfaces.spec.ts +++ b/e2e/calendars/microsoft-surfaces.spec.ts @@ -117,7 +117,7 @@ test("Add account starts Microsoft OAuth from the chooser", async ({ const settingsDialog = page.getByRole("dialog", { name: "Settings" }); await settingsDialog.getByRole("button", { name: "Accounts" }).click(); const addAccount = settingsDialog.getByRole("button", { - name: "Add account", + name: "Connect a calendar account", }); await expect(addAccount).toBeVisible(); await addAccount.click(); @@ -151,7 +151,9 @@ test("Add account menu shows a logo for Google and Microsoft plus Other / iCloud const settingsDialog = page.getByRole("dialog", { name: "Settings" }); await settingsDialog.getByRole("button", { name: "Accounts" }).click(); - await settingsDialog.getByRole("button", { name: "Add account" }).click(); + await settingsDialog + .getByRole("button", { name: "Connect a calendar account" }) + .click(); const menu = settingsDialog.getByRole("menu"); await expect(menu).toBeVisible(); diff --git a/e2e/onboarding/first-run.spec.ts b/e2e/onboarding/first-run.spec.ts index ebec09be8a..ccb09f754a 100644 --- a/e2e/onboarding/first-run.spec.ts +++ b/e2e/onboarding/first-run.spec.ts @@ -46,7 +46,7 @@ test("welcomes a first-time user and seeds sample events", async ({ // The welcome modal advertises S for this action; prove the shortcut. await expect( - welcomeDialog.getByRole("button", { name: "Explore without an account" }), + welcomeDialog.getByRole("button", { name: "On this device only" }), ).toBeVisible(); await page.keyboard.press("s"); await expect(welcomeDialog).toBeHidden(); diff --git a/e2e/onboarding/shortcut-showcase.spec.ts b/e2e/onboarding/shortcut-showcase.spec.ts index e2b26f25c9..1c29e775ec 100644 --- a/e2e/onboarding/shortcut-showcase.spec.ts +++ b/e2e/onboarding/shortcut-showcase.spec.ts @@ -19,7 +19,7 @@ const dismissWelcomeWithExplore = async (page: Page) => { ).toBeVisible(); await page.keyboard.press("Enter"); await expect( - welcomeDialog.getByRole("button", { name: "Explore without an account" }), + welcomeDialog.getByRole("button", { name: "On this device only" }), ).toBeVisible(); await page.keyboard.press("s"); await expect(welcomeDialog).toBeHidden(); diff --git a/e2e/onboarding/welcome-cta-width.spec.ts b/e2e/onboarding/welcome-cta-width.spec.ts index 3cf7f9332f..11e3ca013f 100644 --- a/e2e/onboarding/welcome-cta-width.spec.ts +++ b/e2e/onboarding/welcome-cta-width.spec.ts @@ -40,7 +40,7 @@ test("keeps Google, email signup, and explore buttons the same width", async ({ name: "Sign up with email", }); const explore = welcomeDialog.getByRole("button", { - name: "Explore without an account", + name: "On this device only", }); await expect(google).toBeVisible(); await expect(email).toBeVisible(); diff --git a/packages/backend/src/auth/services/google/google.auth.service.test.ts b/packages/backend/src/auth/services/google/google.auth.service.test.ts index 6f597d011a..9da0087614 100644 --- a/packages/backend/src/auth/services/google/google.auth.service.test.ts +++ b/packages/backend/src/auth/services/google/google.auth.service.test.ts @@ -244,7 +244,7 @@ describe("handleGoogleAuth", () => { }), ).rejects.toMatchObject({ result: - "You're already signed in. Use Settings → Add account to connect this Google account.", + "You're already signed in. Use Settings → Connect a calendar account to connect this Google account.", code: "GOOGLE_SIGNIN_WHILE_AUTHENTICATED", }); diff --git a/packages/backend/src/auth/services/google/google.auth.service.ts b/packages/backend/src/auth/services/google/google.auth.service.ts index a62b69ff3d..815e13feed 100644 --- a/packages/backend/src/auth/services/google/google.auth.service.ts +++ b/packages/backend/src/auth/services/google/google.auth.service.ts @@ -244,7 +244,7 @@ async function handleGoogleAuth( if (options?.hasExistingSession) { throw error( AuthError.GoogleSignInWhileAuthenticated, - "You're already signed in. Use Settings → Add account to connect this Google account.", + "You're already signed in. Use Settings → Connect a calendar account to connect this Google account.", ); } const isNewUser = createdNewRecipeUser && loginMethodsLength === 1; diff --git a/packages/backend/src/common/errors/auth/auth.errors.ts b/packages/backend/src/common/errors/auth/auth.errors.ts index b5bb34a255..83eecf595c 100644 --- a/packages/backend/src/common/errors/auth/auth.errors.ts +++ b/packages/backend/src/common/errors/auth/auth.errors.ts @@ -34,7 +34,7 @@ export const authErrorCopy = { refreshTokenMissing: (provider?: ProviderKind) => `${calendarHostLabel(provider)} did not grant a fresh authorization. Please try again.`, signInWhileAuthenticated: (provider?: ProviderKind) => - `You're already signed in. Use Settings → Add account to connect this ${calendarHostLabel(provider)} account.`, + `You're already signed in. Use Settings → Connect a calendar account to connect this ${calendarHostLabel(provider)} account.`, }; export const AuthError: AuthErrors = {