Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,9 @@ EXPO_PUBLIC_API_URL=
# --- App funnel visibility (optional; website) ---
# Per-store launch flags gate the public /app funnel. /app is visible (download
# mode) when either is true, and redirects home (no nav/homepage links) when both
# are false. iOS launches first; Android trails through its Closed Test.
#
# iOS App Store listing is public (default true — iOS is approved today).
# are false. Set one to 'false' to pull that store's download CTA.
PUBLIC_APP_IOS_LAUNCHED=true
# Google Play listing is public. Set 'true' once Google grants production access;
# until then the Android tile routes to /android-testers instead of a Play badge.
PUBLIC_APP_ANDROID_LAUNCHED=false
PUBLIC_APP_ANDROID_LAUNCHED=true

# --- Answer recording opt-in (optional) ---
# Recording feeds the shared Global progress and is ALWAYS on in production builds.
Expand Down
13 changes: 13 additions & 0 deletions apps/website/netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,16 @@

# [plugin.inputs]
# enable = false

# The Android Closed Test is over; send old tester links to the public download page.
[[redirects]]
from = "/android-testers"
to = "/app"
status = 302
force = true
Comment on lines +52 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High website/netlify.toml:52

When PUBLIC_APP_ANDROID_LAUNCHED=false, the “Become a tester” CTA links to /android-testers, but this forced redirect sends visitors to /app instead of the tester instructions. Make this redirect conditional on launch state or change the false-state CTA to a reachable destination.

-[[redirects]]
-  from = "/android-testers"
-  to = "/app"
-  status = 302
-  force = true
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/website/netlify.toml around lines 52-56:

When `PUBLIC_APP_ANDROID_LAUNCHED=false`, the “Become a tester” CTA links to `/android-testers`, but this forced redirect sends visitors to `/app` instead of the tester instructions. Make this redirect conditional on launch state or change the false-state CTA to a reachable destination.


[[redirects]]
from = "/android-testers-birthday-present"
to = "/app"
status = 302
force = true
2 changes: 1 addition & 1 deletion apps/website/playwright.ssr.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ export default defineConfig({
reuseExistingServer: !process.env.CI,
timeout: 120_000,
// /app renders in download mode under the default flags (PUBLIC_APP_IOS_LAUNCHED
// defaults on, Android off), which is exactly what the SSR /app suite asserts.
// and PUBLIC_APP_ANDROID_LAUNCHED default on), which is what the SSR /app suite asserts.
},
})
Binary file not shown.
5 changes: 2 additions & 3 deletions apps/website/src/constants/app-visibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
* Pure visibility rule for the public /app funnel, kept free of any `env` import
* so it can be unit-tested without the full environment.
*
* iOS and Android launch on separate timelines (iOS is public; Android trails by
* Google's Closed Test), so the flags are per-store. The funnel (route, nav entry,
* iOS and Android launched on separate timelines, so the flags are per-store. The funnel (route, nav entry,
* homepage CTA) is reachable as soon as either store is live:
*
* PUBLIC_APP_IOS_LAUNCHED — iOS App Store listing is live (default on)
* PUBLIC_APP_ANDROID_LAUNCHED — Google Play listing is live (default off)
* PUBLIC_APP_ANDROID_LAUNCHED — Google Play listing is live (default on)
*
* ios=false, android=false → hidden (/app redirects home)
* ios=true OR android=true → visible (download mode)
Expand Down
8 changes: 3 additions & 5 deletions apps/website/src/constants/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
*
* iOS and Android launch on separate timelines, so each store has its own flag:
* - PUBLIC_APP_IOS_LAUNCHED iOS App Store listing is public (default true).
* - PUBLIC_APP_ANDROID_LAUNCHED Google Play listing is public (default false —
* Android is still in Google's mandatory Closed Test).
* - PUBLIC_APP_ANDROID_LAUNCHED Google Play listing is public (default true).
*
* While Android is in testing, /app offers the real iOS download and routes
* Android visitors to the /android-testers funnel instead of a Play badge.
Expand All @@ -25,9 +24,8 @@ import {computeAppVisible} from './app-visibility'
export const APP_IOS_LAUNCHED: boolean = env.PUBLIC_APP_IOS_LAUNCHED

/**
* True once the Google Play listing is public. Flip via PUBLIC_APP_ANDROID_LAUNCHED
* after Google grants production access. Until then Android visitors are sent to
* the /android-testers Closed Test funnel.
* True when the Google Play listing is public. When false, Android visitors are
* sent to the /android-testers Closed Test funnel.
*/
export const APP_ANDROID_LAUNCHED: boolean = env.PUBLIC_APP_ANDROID_LAUNCHED

Expand Down
185 changes: 0 additions & 185 deletions apps/website/src/pages/android-testers-birthday-present.astro

This file was deleted.

4 changes: 3 additions & 1 deletion apps/website/src/pages/app.astro
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ const features = [

<Layout
title='WhoCards — Get the App'
description='Download WhoCards on iOS today. Android is in testing — meaningful conversations in your pocket, wherever you go.'
description={APP_ANDROID_LAUNCHED
? 'Download WhoCards on iOS and Android — meaningful conversations in your pocket, wherever you go.'
: 'Download WhoCards on iOS today. Android is in testing — meaningful conversations in your pocket, wherever you go.'}
class='scrollbar-hide'
>
<section
Expand Down
2 changes: 0 additions & 2 deletions apps/website/src/seo/indexation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ describe('search indexation policy', () => {
it.each([
'/404',
'/android-testers',
'/android-testers-birthday-present',
'/images',
'/hu/images',
'/dev/image-playground',
Expand Down Expand Up @@ -52,7 +51,6 @@ describe('search indexation policy', () => {

it.each([
'/android-testers.html',
'/android-testers-birthday-present.html',
'/events/hajnalig/play.html',
'/events/hajnalig/2025/play.html',
'/events/sdd-2026.html',
Expand Down
1 change: 0 additions & 1 deletion apps/website/src/seo/indexation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export function isNoindexRoute(pathname: string): boolean {
return (
route === '/404' ||
route === '/android-testers' ||
route === '/android-testers-birthday-present' ||
route.endsWith('/images') ||
route === '/events/hajnalig/play' ||
/^\/events\/hajnalig\/[^/]+\/play$/.test(route) ||
Expand Down
36 changes: 10 additions & 26 deletions apps/website/tests/e2e-ssr/app.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {expect, test} from '@playwright/test'

// /app is SSR (prerender = false). The e2e-ssr suite runs against `astro dev`
// under the default flags (PUBLIC_APP_IOS_LAUNCHED on, PUBLIC_APP_ANDROID_LAUNCHED
// off) → the page renders in download mode: iOS is a real App Store download and
// Android routes to the /android-testers Closed Test funnel. These assertions need
// no external services (no valid form submit, so neither Postgres nor Resend is hit).
// under the default flags (both stores launched) → the page offers both store
// downloads. These assertions need no external services (no valid form submit,
// so neither Postgres nor Resend is hit).

test.describe('/app download mode (iOS live, Android in testing)', () => {
test('offers the real iOS App Store download', async ({page}) => {
test.describe('/app download mode (iOS + Android live)', () => {
test('offers both store downloads', async ({page}) => {
const res = await page.goto('/app')

// Prove we are on /app, not an unexpected redirect to /.
Expand All @@ -16,35 +15,20 @@ test.describe('/app download mode (iOS live, Android in testing)', () => {

const html = await res!.text()
expect(html).toContain('apps.apple.com')
expect(html).toContain('play.google.com')
await expect(page.locator('a[data-store="ios"]').first()).toBeVisible()
await expect(page.locator('a[data-store="android"]').first()).toBeVisible()
await expect(page.locator('a[data-store="android-test"]')).toHaveCount(0)
})

test('routes Android visitors to the tester funnel, not a dead Play badge', async ({page}) => {
const res = await page.goto('/app')
const html = await res!.text()

// While Android is in Closed Testing the Play store link must not appear.
expect(html).not.toContain('play.google.com')

// The android-test tile is the tester-funnel route (its href is the configured
// ANDROID_TESTER_SIGNUP_URL, which may be overridden per environment, so we only
// assert the tile is present rather than a hardcoded path).
await expect(page.locator('a[data-store="android-test"]').first()).toBeVisible()
})

test('leads Android visitors with the tester tile (UA-aware ordering)', async ({browser}) => {
// Exercise the server-side UA branch: an Android visitor's first tile must be
// the tester route, not the iOS App Store badge they can't use.
test('leads Android visitors with Google Play (UA-aware ordering)', async ({browser}) => {
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 Chrome/124 Mobile',
})
const page = await context.newPage()
await page.goto('/app')

await expect(page.locator('a[data-store]').first()).toHaveAttribute(
'data-store',
'android-test'
)
await expect(page.locator('a[data-store]').first()).toHaveAttribute('data-store', 'android')
await context.close()
})

Expand Down
11 changes: 2 additions & 9 deletions apps/website/tests/e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ test('AI Check-In has intent-aligned metadata and links back to the conversation
}) => {
await page.goto('/ai-at-work')

await expect(page).toHaveTitle(
'WhoCards | AI Team Check-In Questions for Honest Conversations'
)
await expect(page).toHaveTitle('WhoCards | AI Team Check-In Questions for Honest Conversations')
await expect(page.locator('meta[name="description"]')).toHaveAttribute(
'content',
/Talk to your team about AI with a free set of AI team check-in questions/
Expand Down Expand Up @@ -70,12 +68,7 @@ test('unknown route serves the 404 page', async ({request}) => {
})

test('production HTML aligns robots metadata with temporary route policy', async ({page}) => {
for (const route of [
'/android-testers',
'/android-testers-birthday-present',
'/events/hajnalig/play',
'/events/hajnalig/2025/play',
]) {
for (const route of ['/android-testers', '/events/hajnalig/play', '/events/hajnalig/2025/play']) {
await page.goto(route)
await expect(page.locator('meta[name="robots"]')).toHaveAttribute('content', 'noindex, follow')
}
Expand Down
Loading
Loading