From 1aba819f7d9d5659399bfaef24427b044712559b Mon Sep 17 00:00:00 2001 From: JOY Date: Mon, 26 Oct 2026 00:53:56 +0700 Subject: [PATCH 1/2] fix(web): keep the Czech leaderboard and merchant header inside 320px (#1084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "falešných/spamových" had no line-break opportunity and overflowed the leaderboard's blocked-spam badge; use "či" instead of the slash. The merchant header's rating, follower and model items were non-wrapping inline-flex rows that forced the text column wider than the screen. Co-Authored-By: Claude Opus 5.5 --- apps/web/e2e/reflow-contrast.spec.ts | 4 ++++ apps/web/locales/cs.json | 2 +- apps/web/src/app/[locale]/merchants/[id]/page.tsx | 8 +++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/web/e2e/reflow-contrast.spec.ts b/apps/web/e2e/reflow-contrast.spec.ts index d7bc8002..887d7ee9 100644 --- a/apps/web/e2e/reflow-contrast.spec.ts +++ b/apps/web/e2e/reflow-contrast.spec.ts @@ -13,6 +13,10 @@ const PHONE_PAGES = [ // (DOS/Rate#1082). '/en/smartphones', '/cs/smartphones', + // Czech copy is the longest; the merchant rating line did not wrap + // (DOS/Rate#1084). + '/cs/leaderboard', + '/cs/merchants/excluded-review-store', ]; for (const path of PHONE_PAGES) { diff --git a/apps/web/locales/cs.json b/apps/web/locales/cs.json index 12df9c60..bf2461c4 100644 --- a/apps/web/locales/cs.json +++ b/apps/web/locales/cs.json @@ -396,7 +396,7 @@ "rank3Label": "3. místo", "fullTableTitle": "Kompletní žebříček kategorie", "verifiedReviewsSuffix": "ověřených recenzí", - "blockedSpamCount": "{count, plural, one {Zablokována # falešná/spamová recenze} few {Zablokovány # falešné/spamové recenze} other {Zablokováno # falešných/spamových recenzí}}", + "blockedSpamCount": "{count, plural, one {Zablokována # falešná či spamová recenze} few {Zablokovány # falešné či spamové recenze} other {Zablokováno # falešných či spamových recenzí}}", "storesTrackedSuffix": "obchodů", "viewAuditDetailBtn": "Recenze a 10 skupin parametrů →", "inspectBtn": "Ověřit cenu", diff --git a/apps/web/src/app/[locale]/merchants/[id]/page.tsx b/apps/web/src/app/[locale]/merchants/[id]/page.tsx index 70d864fa..ab014465 100644 --- a/apps/web/src/app/[locale]/merchants/[id]/page.tsx +++ b/apps/web/src/app/[locale]/merchants/[id]/page.tsx @@ -282,7 +282,9 @@ export default async function MerchantProfilePage({ className="d-flex align-items-center small flex-wrap gap-3" style={{ color: '#94a3b8' }} > - + {/* Items wrap: "Hodnocení tržiště: Neaktualizováno" is wider + than a 320px header column (DOS/Rate#1084). */} + {t('platformRatingLabel')}{' '} @@ -291,7 +293,7 @@ export default async function MerchantProfilePage({ : t('notUpdatedYet')} - + {t('followersLabel')}{' '} @@ -300,7 +302,7 @@ export default async function MerchantProfilePage({ : '-'} - + {t('trackedModelsLabel')}{' '} From 7712c71494202a3f804dcd65a947510d3ec39cfd Mon Sep 17 00:00:00 2001 From: JOY Date: Mon, 26 Oct 2026 01:07:42 +0700 Subject: [PATCH 2/2] test(web): guard the leaderboard blocked-spam badge copy (#1084) The E2E seed has no ignored reviews, so /cs/leaderboard never renders the blocked-spam badge. Check the copy directly: no plural branch in any locale may contain a word too long to wrap inside the badge. Co-Authored-By: Claude Opus 5.5 --- apps/web/package.json | 2 +- .../__tests__/leaderboard-badge-copy.test.ts | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/lib/__tests__/leaderboard-badge-copy.test.ts diff --git a/apps/web/package.json b/apps/web/package.json index e08cba59..78157a69 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,7 +8,7 @@ "lint": "eslint . --max-warnings 0", "preview": "next build && next start", "start": "next start", - "test": "pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/server/rate/__tests__/core-rate-architecture.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/app/api/v1/rate/__tests__/write-paths.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/logger.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/review-display.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/concurrency.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/catalog-listing.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/discount-verdict-copy.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/product-detail-copy.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/product-competitors.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/analyzer-copy.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/sold-count.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/content-lang.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/image-url.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/client-messages.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/seo.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/offer-plausibility.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/sitemap-lastmod.test.ts", + "test": "pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/server/rate/__tests__/core-rate-architecture.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/app/api/v1/rate/__tests__/write-paths.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/logger.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/review-display.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/concurrency.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/catalog-listing.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/discount-verdict-copy.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/product-detail-copy.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/product-competitors.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/analyzer-copy.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/sold-count.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/content-lang.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/image-url.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/client-messages.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/seo.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/offer-plausibility.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/sitemap-lastmod.test.ts && pnpm --filter @repo/crawlers exec tsx --tsconfig ../web/tsconfig.test.json ../web/src/lib/__tests__/leaderboard-badge-copy.test.ts", "typecheck": "tsc --noEmit" }, "browserslist": [ diff --git a/apps/web/src/lib/__tests__/leaderboard-badge-copy.test.ts b/apps/web/src/lib/__tests__/leaderboard-badge-copy.test.ts new file mode 100644 index 00000000..b69c5a67 --- /dev/null +++ b/apps/web/src/lib/__tests__/leaderboard-badge-copy.test.ts @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +// The leaderboard's blocked-spam badge is about 114px wide on a 320px phone +// (~12 characters at its font size). "falešných/spamových" had no line-break +// opportunity (a slash only allows a break before digits), so the badge +// pushed /cs/leaderboard 16px sideways (DOS/Rate#1084). The E2E seed has no +// ignored reviews, so this guards the copy directly. +const MAX_UNBREAKABLE_CHARS = 12; + +for (const locale of ['vi', 'en', 'cs'] as const) { + const file = join(__dirname, '..', '..', '..', 'locales', `${locale}.json`); + const message: string = JSON.parse(readFileSync(file, 'utf8')).leaderboard + .blockedSpamCount; + assert.ok(message, `${locale}: missing leaderboard.blockedSpamCount`); + + // Keep the text of every plural branch; drop ICU syntax and placeholders. + const text = message + .replace(/\{count\}/g, '0') + .replace(/\{count, plural,/, '') + .replace(/\b(?:zero|one|two|few|many|other)\s*\{/g, ' ') + .replace(/[{}#]/g, ' '); + + for (const word of text.split(/\s+/).filter(Boolean)) { + assert.ok( + word.length <= MAX_UNBREAKABLE_CHARS, + `${locale}: "${word}" cannot wrap inside the leaderboard badge` + ); + } +} + +console.log('leaderboard-badge-copy: ok');