From 0c61dc32ee7bf49acffd60ee1e6bf7b6a486807d Mon Sep 17 00:00:00 2001
From: TaprootFreakAI <315477232+TaprootFreakAI@users.noreply.github.com>
Date: Wed, 9 Sep 2026 03:00:28 +0200
Subject: [PATCH 1/8] fix(invite): keep English copy english and explain a
JS-less landing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two follow-ups from the review of #30.
?lang=en without a code flipped html lang and og:locale to English while
the shell text stayed German — crawlers snapshot exactly those bytes.
shareTitle and shareDescription now fall back to generic English copy.
Without JavaScript the loading state sat there for ever: nothing resolves
the code. Both shells now hide it and say why, and a Playwright case with
javaScriptEnabled:false asserts the served bytes.
---
functions/lib/itunes-banner.js | 12 ++++++++++--
public/invite/index.html | 16 ++++++++++++++++
public/promo/index.html | 16 ++++++++++++++++
test/itunes-banner-function.test.mjs | 20 ++++++++++++++++++++
tests/behavior.spec.mjs | 18 ++++++++++++++++++
5 files changed, 80 insertions(+), 2 deletions(-)
diff --git a/functions/lib/itunes-banner.js b/functions/lib/itunes-banner.js
index b18c0b1..686ea04 100644
--- a/functions/lib/itunes-banner.js
+++ b/functions/lib/itunes-banner.js
@@ -426,7 +426,14 @@ export function parseLangFromUrl(urlLike) {
/** Crawlers snapshot og:title / twitter:title from the HTML bytes. Names wait for lookup JS. */
export function shareTitle(kind, code, lang) {
- if (!kind || !code) return null;
+ // Without a code there is nothing code-specific to say, but ?lang=en still
+ // flips html lang and og:locale — leaving the German shell text under an
+ // English locale for crawlers. Fall back to a generic English title.
+ if (!code) {
+ if (lang !== 'en') return null;
+ return kind === 'promo' ? 'RealUnit — Promo code' : 'RealUnit — Invitation';
+ }
+ if (!kind) return null;
if (lang === 'en') {
return kind === 'promo' ? 'RealUnit — Promo code ' + code : 'RealUnit — Invitation ' + code;
}
@@ -477,7 +484,8 @@ export function injectShareImageAltHtml(html, kind, code, lang) {
/** Crawlers snapshot og:description from the HTML bytes. Names wait for lookup JS. */
export function shareDescription(code, lang) {
- if (!code) return null;
+ // Same reason as shareTitle: an English locale must not keep German copy.
+ if (!code) return lang === 'en' ? 'Open the RealUnit app with your code.' : null;
if (lang === 'en') return 'Open the RealUnit app with code ' + code + '.';
return 'Öffne die RealUnit-App mit dem Code ' + code + '.';
}
diff --git a/public/invite/index.html b/public/invite/index.html
index d4c8943..93fdc74 100644
--- a/public/invite/index.html
+++ b/public/invite/index.html
@@ -150,6 +150,22 @@
diff --git a/test/itunes-banner-function.test.mjs b/test/itunes-banner-function.test.mjs
index 9edaddb..37cd86e 100644
--- a/test/itunes-banner-function.test.mjs
+++ b/test/itunes-banner-function.test.mjs
@@ -560,6 +560,26 @@ describe('referral code injection hardening', () => {
});
});
+describe('an English locale without a code keeps English copy', () => {
+ test('shareTitle falls back to a generic English title', () => {
+ expect(shareTitle('invite', null, 'en')).toBe('RealUnit — Invitation');
+ expect(shareTitle('promo', null, 'en')).toBe('RealUnit — Promo code');
+ // German is the shell's own language, so there is nothing to replace.
+ expect(shareTitle('invite', null, 'de')).toBeNull();
+ expect(shareTitle('invite', null, null)).toBeNull();
+ });
+
+ test('shareDescription falls back to a generic English description', () => {
+ expect(shareDescription(null, 'en')).toBe('Open the RealUnit app with your code.');
+ expect(shareDescription(null, 'de')).toBeNull();
+ });
+
+ test('a code still wins over the fallback', () => {
+ expect(shareTitle('promo', 'EVT1', 'en')).toBe('RealUnit — Promo code EVT1');
+ expect(shareDescription('EVT1', 'en')).toBe('Open the RealUnit app with code EVT1.');
+ });
+});
+
describe('paths the 100% gate now covers on the function module', () => {
test('playStoreUrl without a code returns the bare store link', () => {
expect(playStoreUrl(null, 'invite')).toBe(playStoreUrl(null, 'promo'));
diff --git a/tests/behavior.spec.mjs b/tests/behavior.spec.mjs
index 5b71939..b2f780b 100644
--- a/tests/behavior.spec.mjs
+++ b/tests/behavior.spec.mjs
@@ -694,6 +694,24 @@ test.describe('account-merge flow', () => {
const REFERRAL_CODE_ENDPOINT = '**/v1/realunit/referral/code/**';
+test.describe('invite and promo landing without JavaScript', () => {
+ test.use({ javaScriptEnabled: false });
+
+ test('says why nothing resolves instead of spinning for ever', async ({ page }, testInfo) => {
+ test.skip(testInfo.project.name !== 'desktop-chromium', 'desktop-only check');
+ for (const path of ['/invite/AB12CD', '/promo/EVT1']) {
+ await page.goto(path);
+ // Nothing can resolve the code, so the loading state must not be the
+ // only thing on screen.
+ await expect(page.locator('#state-loading')).toBeHidden();
+ // Assert the served bytes: a no-JS visitor sees whatever the shell
+ // carries, and the hint lives in